From 51ebac5e83686424e2a08c652527d23bd1fd1a6d Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Mon, 11 May 2026 02:47:06 +0900 Subject: [PATCH 1/5] test(query-devtools/Devtools): add tests for settings menu --- .../src/__tests__/Devtools.test.tsx | 180 ++++++++++++++++-- 1 file changed, 167 insertions(+), 13 deletions(-) diff --git a/packages/query-devtools/src/__tests__/Devtools.test.tsx b/packages/query-devtools/src/__tests__/Devtools.test.tsx index 6517f2a013..88c29e11a7 100644 --- a/packages/query-devtools/src/__tests__/Devtools.test.tsx +++ b/packages/query-devtools/src/__tests__/Devtools.test.tsx @@ -34,19 +34,6 @@ describe('Devtools', () => { beforeEach(() => { vi.useFakeTimers() previousRootFontSize = document.documentElement.style.fontSize - // jsdom doesn't implement `PointerEvent`; the DropdownMenu trigger checks - // `e.pointerType !== 'touch'` on pointerdown to decide whether to open, - // so we polyfill it as a thin wrapper around `MouseEvent`. - if (typeof window.PointerEvent === 'undefined') { - class FakePointerEvent extends MouseEvent { - pointerType: string - constructor(type: string, init: PointerEventInit = {}) { - super(type, init) - this.pointerType = init.pointerType ?? 'mouse' - } - } - vi.stubGlobal('PointerEvent', FakePointerEvent) - } vi.stubGlobal('localStorage', { getItem: (key: string) => Object.prototype.hasOwnProperty.call(storage, key) @@ -804,4 +791,171 @@ describe('Devtools', () => { expect(afterSecondToggle).toBe(afterFirstToggle === '1' ? '-1' : '1') }) }) + + describe('settings menu', () => { + it('should render the settings button', () => { + const rendered = renderDevtools({ initialIsOpen: true }) + + expect( + rendered.getByLabelText('Open settings menu'), + ).toBeInTheDocument() + }) + + it('should show "Position" sub-trigger when the settings menu is opened', () => { + const rendered = renderDevtools({ initialIsOpen: true }) + + fireEvent.keyDown(rendered.getByLabelText('Open settings menu'), { + key: 'Enter', + }) + + // The menu is rendered through a Portal mounted on `document.body`, + // outside `rendered.container`, so look it up via `document` directly. + expect( + document.querySelector('.tsqd-settings-menu-sub-trigger-position'), + ).not.toBeNull() + }) + + it('should open "Position" sub-menu when the sub-trigger is activated', () => { + const rendered = renderDevtools({ initialIsOpen: true }) + + fireEvent.keyDown(rendered.getByLabelText('Open settings menu'), { + key: 'Enter', + }) + + const subTrigger = document.querySelector( + '.tsqd-settings-menu-sub-trigger-position', + ) + if (subTrigger) { + fireEvent.keyDown(subTrigger, { key: 'ArrowRight' }) + } + + expect( + document.querySelector('[aria-label="Position settings"]'), + ).not.toBeNull() + }) + + it('should persist "position" when a position radio item is selected', () => { + const rendered = renderDevtools({ initialIsOpen: true }) + + fireEvent.keyDown(rendered.getByLabelText('Open settings menu'), { + key: 'Enter', + }) + + const subTrigger = document.querySelector( + '.tsqd-settings-menu-sub-trigger-position', + ) + if (subTrigger) { + fireEvent.keyDown(subTrigger, { key: 'ArrowRight' }) + } + + const topItem = document.querySelector( + '.tsqd-settings-menu-position-btn-top', + ) + if (topItem) { + fireEvent.keyDown(topItem, { key: 'Enter' }) + } + + expect(localStorage.getItem('TanstackQueryDevtools.position')).toBe( + 'top', + ) + }) + + it('should open "Theme" sub-menu when the sub-trigger is activated', () => { + const rendered = renderDevtools({ initialIsOpen: true }) + + fireEvent.keyDown(rendered.getByLabelText('Open settings menu'), { + key: 'Enter', + }) + + const themeTrigger = Array.from( + document.querySelectorAll( + '.tsqd-settings-menu-sub-trigger', + ), + ).find((el) => el.textContent.includes('Theme')) + if (themeTrigger) { + fireEvent.keyDown(themeTrigger, { key: 'ArrowRight' }) + } + + expect( + document.querySelector('[aria-label="Theme preference"]'), + ).not.toBeNull() + }) + + it('should persist "theme_preference" when a theme radio item is selected', () => { + const rendered = renderDevtools({ initialIsOpen: true }) + + fireEvent.keyDown(rendered.getByLabelText('Open settings menu'), { + key: 'Enter', + }) + + const themeTrigger = Array.from( + document.querySelectorAll( + '.tsqd-settings-menu-sub-trigger', + ), + ).find((el) => el.textContent.includes('Theme')) + if (themeTrigger) { + fireEvent.keyDown(themeTrigger, { key: 'ArrowRight' }) + } + + const themeMenu = document.querySelector( + '[aria-label="Theme preference"]', + ) + const lightItem = Array.from( + themeMenu?.querySelectorAll('[role="menuitemradio"]') ?? + [], + ).find((el) => el.textContent.includes('Light')) + if (lightItem) { + fireEvent.keyDown(lightItem, { key: 'Enter' }) + } + + expect( + localStorage.getItem('TanstackQueryDevtools.theme_preference'), + ).toBe('light') + }) + + it('should open "Hide disabled queries" sub-menu when the sub-trigger is activated', () => { + const rendered = renderDevtools({ initialIsOpen: true }) + + fireEvent.keyDown(rendered.getByLabelText('Open settings menu'), { + key: 'Enter', + }) + + const hideTrigger = document.querySelector( + '.tsqd-settings-menu-sub-trigger-disabled-queries', + ) + if (hideTrigger) { + fireEvent.keyDown(hideTrigger, { key: 'ArrowRight' }) + } + + expect( + document.querySelector('[aria-label="Hide disabled queries setting"]'), + ).not.toBeNull() + }) + + it('should persist "hideDisabledQueries" when a hide-disabled radio item is selected', () => { + const rendered = renderDevtools({ initialIsOpen: true }) + + fireEvent.keyDown(rendered.getByLabelText('Open settings menu'), { + key: 'Enter', + }) + + const hideTrigger = document.querySelector( + '.tsqd-settings-menu-sub-trigger-disabled-queries', + ) + if (hideTrigger) { + fireEvent.keyDown(hideTrigger, { key: 'ArrowRight' }) + } + + const hideItem = document.querySelector( + '.tsqd-settings-menu-position-btn-hide', + ) + if (hideItem) { + fireEvent.keyDown(hideItem, { key: 'Enter' }) + } + + expect( + localStorage.getItem('TanstackQueryDevtools.hideDisabledQueries'), + ).toBe('true') + }) + }) }) From e3d358aa01357ddc6ff845e99253199e5d2749be Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 17:48:50 +0000 Subject: [PATCH 2/5] ci: apply automated fixes --- packages/query-devtools/src/__tests__/Devtools.test.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/query-devtools/src/__tests__/Devtools.test.tsx b/packages/query-devtools/src/__tests__/Devtools.test.tsx index 88c29e11a7..18d2ab39d5 100644 --- a/packages/query-devtools/src/__tests__/Devtools.test.tsx +++ b/packages/query-devtools/src/__tests__/Devtools.test.tsx @@ -796,9 +796,7 @@ describe('Devtools', () => { it('should render the settings button', () => { const rendered = renderDevtools({ initialIsOpen: true }) - expect( - rendered.getByLabelText('Open settings menu'), - ).toBeInTheDocument() + expect(rendered.getByLabelText('Open settings menu')).toBeInTheDocument() }) it('should show "Position" sub-trigger when the settings menu is opened', () => { @@ -855,9 +853,7 @@ describe('Devtools', () => { fireEvent.keyDown(topItem, { key: 'Enter' }) } - expect(localStorage.getItem('TanstackQueryDevtools.position')).toBe( - 'top', - ) + expect(localStorage.getItem('TanstackQueryDevtools.position')).toBe('top') }) it('should open "Theme" sub-menu when the sub-trigger is activated', () => { From 1515febf7990ad072812032c7ab12bd386c1de97 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Mon, 11 May 2026 02:52:56 +0900 Subject: [PATCH 3/5] test(query-devtools/Devtools): fail fast with 'expect' instead of conditional guards on selector lookups --- .../src/__tests__/Devtools.test.tsx | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/packages/query-devtools/src/__tests__/Devtools.test.tsx b/packages/query-devtools/src/__tests__/Devtools.test.tsx index 18d2ab39d5..3320c778b7 100644 --- a/packages/query-devtools/src/__tests__/Devtools.test.tsx +++ b/packages/query-devtools/src/__tests__/Devtools.test.tsx @@ -823,9 +823,8 @@ describe('Devtools', () => { const subTrigger = document.querySelector( '.tsqd-settings-menu-sub-trigger-position', ) - if (subTrigger) { - fireEvent.keyDown(subTrigger, { key: 'ArrowRight' }) - } + expect(subTrigger).not.toBeNull() + fireEvent.keyDown(subTrigger!, { key: 'ArrowRight' }) expect( document.querySelector('[aria-label="Position settings"]'), @@ -842,16 +841,14 @@ describe('Devtools', () => { const subTrigger = document.querySelector( '.tsqd-settings-menu-sub-trigger-position', ) - if (subTrigger) { - fireEvent.keyDown(subTrigger, { key: 'ArrowRight' }) - } + expect(subTrigger).not.toBeNull() + fireEvent.keyDown(subTrigger!, { key: 'ArrowRight' }) const topItem = document.querySelector( '.tsqd-settings-menu-position-btn-top', ) - if (topItem) { - fireEvent.keyDown(topItem, { key: 'Enter' }) - } + expect(topItem).not.toBeNull() + fireEvent.keyDown(topItem!, { key: 'Enter' }) expect(localStorage.getItem('TanstackQueryDevtools.position')).toBe('top') }) @@ -868,9 +865,8 @@ describe('Devtools', () => { '.tsqd-settings-menu-sub-trigger', ), ).find((el) => el.textContent.includes('Theme')) - if (themeTrigger) { - fireEvent.keyDown(themeTrigger, { key: 'ArrowRight' }) - } + expect(themeTrigger).not.toBeUndefined() + fireEvent.keyDown(themeTrigger!, { key: 'ArrowRight' }) expect( document.querySelector('[aria-label="Theme preference"]'), @@ -889,9 +885,8 @@ describe('Devtools', () => { '.tsqd-settings-menu-sub-trigger', ), ).find((el) => el.textContent.includes('Theme')) - if (themeTrigger) { - fireEvent.keyDown(themeTrigger, { key: 'ArrowRight' }) - } + expect(themeTrigger).not.toBeUndefined() + fireEvent.keyDown(themeTrigger!, { key: 'ArrowRight' }) const themeMenu = document.querySelector( '[aria-label="Theme preference"]', @@ -900,9 +895,8 @@ describe('Devtools', () => { themeMenu?.querySelectorAll('[role="menuitemradio"]') ?? [], ).find((el) => el.textContent.includes('Light')) - if (lightItem) { - fireEvent.keyDown(lightItem, { key: 'Enter' }) - } + expect(lightItem).not.toBeUndefined() + fireEvent.keyDown(lightItem!, { key: 'Enter' }) expect( localStorage.getItem('TanstackQueryDevtools.theme_preference'), @@ -919,9 +913,8 @@ describe('Devtools', () => { const hideTrigger = document.querySelector( '.tsqd-settings-menu-sub-trigger-disabled-queries', ) - if (hideTrigger) { - fireEvent.keyDown(hideTrigger, { key: 'ArrowRight' }) - } + expect(hideTrigger).not.toBeNull() + fireEvent.keyDown(hideTrigger!, { key: 'ArrowRight' }) expect( document.querySelector('[aria-label="Hide disabled queries setting"]'), @@ -938,16 +931,14 @@ describe('Devtools', () => { const hideTrigger = document.querySelector( '.tsqd-settings-menu-sub-trigger-disabled-queries', ) - if (hideTrigger) { - fireEvent.keyDown(hideTrigger, { key: 'ArrowRight' }) - } + expect(hideTrigger).not.toBeNull() + fireEvent.keyDown(hideTrigger!, { key: 'ArrowRight' }) const hideItem = document.querySelector( '.tsqd-settings-menu-position-btn-hide', ) - if (hideItem) { - fireEvent.keyDown(hideItem, { key: 'Enter' }) - } + expect(hideItem).not.toBeNull() + fireEvent.keyDown(hideItem!, { key: 'Enter' }) expect( localStorage.getItem('TanstackQueryDevtools.hideDisabledQueries'), From e6432afc5a74ad53efd3a453fa7c95d80d4d5998 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Mon, 11 May 2026 02:57:38 +0900 Subject: [PATCH 4/5] test(query-devtools/Devtools): coerce 'el.textContent' to string for 'TS18047' under strict 'vue-tsc' build --- packages/query-devtools/src/__tests__/Devtools.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/query-devtools/src/__tests__/Devtools.test.tsx b/packages/query-devtools/src/__tests__/Devtools.test.tsx index 3320c778b7..0af643fb04 100644 --- a/packages/query-devtools/src/__tests__/Devtools.test.tsx +++ b/packages/query-devtools/src/__tests__/Devtools.test.tsx @@ -864,7 +864,7 @@ describe('Devtools', () => { document.querySelectorAll( '.tsqd-settings-menu-sub-trigger', ), - ).find((el) => el.textContent.includes('Theme')) + ).find((el) => String(el.textContent).includes('Theme')) expect(themeTrigger).not.toBeUndefined() fireEvent.keyDown(themeTrigger!, { key: 'ArrowRight' }) @@ -884,7 +884,7 @@ describe('Devtools', () => { document.querySelectorAll( '.tsqd-settings-menu-sub-trigger', ), - ).find((el) => el.textContent.includes('Theme')) + ).find((el) => String(el.textContent).includes('Theme')) expect(themeTrigger).not.toBeUndefined() fireEvent.keyDown(themeTrigger!, { key: 'ArrowRight' }) @@ -894,7 +894,7 @@ describe('Devtools', () => { const lightItem = Array.from( themeMenu?.querySelectorAll('[role="menuitemradio"]') ?? [], - ).find((el) => el.textContent.includes('Light')) + ).find((el) => String(el.textContent).includes('Light')) expect(lightItem).not.toBeUndefined() fireEvent.keyDown(lightItem!, { key: 'Enter' }) From 3beb4ff1716a9fb5f9d5935b6f77f7fd7a509faa Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Mon, 11 May 2026 03:06:00 +0900 Subject: [PATCH 5/5] test(query-devtools/Devtools): remove redundant 'should render the settings button' covered by other settings menu tests --- packages/query-devtools/src/__tests__/Devtools.test.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/query-devtools/src/__tests__/Devtools.test.tsx b/packages/query-devtools/src/__tests__/Devtools.test.tsx index 0af643fb04..1ea120c459 100644 --- a/packages/query-devtools/src/__tests__/Devtools.test.tsx +++ b/packages/query-devtools/src/__tests__/Devtools.test.tsx @@ -793,12 +793,6 @@ describe('Devtools', () => { }) describe('settings menu', () => { - it('should render the settings button', () => { - const rendered = renderDevtools({ initialIsOpen: true }) - - expect(rendered.getByLabelText('Open settings menu')).toBeInTheDocument() - }) - it('should show "Position" sub-trigger when the settings menu is opened', () => { const rendered = renderDevtools({ initialIsOpen: true })