From 8aff33011d453688c0ccb1e6a5475990addde4f1 Mon Sep 17 00:00:00 2001 From: Ruud Andriessen Date: Fri, 16 Oct 2026 13:43:48 +0200 Subject: [PATCH] fix(apollo-vertex): hide command separator from listbox semantics cmdk's Command.Separator hardcodes role="separator" after spreading props, so it cannot be overridden. Inside the role="listbox" list this fails axe aria-required-children. cmdk 1.1.1 is the latest release and the issue is still open upstream (dip/cmdk#179); shadcn new-york-v4 uses the same primitive. CommandSeparator now renders its own div with role="none" and reproduces cmdk's visibility logic (hidden while searching unless alwaysRender) via useCommandState. The asChild prop from cmdk's primitive is no longer accepted. Vitest now resolves tsconfig paths so registry components that import other registry components can be tested. --- .../registry/command/command.test.tsx | 78 +++++++++++++++++++ .../registry/command/command.tsx | 19 ++++- packages/apollo-vertex/vitest.config.ts | 3 +- 3 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 packages/apollo-vertex/registry/command/command.test.tsx diff --git a/packages/apollo-vertex/registry/command/command.test.tsx b/packages/apollo-vertex/registry/command/command.test.tsx new file mode 100644 index 0000000000..3a20257c7f --- /dev/null +++ b/packages/apollo-vertex/registry/command/command.test.tsx @@ -0,0 +1,78 @@ +import { cleanup, render } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { + Command, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from "./command"; + +vi.stubGlobal( + "ResizeObserver", + class { + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); + }, +); + +Element.prototype.scrollIntoView = vi.fn(); + +afterEach(cleanup); + +function renderCommand({ + search, + alwaysRender, +}: { + search?: string; + alwaysRender?: boolean; +} = {}) { + return render( + + + + + {"Calendar"} + + + + {"Profile"} + + + , + ); +} + +describe("CommandSeparator", () => { + it("renders inside the listbox without the separator role", () => { + const { container, getByRole } = renderCommand(); + + const separator = container.querySelector( + '[data-slot="command-separator"]', + ); + + expect(separator).not.toBeNull(); + expect(separator?.getAttribute("role")).toBe("none"); + expect(getByRole("listbox").contains(separator)).toBe(true); + expect(container.querySelector('[role="separator"]')).toBeNull(); + }); + + it("is hidden while searching", () => { + const { container } = renderCommand({ search: "cal" }); + + expect( + container.querySelector('[data-slot="command-separator"]'), + ).toBeNull(); + }); + + it("stays visible while searching when alwaysRender is set", () => { + const { container } = renderCommand({ search: "cal", alwaysRender: true }); + + expect( + container.querySelector('[data-slot="command-separator"]'), + ).not.toBeNull(); + }); +}); diff --git a/packages/apollo-vertex/registry/command/command.tsx b/packages/apollo-vertex/registry/command/command.tsx index cab3c271b2..916773be59 100644 --- a/packages/apollo-vertex/registry/command/command.tsx +++ b/packages/apollo-vertex/registry/command/command.tsx @@ -1,6 +1,6 @@ "use client"; -import { Command as CommandPrimitive } from "cmdk"; +import { Command as CommandPrimitive, useCommandState } from "cmdk"; import { SearchIcon } from "lucide-react"; import * as React from "react"; import { @@ -125,13 +125,25 @@ function CommandGroup({ ); } +interface CommandSeparatorProps extends React.ComponentProps<"div"> { + alwaysRender?: boolean; +} + function CommandSeparator({ className, + alwaysRender = false, ...props -}: React.ComponentProps) { +}: CommandSeparatorProps) { + const isSearchEmpty = useCommandState((state) => !state.search); + + if (!alwaysRender && !isSearchEmpty) { + return null; + } + return ( - @@ -181,3 +193,4 @@ export { CommandShortcut, CommandSeparator, }; +export type { CommandSeparatorProps }; diff --git a/packages/apollo-vertex/vitest.config.ts b/packages/apollo-vertex/vitest.config.ts index 5f63b1ed24..b98e9f3b4a 100644 --- a/packages/apollo-vertex/vitest.config.ts +++ b/packages/apollo-vertex/vitest.config.ts @@ -1,9 +1,8 @@ -import { resolve } from "node:path"; import { defineConfig } from "vitest/config"; export default defineConfig({ resolve: { - alias: { "@": resolve(__dirname, ".") }, + tsconfigPaths: true, }, test: { environment: "jsdom",