Skip to content
Merged
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
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,18 @@ The repo uses Husky with lint-staged. On commit:
- **Formatting**: Prettier 3.4.2
- **CSS**: Sass 1.93.2

## Bug Fix Workflow (TDD)

When fixing bugs, always follow Test-Driven Development:

1. **Write the test first** - Create a failing test that reproduces the bug
2. **Confirm it fails** - Run the test to verify it captures the broken behavior
3. **Implement the fix** - Make the minimal code change to fix the issue
4. **Verify the test passes** - Run the test again to confirm the fix works
5. **Run full test suite** - Ensure no regressions with `yarn test:ci`

This ensures every bug fix has regression coverage and documents the expected behavior.

## Code Conventions

- **Prettier handles all code formatting** - don't worry about tabs vs spaces
Expand Down
38 changes: 38 additions & 0 deletions src/test/timepicker_test.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,44 @@ describe("TimePicker", () => {
expect(resizeObserverCallback).toBe(null);
});
});

it("should not cause infinite loop when resize callback is called multiple times", async () => {
const { container } = render(
<DatePicker
inline
selected={new Date()}
showTimeSelect
timeIntervals={15}
/>,
);

await waitFor(() => {
expect(mockObserve).toHaveBeenCalledTimes(1);
});

const resizeObserverCallback = getResizeObserverCallback();
const mockObserveElement = mockObserve.mock.calls[0][0];
expect(typeof resizeObserverCallback).toBe("function");

const timeList = container.querySelector(
".react-datepicker__time-list",
) as HTMLElement;
expect(timeList).not.toBeNull();

// Get initial height
const initialHeight = timeList.style.height;

// Call resize callback multiple times (simulating what would happen in an infinite loop)
if (resizeObserverCallback) {
resizeObserverCallback([], mockObserveElement);
resizeObserverCallback([], mockObserveElement);
resizeObserverCallback([], mockObserveElement);
}

// Height should remain stable (not grow infinitely)
// The fix ensures setState is only called when height actually changes
expect(timeList.style.height).toBe(initialHeight);
});
});

it("should update on input time change", () => {
Expand Down
11 changes: 8 additions & 3 deletions src/time.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,14 @@ export default class Time extends Component<TimeProps, TimeState> {

private updateContainerHeight(): void {
if (this.props.monthRef && this.header) {
this.setState({
height: this.props.monthRef.clientHeight - this.header.clientHeight,
});
const newHeight =
this.props.monthRef.clientHeight - this.header.clientHeight;
// Only update state if height actually changed to prevent infinite resize loops
if (this.state.height !== newHeight) {
this.setState({
height: newHeight,
});
}
}
}

Expand Down
Loading