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
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@
## 2024-07-01 - Testing components with focusable disabled button wrappers
**Learning:** When native disabled buttons are wrapped in a focusable `span` to provide accessible tooltips, tests that previously found and clicked the `button` (by temporarily removing the `disabled` attribute) may fail or become overly complex. It is cleaner and more accurate to query the wrapper element (e.g. via its `title`) and fire events on it, reflecting the actual accessible DOM structure.
**Action:** When testing UI components that wrap disabled buttons in a focusable span for accessibility (e.g., using a tooltip/title), use `screen.getByTitle(...)` to query the wrapper element for interactions like `fireEvent.click` rather than `screen.getByRole('button')`.

## 2025-07-04 - Accessible Clear Button Unmounting Focus Management
**Learning:** When a conditionally rendered interactive element (like a 'Clear' button) triggers its own unmounting via a state change (e.g., clearing an input value), focus is lost and reset to the document body, breaking keyboard navigation.
**Action:** Always use a `useRef` to programmatically return focus to the associated primary element (e.g., the input field) prior to the state update when building self-unmounting clear buttons.
16 changes: 16 additions & 0 deletions apps/desktop/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,22 @@ describe("App", () => {
});
});

it("clears the YouTube URL and resets focus when the clear button is clicked", async () => {
render(<App />);
const input = screen.getByPlaceholderText(/YouTube URL.../i);
fireEvent.change(input, { target: { value: "https://youtube.com/watch?v=abc123DEF45" } });

// Using simple assertions rather than toHaveValue because RTL setup might not include custom matchers
expect((input as HTMLInputElement).value).toBe("https://youtube.com/watch?v=abc123DEF45");

const clearBtn = screen.getByRole("button", { name: /Clear YouTube URL/i });
fireEvent.click(clearBtn);

expect((input as HTMLInputElement).value).toBe("");
expect(screen.queryByRole("button", { name: /Clear YouTube URL/i })).toBeNull();
expect(document.activeElement).toBe(input);
});

it("rejects non-http YouTube URL", async () => {
render(<App />);
const input = screen.getByPlaceholderText(/YouTube URL.../i);
Expand Down
Loading