fix(FN-725): sync isExpanded and isDisclosureExpanded states in QuickEntryBox
- Fix state desync between isExpanded and isDisclosureExpanded in QuickEntryBox component - Ensure both states update together to prevent UI inconsistencies - Add regression tests for state synchronization scenarios - Add 72 lines of new test coverage in QuickEntryBox.test.tsx
This commit is contained in:
@@ -473,8 +473,11 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
const showExpandedControls = isDisclosureExpanded;
|
const showExpandedControls = isDisclosureExpanded;
|
||||||
|
|
||||||
const toggleExpanded = useCallback(() => {
|
const toggleExpanded = useCallback(() => {
|
||||||
setIsDisclosureExpanded((prev) => !prev);
|
setIsDisclosureExpanded((prev) => {
|
||||||
setIsExpanded((prev) => !prev);
|
const next = !prev;
|
||||||
|
setIsExpanded(next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -493,7 +496,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
data-testid="quick-entry-input"
|
data-testid="quick-entry-input"
|
||||||
rows={1}
|
rows={1}
|
||||||
aria-controls="quick-entry-controls"
|
aria-controls="quick-entry-controls"
|
||||||
aria-expanded={isExpanded}
|
aria-expanded={isDisclosureExpanded}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -827,6 +827,78 @@ describe("QuickEntryBox", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("State sync between isExpanded and isDisclosureExpanded", () => {
|
||||||
|
it("focus then toggle shows controls without collapsing textarea", () => {
|
||||||
|
renderQuickEntryBox();
|
||||||
|
const textarea = screen.getByTestId("quick-entry-input");
|
||||||
|
const controls = document.getElementById("quick-entry-controls");
|
||||||
|
|
||||||
|
// Focus auto-expands textarea height (isExpanded=true) but NOT disclosure
|
||||||
|
fireEvent.focus(textarea);
|
||||||
|
expect(textarea.classList.contains("quick-entry-input--expanded")).toBe(true);
|
||||||
|
expect(controls?.hasAttribute("hidden")).toBe(true); // controls still hidden
|
||||||
|
|
||||||
|
// Now click toggle — should expand disclosure AND keep textarea expanded
|
||||||
|
expandQuickEntry();
|
||||||
|
expect(textarea.classList.contains("quick-entry-input--expanded")).toBe(true);
|
||||||
|
expect(controls?.hasAttribute("hidden")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toggle twice from focused state returns to collapsed", () => {
|
||||||
|
renderQuickEntryBox();
|
||||||
|
const textarea = screen.getByTestId("quick-entry-input");
|
||||||
|
const controls = document.getElementById("quick-entry-controls");
|
||||||
|
|
||||||
|
// Focus → auto-expand textarea
|
||||||
|
fireEvent.focus(textarea);
|
||||||
|
expect(textarea.classList.contains("quick-entry-input--expanded")).toBe(true);
|
||||||
|
|
||||||
|
// Toggle expand
|
||||||
|
expandQuickEntry();
|
||||||
|
expect(textarea.classList.contains("quick-entry-input--expanded")).toBe(true);
|
||||||
|
expect(controls?.hasAttribute("hidden")).toBe(false);
|
||||||
|
|
||||||
|
// Toggle collapse — both states should collapse together
|
||||||
|
expandQuickEntry();
|
||||||
|
expect(textarea.classList.contains("quick-entry-input--expanded")).toBe(false);
|
||||||
|
expect(controls?.hasAttribute("hidden")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("after task creation with autoExpand, focus restore does not show controls unless toggle was used", async () => {
|
||||||
|
const { props } = renderQuickEntryBox();
|
||||||
|
const textarea = screen.getByTestId("quick-entry-input");
|
||||||
|
const controls = document.getElementById("quick-entry-controls");
|
||||||
|
|
||||||
|
// Type and submit without toggling disclosure
|
||||||
|
fireEvent.change(textarea, { target: { value: "New task" } });
|
||||||
|
fireEvent.keyDown(textarea, { key: "Enter" });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(props.onCreate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
// After creation, focus is restored → autoExpand triggers isExpanded=true
|
||||||
|
// But disclosure should still be false (controls hidden)
|
||||||
|
expect(textarea.classList.contains("quick-entry-input--expanded")).toBe(true);
|
||||||
|
expect(controls?.hasAttribute("hidden")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("textarea aria-expanded reflects disclosure state, not textarea height", () => {
|
||||||
|
renderQuickEntryBox();
|
||||||
|
const textarea = screen.getByTestId("quick-entry-input");
|
||||||
|
|
||||||
|
// Focus auto-expands textarea height but NOT disclosure
|
||||||
|
fireEvent.focus(textarea);
|
||||||
|
expect(textarea.classList.contains("quick-entry-input--expanded")).toBe(true);
|
||||||
|
// aria-expanded should still be false (reflects disclosure, not height)
|
||||||
|
expect(textarea.getAttribute("aria-expanded")).toBe("false");
|
||||||
|
|
||||||
|
// Toggle expand — now aria-expanded should be true
|
||||||
|
expandQuickEntry();
|
||||||
|
expect(textarea.getAttribute("aria-expanded")).toBe("true");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("localStorage persistence", () => {
|
describe("localStorage persistence", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Clear localStorage before each test
|
// Clear localStorage before each test
|
||||||
|
|||||||
Reference in New Issue
Block a user