Skip to content

[Test Improver] Add unit tests for useClickOutside composable - #77

Draft
github-actions[bot] wants to merge 1 commit into
masterfrom
test-assist/use-click-outside-ab3a7912c9a5a2e6
Draft

[Test Improver] Add unit tests for useClickOutside composable#77
github-actions[bot] wants to merge 1 commit into
masterfrom
test-assist/use-click-outside-ab3a7912c9a5a2e6

Conversation

@github-actions

Copy link
Copy Markdown

🤖 This PR was created by Test Improver, an automated AI assistant focused on improving test coverage.

Goal and Rationale

Add unit tests for shell/composables/useClickOutside.ts. This composable manages DOM click-outside detection — it registers click and pointerdown listeners on window to call a callback when the user clicks outside a given element.

It had zero test coverage despite containing non-trivial logic:

  • An ignore option supporting CSS selectors to suppress callbacks from specific elements (e.g. dropdown overlays, modal backdrops)
  • A pointerdown guard that prevents drag-from-inside-to-outside from falsely triggering the callback (setShouldListen)
  • A keyboard-event path (detail === 0) that sets shouldListen via shouldIgnore to respect the ignore list for keyboard-triggered clicks

Approach

  • Used @vue/test-utils mount with a minimal defineComponent wrapper to trigger the onMounted/onBeforeUnmount lifecycle hooks naturally
  • Spied on window.addEventListener / window.removeEventListener with mockImplementation to capture the handlers without altering DOM behaviour
  • Passed plain mock event objects (with explicit target, composedPath, detail) to exercise the listener logic in isolation
  • Used it.each with named-object entries for the lifecycle registration/deregistration tests

Coverage Impact

Metric Before After
useClickOutside statements 0% ~100%
useClickOutside branches 0% ~100%

Test Cases (13 total)

Lifecycle (×2 for click + pointerdown):

  • registers the click/pointerdown event listener on mount
  • removes the click/pointerdown event listener on unmount

Click listener:

  • calls callback when click target is outside the component element
  • does not call callback when click target is the component element
  • does not call callback when the composedPath of the click includes the component element
  • does not call callback when component.value is null
  • does not throw when callback is not a function

Ignore option:

  • suppresses callback when a prior pointerdown targeted a css-selector-matched ignore element
  • calls callback when pointerdown targets a non-ignored element outside the component

Pointerdown inside component:

  • does not call callback when pointerdown was inside the component and click was outside

Keyboard click (detail=0):

  • does not call callback when detail is 0 and click event target matches an ignore selector

Trade-offs

  • Tests mock window.addEventListener to capture handlers — this avoids JSDOM composedPath() limitations on real events and keeps tests deterministic
  • useBasicSetupFocusTrap and useWatcherBasedSetupFocusTrapWithDestroyIncluded (which use this composable) are not tested here; those require component-level integration tests

Reproducibility

NODE_OPTIONS=--max_old_space_size=8192 yarn test:ci --testPathPattern=shell/composables/__tests__/useClickOutside

Test Status

✅ All 13 new tests pass
✅ All 42 existing composable tests continue to pass
✅ ESLint: no warnings or errors

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • download.cypress.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "download.cypress.io"

See Network Configuration for more information.

Generated by Daily Test Improver · ● 3.4M ·

Cover the key behaviours of useClickOutside:
- Lifecycle: click and pointerdown listeners registered on mount,
  deregistered on unmount (it.each for both events)
- Click handler: callback called for outside click; not called when
  click target equals or is inside the component element; not called
  when component.value is null; no throw when callback is non-function
- Ignore option (CSS selector): callback suppressed after a pointerdown
  on a selector-matched element; callback fires when pointerdown is on
  a non-ignored element outside the component
- Pointerdown inside component: drag-inside-then-click-outside does not
  fire callback (setShouldListen guard)
- Keyboard click (detail=0): callback suppressed when the click event
  target matches an ignore selector

13 tests total; all pass with 0 ESLint warnings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants