diff --git a/packages/code-editor/README.md b/packages/code-editor/README.md index 228b8c030c..0bcb30d173 100644 --- a/packages/code-editor/README.md +++ b/packages/code-editor/README.md @@ -317,546 +317,91 @@ editorRef.current.downloadContent(); // No filename provided ## Test Utilities -The `@leafygreen-ui/code-editor` package provides a comprehensive testing utility to help you test CodeEditor components in your Jest tests. +The `@leafygreen-ui/code-editor` package provides test utilities to help test CodeEditor and Panel components in your applications. -### `renderCodeEditor(props?)` +### `getTestUtils(lgId?)` -Renders a CodeEditor component with the specified props for testing purposes. +Returns utilities for testing CodeEditor and Panel components. These utilities follow the `@testing-library` pattern with `get*`, `find*`, and `query*` variants for each element. **Parameters:** -- `props` _(optional)_: Partial `CodeEditorProps` to pass to the CodeEditor component +- `lgId` _(optional)_: Custom lgId string to scope the queries. Must follow the pattern `lg-${string}`. Defaults to `'lg-code_editor'` **Returns:** -- `container`: The rendered container element from `@testing-library/react` -- `editor`: Editor test utilities object with methods for interacting with the editor +An object with methods for testing the CodeEditor and its Panel. -**Example:** +#### Editor Utilities -```tsx -import { renderCodeEditor } from '@leafygreen-ui/code-editor'; - -test('renders code editor with default value', async () => { - const { editor, container } = renderCodeEditor({ - defaultValue: 'console.log("Hello World");', - }); - await editor.waitForEditorView(); - - expect(container).toHaveTextContent('console.log("Hello World");'); -}); -``` - -### Editor Test Utilities - -The `editor` object returned by `renderCodeEditor` provides the following methods: - -#### `editor.waitForEditorView(timeout?)` - -Waits for the editor view to be available before proceeding with tests. - -##### Parameters - -- `timeout` _(optional)_: Maximum time to wait in milliseconds (default: 5000) - -##### Returns - -Promise that resolves when the editor view is available - -##### Example - -```tsx -const { editor } = renderCodeEditor(); -await editor.waitForEditorView(); // Wait before interacting with editor -``` - -#### `editor.getBySelector(selector, options?)` - -Returns the first element matching a specific CodeEditor selector. Throws an error if no element or multiple elements are found. - -##### Parameters - -- `selector`: The CSS selector from `CodeEditorSelectors` enum -- `options` _(optional)_: Object with optional `text` property for filtering by text content - -##### Example - -```tsx -// Get the content element -const content = editor.getBySelector(CodeEditorSelectors.Content); - -// Get a specific line number -const lineNumber = editor.getBySelector(CodeEditorSelectors.GutterElement, { - text: '1', -}); -``` - -#### `editor.getAllBySelector(selector, options?)` - -Returns all elements matching a specific CodeEditor selector. Throws an error if no elements are found. - -##### Parameters - -- `selector`: The CSS selector from `CodeEditorSelectors` enum -- `options` _(optional)_: Object with optional `text` property for filtering by text content - -##### Example - -```tsx -// Get all line numbers -const allLineNumbers = editor.getAllBySelector( - CodeEditorSelectors.GutterElement, -); -``` - -#### `editor.queryBySelector(selector, options?)` - -Returns the first element matching a specific CodeEditor selector, or null if not found. Useful when you're not sure if an element exists. - -##### Parameters - -- `selector`: The CSS selector from `CodeEditorSelectors` enum -- `options` _(optional)_: Object with optional `text` property for filtering by text content - -##### Example - -```tsx -// Check if fold gutter exists -const foldGutter = editor.queryBySelector(CodeEditorSelectors.FoldGutter); -expect(foldGutter).not.toBeInTheDocument(); -``` - -#### `editor.queryAllBySelector(selector, options?)` - -Returns all elements matching a specific CodeEditor selector, or null if none are found. - -##### Parameters - -- `selector`: The CSS selector from `CodeEditorSelectors` enum -- `options` _(optional)_: Object with optional `text` property for filtering by text content - -#### `editor.isReadOnly()` - -Checks if the editor is in read-only mode. - -##### Returns - -Boolean indicating whether the editor is in read-only mode - -##### Example - -```tsx -const { editor } = renderCodeEditor({ readOnly: true }); -await editor.waitForEditorView(); - -expect(editor.isReadOnly()).toBe(true); -``` - -#### `editor.getIndentUnit()` - -Retrieves the current indentation unit configuration from the editor. - -##### Returns - -The string used for indentation (spaces or tab character) - -##### Example - -```tsx -const { editor } = renderCodeEditor({ - indentUnit: 'space', - indentSize: 4, -}); -await editor.waitForEditorView(); - -expect(editor.getIndentUnit()).toBe(' '); // 4 spaces -``` - -#### `editor.isLineWrappingEnabled()` - -Checks if line wrapping is enabled in the editor. - -##### Returns - -Boolean indicating whether line wrapping is enabled - -##### Example - -```tsx -const { editor } = renderCodeEditor({ enableLineWrapping: true }); -await editor.waitForEditorView(); - -expect(editor.isLineWrappingEnabled()).toBe(true); -``` - -#### `editor.interactions.insertText(text, options?)` - -Inserts text into the editor at the specified position. - -##### Parameters - -- `text`: The text to insert -- `options` _(optional)_: Object with optional position properties - - `from`: Starting position for insertion (defaults to end of document) - - `to`: End position for replacement (optional) - -##### Example - -```tsx -import { act } from '@testing-library/react'; - -const { editor } = renderCodeEditor(); -await editor.waitForEditorView(); - -act(() => { - editor.interactions.insertText('new content'); -}); - -expect(editor.getBySelector(CodeEditorSelectors.Content)).toHaveTextContent( - 'new content', -); -``` - -#### `editor.getContent()` - -Gets the current text content of the editor. - -**Returns:** String containing the current editor content - -**Example:** - -```tsx -const { editor } = renderCodeEditor({ defaultValue: 'Hello World' }); -await editor.waitForEditorView(); +- `getEditor()` / `findEditor()` / `queryEditor()` - Get the CodeEditor root element +- `getContentContainer()` / `findContentContainer()` / `queryContentContainer()` - Get the content container element +- `getCopyButton()` / `findCopyButton()` / `queryCopyButton()` - Get the copy button element (when not using panel) -expect(editor.getContent()).toBe('Hello World'); -``` - -#### `editor.getHandle()` - -Gets the imperative handle instance for testing imperative methods like undo/redo. - -**Returns:** The editor handle instance with all imperative methods - -**Example:** - -```tsx -const { editor } = renderCodeEditor(); -await editor.waitForEditorView(); - -const handle = editor.getHandle(); -expect(typeof handle.undo).toBe('function'); -expect(typeof handle.redo).toBe('function'); -expect(typeof handle.downloadContent).toBe('function'); -``` - -#### `editor.interactions.undo()` - -Performs an undo operation on the editor. - -**Returns:** Boolean indicating if undo was successful - -**Example:** - -```tsx -const { editor } = renderCodeEditor({ defaultValue: 'original' }); -await editor.waitForEditorView(); - -editor.interactions.insertText(' modified'); -expect(editor.getContent()).toBe('original modified'); +#### Loading Utilities -const success = editor.interactions.undo(); -expect(success).toBe(true); -expect(editor.getContent()).toBe('original'); -``` +- `isLoading()` - Checks if the editor is currently in a loading state +- `waitForLoadingToComplete(timeout?)` - Waits for the loading state to complete (returns `Promise`) -#### `editor.interactions.redo()` +#### Panel Utilities -Performs a redo operation on the editor. +- `getPanelUtils()` - Gets panel-specific utilities if panel is present -**Returns:** Boolean indicating if redo was successful +Returns an object with: -**Example:** +- `getPanelElement()` / `findPanelElement()` / `queryPanelElement()` - Get the panel element +- `getFormatButton()` / `findFormatButton()` / `queryFormatButton()` - Get the format button element +- `getPanelCopyButton()` / `findPanelCopyButton()` / `queryPanelCopyButton()` - Get the panel's copy button element +- `getSecondaryMenuButton()` / `findSecondaryMenuButton()` / `querySecondaryMenuButton()` - Get the secondary menu button element +- `getSecondaryMenu()` / `findSecondaryMenu()` / `querySecondaryMenu()` - Get the secondary menu element -```tsx -const { editor } = renderCodeEditor({ defaultValue: 'original' }); -await editor.waitForEditorView(); - -editor.interactions.insertText(' modified'); -editor.interactions.undo(); -expect(editor.getContent()).toBe('original'); - -const success = editor.interactions.redo(); -expect(success).toBe(true); -expect(editor.getContent()).toBe('original modified'); -``` +#### Example Usage -### Complete Test Example +**Basic Testing:** ```tsx -import { - renderCodeEditor, - CodeEditorSelectors, -} from '@leafygreen-ui/code-editor'; -import { act } from '@testing-library/react'; - -test('comprehensive editor testing', async () => { - const { editor, container } = renderCodeEditor({ - defaultValue: 'const greeting = "Hello";', - language: LanguageName.javascript, - enableLineNumbers: true, - enableCodeFolding: true, - }); - - // Wait for editor to initialize - await editor.waitForEditorView(); - - // Check initial content - expect(container).toHaveTextContent('const greeting = "Hello";'); - - // Verify line numbers are present - expect( - editor.getBySelector(CodeEditorSelectors.GutterElement, { text: '1' }), - ).toBeInTheDocument(); - - // Verify fold gutter is present - expect( - editor.getBySelector(CodeEditorSelectors.FoldGutter), - ).toBeInTheDocument(); - - // Insert new text - act(() => { - editor.interactions.insertText('\nconsole.log(greeting);', { from: 25 }); - }); - - // Verify the new content using getContent() - expect(editor.getContent()).toBe( - 'const greeting = "Hello";\nconsole.log(greeting);', - ); - - // Test undo/redo functionality - const undoSuccess = editor.interactions.undo(); - expect(undoSuccess).toBe(true); - expect(editor.getContent()).toBe('const greeting = "Hello";'); +import { render } from '@testing-library/react'; +import { getTestUtils } from '@leafygreen-ui/code-editor/testing'; +import { CodeEditor, Panel, LanguageName } from '@leafygreen-ui/code-editor'; - const redoSuccess = editor.interactions.redo(); - expect(redoSuccess).toBe(true); - expect(editor.getContent()).toBe( - 'const greeting = "Hello";\nconsole.log(greeting);', +test('CodeEditor structure and panel work correctly', async () => { + render( + + } + data-lgid="lg-my-editor" + />, ); -}); -``` - -### Panel Test Utilities - -The package also provides comprehensive testing utilities for the Panel component, making it easy to test Panel interactions and behaviors. - -#### `renderPanel(config?)` - -Renders a Panel component with proper LeafyGreen and CodeEditor context for testing. - -**Parameters:** - -- `config` _(optional)_: Configuration object with the following properties: - - `panelProps` _(optional)_: Partial `PanelProps` to pass to the Panel component - - `contextConfig` _(optional)_: Override the default CodeEditor context values - -**Returns:** - -- `container`: The rendered container element from `@testing-library/react` -- `panel`: Panel test utilities object with methods for interacting with panel elements - -**Example:** - -```tsx -import { renderPanel, PanelSelectors } from '@leafygreen-ui/code-editor'; - -test('renders panel with format button', async () => { - const { container, panel } = renderPanel({ - panelProps: { - title: 'JavaScript Editor', - showFormatButton: true, - showCopyButton: true, - showSecondaryMenuButton: true, - }, - }); - - // Use utilities to interact with the panel - await panel.interactions.clickFormatButton(); - await panel.interactions.clickUndoMenuItem(); -}); -``` -#### Panel Test Utilities Object + const utils = getTestUtils('lg-my-editor'); -The `panel` object provides access to Panel elements and interactions: + // Test editor presence and basic structure + expect(utils.getEditor()).toBeInTheDocument(); + expect(utils.getContentContainer()).toBeInTheDocument(); -##### Element Getters + // Test panel functionality + const panelUtils = utils.getPanelUtils(); + expect(panelUtils.getPanelElement()).toBeInTheDocument(); + expect(panelUtils.getFormatButton()).toBeInTheDocument(); + expect(panelUtils.getPanelCopyButton()).toBeInTheDocument(); + expect(panelUtils.getSecondaryMenuButton()).toBeInTheDocument(); -- `panel.getFormatButton()` - Gets the format button element -- `panel.getCopyButton()` - Gets the copy button element -- `panel.getSecondaryMenuButton()` - Gets the secondary menu button -- `panel.getMenuItem(ariaLabel)` - Gets a menu item by aria-label -- `panel.getUndoMenuItem()` - Gets the undo menu item -- `panel.getRedoMenuItem()` - Gets the redo menu item -- `panel.getDownloadMenuItem()` - Gets the download menu item -- `panel.getViewShortcutsMenuItem()` - Gets the view shortcuts menu item -- `panel.getCustomSecondaryButton(labelOrAriaLabel)` - Gets a custom secondary button + // Test loading state + expect(typeof utils.isLoading()).toBe('boolean'); -##### Interactions - -All interaction methods are available under `panel.interactions`: - -- `clickFormatButton()` - Clicks the format button -- `clickCopyButton()` - Clicks the copy button -- `openSecondaryMenu()` - Opens the secondary menu -- `clickMenuItem(ariaLabel)` - Clicks a menu item by aria-label -- `clickUndoMenuItem()` - Opens menu and clicks undo -- `clickRedoMenuItem()` - Opens menu and clicks redo -- `clickDownloadMenuItem()` - Opens menu and clicks download -- `clickViewShortcutsMenuItem()` - Opens menu and clicks view shortcuts -- `clickCustomSecondaryButton(labelOrAriaLabel)` - Opens menu and clicks custom button -- `hoverFormatButton()` - Hovers format button and waits for tooltip -- `hoverCopyButton()` - Hovers copy button and waits for tooltip - -##### Utilities - -- `panel.waitForTooltip(text, timeout?)` - Waits for a tooltip with specific text -- `panel.hasTitleText(expectedTitle)` - Checks if panel has expected title -- `panel.hasInnerContent(testId)` - Checks if inner content with testId exists - -#### PanelSelectors - -Consistent selector constants for testing: - -```tsx -import { PanelSelectors } from '@leafygreen-ui/code-editor'; - -// Use instead of hardcoded strings -panel.getMenuItem(PanelSelectors.UndoMenuItem); -panel.getMenuItem(PanelSelectors.RedoMenuItem); -panel.getMenuItem(PanelSelectors.DownloadMenuItem); -panel.getMenuItem(PanelSelectors.ViewShortcutsMenuItem); -``` - -Available selectors: - -- `PanelSelectors.FormatButton` -- `PanelSelectors.CopyButton` -- `PanelSelectors.SecondaryMenuButton` -- `PanelSelectors.UndoMenuItem` -- `PanelSelectors.RedoMenuItem` -- `PanelSelectors.DownloadMenuItem` -- `PanelSelectors.ViewShortcutsMenuItem` - -#### Panel Test Examples - -##### Testing Format Button - -```tsx -test('formats code when format button is clicked', async () => { - const onFormatClick = jest.fn(); - const mockFormatCode = jest.fn(); - - const { panel } = renderPanel({ - panelProps: { - showFormatButton: true, - onFormatClick, - }, - contextConfig: { - formatCode: mockFormatCode, - }, - }); - - await panel.interactions.clickFormatButton(); - - expect(onFormatClick).toHaveBeenCalled(); - expect(mockFormatCode).toHaveBeenCalled(); -}); -``` - -##### Testing Secondary Menu - -```tsx -test('performs undo when undo menu item is clicked', async () => { - const onUndoClick = jest.fn(); - const mockUndo = jest.fn(() => true); - - const { panel } = renderPanel({ - panelProps: { - showSecondaryMenuButton: true, - onUndoClick, - }, - contextConfig: { - undo: mockUndo, - }, - }); - - await panel.interactions.clickUndoMenuItem(); - - expect(onUndoClick).toHaveBeenCalled(); - expect(mockUndo).toHaveBeenCalled(); -}); -``` - -##### Testing Custom Secondary Buttons - -```tsx -test('handles custom secondary button clicks', async () => { - const customOnClick = jest.fn(); - - const { panel } = renderPanel({ - panelProps: { - showSecondaryMenuButton: true, - customSecondaryButtons: [ - { - label: 'Custom Action', - onClick: customOnClick, - 'aria-label': 'Perform custom action', - }, - ], - }, - }); - - await panel.interactions.clickCustomSecondaryButton('Perform custom action'); - - expect(customOnClick).toHaveBeenCalled(); -}); -``` - -#### defaultPanelContextFunctions - -The `defaultPanelContextFunctions` object provides access to the default stub functions used in the CodeEditor context. These can be overridden in tests by providing `contextConfig` to `renderPanel`: - -```tsx -import { - renderPanel, - defaultPanelContextFunctions, -} from '@leafygreen-ui/code-editor'; - -// Override default functions with jest mocks in your test files -const mockUndo = jest.fn(() => true); -const mockFormatCode = jest.fn(); - -const { panel } = renderPanel({ - contextConfig: { - undo: mockUndo, - formatCode: mockFormatCode, - }, + // Wait for loading to complete if needed + await utils.waitForLoadingToComplete(); }); - -// Now you can assert on your custom mocks -expect(mockUndo).toHaveBeenCalled(); -expect(mockFormatCode).toHaveBeenCalled(); ``` -Available default functions: - -- `defaultPanelContextFunctions.getContents` -- `defaultPanelContextFunctions.formatCode` -- `defaultPanelContextFunctions.undo` -- `defaultPanelContextFunctions.redo` - ## CodeMirror Extension Hooks The `CodeEditor` component is built on [CodeMirror v6](https://codemirror.net/) and provides a complete, ready-to-use editor experience. However, some applications may need highly customized CodeMirror implementations that don't fit the standard `CodeEditor` API, while still wanting to maintain consistency with the LeafyGreen design system. diff --git a/packages/code-editor/package.json b/packages/code-editor/package.json index cfceafde84..e6235c7e5c 100644 --- a/packages/code-editor/package.json +++ b/packages/code-editor/package.json @@ -61,6 +61,7 @@ "@leafygreen-ui/tooltip": "workspace:^", "@leafygreen-ui/typography": "^22.1.0", "@lezer/highlight": "^1.2.1", + "@lg-tools/test-harnesses": "^0.3.4", "@replit/codemirror-lang-csharp": "^6.2.0", "@uiw/codemirror-extensions-hyper-link": "^4.23.12", "@wasm-fmt/clang-format": "^20.1.7", diff --git a/packages/code-editor/src/CodeEditor/CodeEditor.spec.tsx b/packages/code-editor/src/CodeEditor/CodeEditor.spec.tsx index 935991d918..b7419cc1e0 100644 --- a/packages/code-editor/src/CodeEditor/CodeEditor.spec.tsx +++ b/packages/code-editor/src/CodeEditor/CodeEditor.spec.tsx @@ -3,12 +3,14 @@ import { EditorState } from '@codemirror/state'; import { act, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { renderCodeEditor } from '../testing/testUtils'; +import { getTestUtils } from '../testing'; import { LanguageName } from './hooks/extensions/useLanguageExtension'; +import { renderCodeEditor } from './CodeEditor.testUtils'; import { CopyButtonAppearance } from './CodeEditor.types'; import { CodeEditorSelectors } from '.'; +// Enhanced MutationObserver mock for CodeMirror compatibility global.MutationObserver = jest.fn().mockImplementation(() => ({ observe: jest.fn(), unobserve: jest.fn(), @@ -16,6 +18,57 @@ global.MutationObserver = jest.fn().mockImplementation(() => ({ takeRecords: jest.fn().mockReturnValue([]), })); +// Mock ResizeObserver which is used by CodeMirror +global.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), +})); + +// Mock IntersectionObserver which may be used by CodeMirror +global.IntersectionObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + root: null, + rootMargin: '', + thresholds: [], +})); + +// Mock document.getSelection for CodeMirror +if (!global.document.getSelection) { + global.document.getSelection = jest.fn().mockReturnValue({ + rangeCount: 0, + getRangeAt: jest.fn(), + removeAllRanges: jest.fn(), + addRange: jest.fn(), + toString: jest.fn().mockReturnValue(''), + }); +} + +// Mock createRange for CodeMirror +if (!global.document.createRange) { + global.document.createRange = jest.fn().mockReturnValue({ + setStart: jest.fn(), + setEnd: jest.fn(), + collapse: jest.fn(), + selectNodeContents: jest.fn(), + insertNode: jest.fn(), + surroundContents: jest.fn(), + cloneRange: jest.fn(), + detach: jest.fn(), + getClientRects: jest.fn().mockReturnValue([]), + getBoundingClientRect: jest.fn().mockReturnValue({ + top: 0, + left: 0, + bottom: 0, + right: 0, + width: 0, + height: 0, + }), + }); +} + // Mock console methods to suppress expected warnings const originalConsoleWarn = console.warn; const originalConsoleError = console.error; @@ -318,27 +371,25 @@ describe('packages/code-editor', () => { }); test('renders copy button when copyButtonAppearance is "hover"', async () => { - const { container, editor } = renderCodeEditor({ + const lgId = 'lg-test-copy-hover'; + const { editor } = renderCodeEditor({ copyButtonAppearance: CopyButtonAppearance.Hover, + 'data-lgid': lgId, }); - await editor.waitForEditorView(); - - expect( - container.querySelector(CodeEditorSelectors.CopyButton), - ).toBeInTheDocument(); + const utils = getTestUtils(lgId); + expect(utils.getCopyButton()).toBeInTheDocument(); }); test('renders copy button when copyButtonAppearance is "persist"', async () => { - const { container, editor } = renderCodeEditor({ + const lgId = 'lg-test-copy-persist'; + const { editor } = renderCodeEditor({ copyButtonAppearance: CopyButtonAppearance.Persist, + 'data-lgid': lgId, }); - await editor.waitForEditorView(); - - expect( - container.querySelector(CodeEditorSelectors.CopyButton), - ).toBeInTheDocument(); + const utils = getTestUtils(lgId); + expect(utils.getCopyButton()).toBeInTheDocument(); }); test('does not render copy button when copyButtonAppearance is "none"', async () => { diff --git a/packages/code-editor/src/testing/testUtils.tsx b/packages/code-editor/src/CodeEditor/CodeEditor.testUtils.tsx similarity index 77% rename from packages/code-editor/src/testing/testUtils.tsx rename to packages/code-editor/src/CodeEditor/CodeEditor.testUtils.tsx index 29d0586b13..57de6372f1 100644 --- a/packages/code-editor/src/testing/testUtils.tsx +++ b/packages/code-editor/src/CodeEditor/CodeEditor.testUtils.tsx @@ -8,7 +8,7 @@ import { CodeEditorProps, CodeEditorSelectors, CodeMirrorView, -} from '..'; +} from '.'; let editorViewInstance: CodeMirrorView | null = null; let getEditorViewFn: (() => CodeMirrorView | null) | null = null; @@ -96,43 +96,6 @@ function getBySelector( return elements[0]; } -/** - * Returns all elements matching a specific CodeEditor selector - * @param selector - The CSS selector to look for in the editor - * @param options - Optional filtering options - * @param options.text - Optional text content filter - * @returns All DOM elements matching the selector and optional text filter - * @throws Error if no elements are found - */ -function getAllBySelector( - selector: CodeEditorSelectors, - options?: { text?: string }, -): Array { - const view = ensureEditorView(); - const elements = view.dom.querySelectorAll(selector); - - if (!elements || elements.length === 0) { - throw new Error(`No elements with selector "${selector}" found`); - } - - // If text filter is provided, return only elements containing the text - if (options?.text) { - const matchingElements = Array.from(elements).filter(element => - element.textContent?.includes(options.text as string), - ); - - if (!matchingElements || matchingElements.length === 0) { - throw new Error( - `No elements with selector "${selector}" and text "${options.text}" found`, - ); - } - - return matchingElements; - } - - return Array.from(elements); -} - /** * Returns the first element matching a specific CodeEditor selector or null if not found * @param selector - The CSS selector to look for in the editor @@ -174,40 +137,6 @@ function queryBySelector( return elements[0]; } -/** - * Returns all elements matching a specific CodeEditor selector or null if none are found - * @param selector - The CSS selector to look for in the editor - * @param options - Optional filtering options - * @param options.text - Optional text content filter - * @returns All DOM elements matching the selector and optional text filter, or null if none found - */ -function queryAllBySelector( - selector: CodeEditorSelectors, - options?: { text?: string }, -): Array | null { - const view = ensureEditorView(); - const elements = view.dom.querySelectorAll(selector); - - if (!elements || elements.length === 0) { - return null; - } - - // If text filter is provided, return only elements containing the text - if (options?.text) { - const matchingElements = Array.from(elements).filter(element => - element.textContent?.includes(options.text as string), - ); - - if (!matchingElements || matchingElements.length === 0) { - return null; - } - - return matchingElements; - } - - return Array.from(elements); -} - /** * Checks if the editor is in read-only mode * @returns Boolean indicating whether the editor is in read-only mode @@ -317,9 +246,7 @@ function redo(): boolean { export const editor = { getBySelector, - getAllBySelector, queryBySelector, - queryAllBySelector, isLineWrappingEnabled, isReadOnly, getIndentUnit, diff --git a/packages/code-editor/src/CodeEditor/CodeEditor.tsx b/packages/code-editor/src/CodeEditor/CodeEditor.tsx index 60140e63b3..58b3f91eab 100644 --- a/packages/code-editor/src/CodeEditor/CodeEditor.tsx +++ b/packages/code-editor/src/CodeEditor/CodeEditor.tsx @@ -13,6 +13,7 @@ import { Body } from '@leafygreen-ui/typography'; import { CodeEditorCopyButton } from '../CodeEditorCopyButton'; import { CopyButtonVariant } from '../CodeEditorCopyButton/CodeEditorCopyButton.types'; +import { getLgIds } from '../utils'; import { useFormattingModuleLoaders } from './hooks/formatting/useFormattingModuleLoaders'; import { @@ -25,7 +26,6 @@ import { CodeEditorHandle, type CodeEditorProps, CopyButtonAppearance, - CopyButtonLgId, type HTMLElementWithCodeMirror, } from './CodeEditor.types'; import { CodeEditorProvider } from './CodeEditorContext'; @@ -42,7 +42,8 @@ export const CodeEditor = forwardRef( const { baseFontSize: baseFontSizeProp, className, - copyButtonAppearance, + copyButtonAppearance = CopyButtonAppearance.Hover, + 'data-lgid': dataLgId, darkMode: darkModeProp, defaultValue, enableClickableUrls, @@ -70,6 +71,8 @@ export const CodeEditor = forwardRef( ...rest } = props; + const lgIds = getLgIds(dataLgId); + const { theme } = useDarkMode(darkModeProp); const [controlledValue, setControlledValue] = useState(value || ''); const isControlled = value !== undefined; @@ -343,6 +346,7 @@ export const CodeEditor = forwardRef( undo: handleUndo, redo: handleRedo, downloadContent: handleDownloadContent, + lgIds, }; return ( @@ -358,6 +362,7 @@ export const CodeEditor = forwardRef( className, copyButtonAppearance, })} + data-lgid={lgIds.root} {...rest} > {panel && ( @@ -371,7 +376,7 @@ export const CodeEditor = forwardRef( className={getCopyButtonStyles(copyButtonAppearance)} variant={CopyButtonVariant.Button} disabled={isLoadingProp || isLoadingCoreModules} - data-lgid={CopyButtonLgId} + data-lgid={lgIds.copyButton} /> )} {(isLoadingProp || @@ -387,6 +392,7 @@ export const CodeEditor = forwardRef( minHeight, maxHeight, })} + data-lgid={lgIds.loader} > Loading code editor... diff --git a/packages/code-editor/src/CodeEditor/CodeEditor.types.ts b/packages/code-editor/src/CodeEditor/CodeEditor.types.ts index 16b786804b..f08dccdde1 100644 --- a/packages/code-editor/src/CodeEditor/CodeEditor.types.ts +++ b/packages/code-editor/src/CodeEditor/CodeEditor.types.ts @@ -2,7 +2,7 @@ import { type ReactNode } from 'react'; import { type EditorState, type Extension } from '@codemirror/state'; import { type EditorView } from '@codemirror/view'; -import { type DarkModeProps } from '@leafygreen-ui/lib'; +import { type DarkModeProps, type LgIdProps } from '@leafygreen-ui/lib'; import { type LanguageName } from './hooks/extensions/useLanguageExtension'; @@ -120,161 +120,162 @@ export interface CodeEditorTooltip { severity?: CodeEditorTooltipSeverity; } -export type CodeEditorProps = DarkModeProps & { - /** - * Font size of text in the editor. - * - * @default 14 - */ - baseFontSize?: 14 | 16; - - /** - * Styling prop - */ - className?: string; - - /** - * Determines the appearance of the copy button without a panel. The copy button allows the code block to be copied to the user's clipboard by clicking the button. - * - * If `hover`, the copy button will only appear when the user hovers over the code block. On mobile devices, the copy button will always be visible. - * - * If `persist`, the copy button will always be visible. - * - * If `none`, the copy button will not be rendered. - * - * Note: 'panel' cannot be used with `copyButtonAppearance`. Either use `copyButtonAppearance` or `panel`, not both. - * - * @default `hover` - */ - copyButtonAppearance?: CopyButtonAppearance; - - /** - * Initial value to render in the editor. - */ - defaultValue?: string; - - /** - * Renders URLs as clickable links in the editor. - */ - enableClickableUrls?: boolean; - - /** - * Enables code folding arrows in the gutter. - */ - enableCodeFolding?: boolean; - - /** - * Enables line numbers in the editor’s gutter. - */ - enableLineNumbers?: boolean; - - /** - * Enables line wrapping when the text exceeds the editor’s width. - */ - enableLineWrapping?: boolean; - - /** - * Additional CodeMirror extensions to apply to the editor. These will be applied - * with high precendence, meaning they can override extensions applied through - * built in props. - * @see {@link https://codemirror.net/docs/ref/#state.Extension} - */ - extensions?: Array; - - /** - * Forces parsing of complete document, even if it is not visible. USE WITH CAUTION! - * By default the editor only parses the visible part of the code on the - * screen. This significantly increases performance when there is a lot of - * code rendered. When enabled, this forces parsing of non-visible code. - * This should only be used in exceptional cases. - */ - forceParsing?: boolean; - - /** - * Sets the editor's height. If not set, the editor will automatically adjust - * its height based on the content. - */ - height?: string; - - /** - * Sets the editor's indent size on tab click. - */ - indentSize?: number; - - /** - * Sets the editor's indent unit on tab click. - */ - indentUnit?: IndentUnits; - - /** - * Renders the editor in a loading state. - * - * @remarks - * The CodeEditor is an asynchronous component that relies on lazy loading of - * modules. Due to this, regardless of the `isLoading` prop, the editor will - * always render a loading state until all required modules are loaded. - * This is to ensure that the editor is fully functional before it is displayed. - */ - isLoading?: boolean; - - /** - * Language to use for syntax highlighting. Will have no highlighting if not set. - */ - language?: LanguageName; - - /** - * Sets the editor's max height. - */ - maxHeight?: string; - - /** - * Sets the editor's max width. - */ - maxWidth?: string; - - /** - * Sets the editor's minimum height. - */ - minHeight?: string; - - /** - * Sets the editor's minimum width. - */ - minWidth?: string; - - /** - * Callback that receives the updated editor value when changes are made. - */ - onChange?: (value: string) => void; - - /** - * Value to display in the editor when it is empty. - */ - placeholder?: HTMLElement | string; - - /** - * Enables read only mode, making the contents uneditable. - */ - readOnly?: boolean; - - /** - * Add tooltips to the editor content. - */ - tooltips?: Array; - - /** - * Controlled value of the editor. If set, the editor will not be editable - * and will not update its value on change. Use `onChange` to update the - * value externally. - */ - value?: string; - - /** - * Sets the editor's width. If not set, the editor will be 100% width of its - * parent container. - */ - width?: string; -} & ( +export type CodeEditorProps = DarkModeProps & + LgIdProps & { + /** + * Font size of text in the editor. + * + * @default 14 + */ + baseFontSize?: 14 | 16; + + /** + * Styling prop + */ + className?: string; + + /** + * Determines the appearance of the copy button without a panel. The copy button allows the code block to be copied to the user's clipboard by clicking the button. + * + * If `hover`, the copy button will only appear when the user hovers over the code block. On mobile devices, the copy button will always be visible. + * + * If `persist`, the copy button will always be visible. + * + * If `none`, the copy button will not be rendered. + * + * Note: 'panel' cannot be used with `copyButtonAppearance`. Either use `copyButtonAppearance` or `panel`, not both. + * + * @default `hover` + */ + copyButtonAppearance?: CopyButtonAppearance; + + /** + * Initial value to render in the editor. + */ + defaultValue?: string; + + /** + * Renders URLs as clickable links in the editor. + */ + enableClickableUrls?: boolean; + + /** + * Enables code folding arrows in the gutter. + */ + enableCodeFolding?: boolean; + + /** + * Enables line numbers in the editor’s gutter. + */ + enableLineNumbers?: boolean; + + /** + * Enables line wrapping when the text exceeds the editor’s width. + */ + enableLineWrapping?: boolean; + + /** + * Additional CodeMirror extensions to apply to the editor. These will be applied + * with high precendence, meaning they can override extensions applied through + * built in props. + * @see {@link https://codemirror.net/docs/ref/#state.Extension} + */ + extensions?: Array; + + /** + * Forces parsing of complete document, even if it is not visible. USE WITH CAUTION! + * By default the editor only parses the visible part of the code on the + * screen. This significantly increases performance when there is a lot of + * code rendered. When enabled, this forces parsing of non-visible code. + * This should only be used in exceptional cases. + */ + forceParsing?: boolean; + + /** + * Sets the editor's height. If not set, the editor will automatically adjust + * its height based on the content. + */ + height?: string; + + /** + * Sets the editor's indent size on tab click. + */ + indentSize?: number; + + /** + * Sets the editor's indent unit on tab click. + */ + indentUnit?: IndentUnits; + + /** + * Renders the editor in a loading state. + * + * @remarks + * The CodeEditor is an asynchronous component that relies on lazy loading of + * modules. Due to this, regardless of the `isLoading` prop, the editor will + * always render a loading state until all required modules are loaded. + * This is to ensure that the editor is fully functional before it is displayed. + */ + isLoading?: boolean; + + /** + * Language to use for syntax highlighting. Will have no highlighting if not set. + */ + language?: LanguageName; + + /** + * Sets the editor's max height. + */ + maxHeight?: string; + + /** + * Sets the editor's max width. + */ + maxWidth?: string; + + /** + * Sets the editor's minimum height. + */ + minHeight?: string; + + /** + * Sets the editor's minimum width. + */ + minWidth?: string; + + /** + * Callback that receives the updated editor value when changes are made. + */ + onChange?: (value: string) => void; + + /** + * Value to display in the editor when it is empty. + */ + placeholder?: HTMLElement | string; + + /** + * Enables read only mode, making the contents uneditable. + */ + readOnly?: boolean; + + /** + * Add tooltips to the editor content. + */ + tooltips?: Array; + + /** + * Controlled value of the editor. If set, the editor will not be editable + * and will not update its value on change. Use `onChange` to update the + * value externally. + */ + value?: string; + + /** + * Sets the editor's width. If not set, the editor will be 100% width of its + * parent container. + */ + width?: string; + } & ( | { /** * Determines the appearance of the copy button without a panel. The copy button allows the code block to be copied to the user's clipboard by clicking the button. diff --git a/packages/code-editor/src/CodeEditor/CodeEditorContext.tsx b/packages/code-editor/src/CodeEditor/CodeEditorContext.tsx index d2f07ffc6e..38797a1bdf 100644 --- a/packages/code-editor/src/CodeEditor/CodeEditorContext.tsx +++ b/packages/code-editor/src/CodeEditor/CodeEditorContext.tsx @@ -1,5 +1,7 @@ import React, { createContext, useContext } from 'react'; +import { getLgIds, type GetLgIdsReturnType } from '../utils/getLgIds'; + import { type LanguageName } from './hooks/extensions/useLanguageExtension'; /** @@ -45,6 +47,12 @@ export interface CodeEditorContextValue { * @param filename - Optional custom filename (without extension). Defaults to 'code' */ downloadContent: (filename?: string) => void; + + /** + * Generated lgIds from the CodeEditor component, enabling child components + * to inherit custom data-lgid prefixes passed to the parent CodeEditor. + */ + lgIds: GetLgIdsReturnType; } // Default context value for when Panel is used standalone @@ -58,6 +66,7 @@ const defaultContextValue: CodeEditorContextValue = { downloadContent: () => { console.warn('downloadContent is not available - editor context not found'); }, + lgIds: getLgIds(), // Use default lgIds when used standalone }; const CodeEditorContext = diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useAutoCompleteExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useAutoCompleteExtension.spec.ts index 685703d82f..48132eb14b 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useAutoCompleteExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useAutoCompleteExtension.spec.ts @@ -3,7 +3,7 @@ import { renderHook } from '@testing-library/react'; import { createMockAutoCompleteModule, createMockStateModule, -} from '../../../testing'; +} from '../hooks.testUtils'; import { useAutoCompleteExtension } from './useAutoCompleteExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useCodeFoldingExtension.spec.tsx b/packages/code-editor/src/CodeEditor/hooks/extensions/useCodeFoldingExtension.spec.tsx index 5ba557ee97..5e22ea0b5a 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useCodeFoldingExtension.spec.tsx +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useCodeFoldingExtension.spec.tsx @@ -3,7 +3,7 @@ import { renderHook } from '@testing-library/react'; import { createMockLanguageModule, createMockStateModule, -} from '../../../testing'; +} from '../hooks.testUtils'; import { useCodeFoldingExtension } from './useCodeFoldingExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useExtension.spec.ts index 5508cf7d6a..b53a671f36 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useExtension.spec.ts @@ -1,6 +1,6 @@ import { renderHook, waitFor } from '@testing-library/react'; -import { createMockExtension, createMockStateModule } from '../../../testing'; +import { createMockExtension, createMockStateModule } from '../hooks.testUtils'; import { useExtension } from './useExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useExtensions.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useExtensions.spec.ts index 6fb42b801a..3df62c2681 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useExtensions.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useExtensions.spec.ts @@ -1,6 +1,6 @@ import { renderHook } from '@testing-library/react'; -import { createComprehensiveFakeModules } from '../../../testing'; +import { createComprehensiveFakeModules } from '../hooks.testUtils'; import { useExtensions } from './useExtensions'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useHighlightExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useHighlightExtension.spec.ts index 671bd76309..3a4907e3de 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useHighlightExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useHighlightExtension.spec.ts @@ -4,7 +4,7 @@ import { createMockLanguageModule, createMockLezerHighlightModule, createMockStateModule, -} from '../../../testing'; +} from '../hooks.testUtils'; import { useHighlightExtension } from './useHighlightExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useHyperLinkExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useHyperLinkExtension.spec.ts index 808cd652ec..df5589b27f 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useHyperLinkExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useHyperLinkExtension.spec.ts @@ -3,7 +3,7 @@ import { renderHook } from '@testing-library/react'; import { createMockHyperLinkModule, createMockStateModule, -} from '../../../testing'; +} from '../hooks.testUtils'; import { useHyperLinkExtension } from './useHyperLinkExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useIndentExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useIndentExtension.spec.ts index 540c615e61..4aa5307a8e 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useIndentExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useIndentExtension.spec.ts @@ -1,10 +1,10 @@ import { renderHook } from '@testing-library/react'; +import { IndentUnits } from '../../CodeEditor.types'; import { createMockLanguageModule, createMockStateModule, -} from '../../../testing'; -import { IndentUnits } from '../../CodeEditor.types'; +} from '../hooks.testUtils'; import { useIndentExtension } from './useIndentExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useLanguageExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useLanguageExtension.spec.ts index 1d630e0849..f852914749 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useLanguageExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useLanguageExtension.spec.ts @@ -1,6 +1,6 @@ import { renderHook } from '@testing-library/react'; -import { createMockStateModule } from '../../../testing'; +import { createMockStateModule } from '../hooks.testUtils'; import { LanguageName, useLanguageExtension } from './useLanguageExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useLineNumbersExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useLineNumbersExtension.spec.ts index ed0f9c5cca..7bc7c2d8a8 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useLineNumbersExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useLineNumbersExtension.spec.ts @@ -1,6 +1,9 @@ import { renderHook } from '@testing-library/react'; -import { createMockStateModule, createMockViewModule } from '../../../testing'; +import { + createMockStateModule, + createMockViewModule, +} from '../hooks.testUtils'; import { useLineNumbersExtension } from './useLineNumbersExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useLineWrapExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useLineWrapExtension.spec.ts index bf94974d60..ad996b9dfb 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useLineWrapExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useLineWrapExtension.spec.ts @@ -1,6 +1,9 @@ import { renderHook } from '@testing-library/react'; -import { createMockStateModule, createMockViewModule } from '../../../testing'; +import { + createMockStateModule, + createMockViewModule, +} from '../hooks.testUtils'; import { useLineWrapExtension } from './useLineWrapExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/usePlaceholderExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/usePlaceholderExtension.spec.ts index c2ba6b74d7..8049902a29 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/usePlaceholderExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/usePlaceholderExtension.spec.ts @@ -1,6 +1,9 @@ import { renderHook } from '@testing-library/react'; -import { createMockStateModule, createMockViewModule } from '../../../testing'; +import { + createMockStateModule, + createMockViewModule, +} from '../hooks.testUtils'; import { usePlaceholderExtension } from './usePlaceholderExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useReadOnlyExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useReadOnlyExtension.spec.ts index d273252220..66fc3d405f 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useReadOnlyExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useReadOnlyExtension.spec.ts @@ -1,6 +1,6 @@ import { renderHook } from '@testing-library/react'; -import { createMockStateModule } from '../../../testing'; +import { createMockStateModule } from '../hooks.testUtils'; import { useReadOnlyExtension } from './useReadOnlyExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useThemeExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useThemeExtension.spec.ts index 27d6c74d61..e861f3f351 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useThemeExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useThemeExtension.spec.ts @@ -1,6 +1,9 @@ import { renderHook } from '@testing-library/react'; -import { createMockStateModule, createMockViewModule } from '../../../testing'; +import { + createMockStateModule, + createMockViewModule, +} from '../hooks.testUtils'; import { useThemeExtension } from './useThemeExtension'; diff --git a/packages/code-editor/src/CodeEditor/hooks/extensions/useTooltipExtension.spec.ts b/packages/code-editor/src/CodeEditor/hooks/extensions/useTooltipExtension.spec.ts index 4237d35759..5d047e5a3a 100644 --- a/packages/code-editor/src/CodeEditor/hooks/extensions/useTooltipExtension.spec.ts +++ b/packages/code-editor/src/CodeEditor/hooks/extensions/useTooltipExtension.spec.ts @@ -1,6 +1,9 @@ import { renderHook } from '@testing-library/react'; -import { createMockLintModule, createMockStateModule } from '../../../testing'; +import { + createMockLintModule, + createMockStateModule, +} from '../hooks.testUtils'; import { useTooltipExtension } from './useTooltipExtension'; diff --git a/packages/code-editor/src/testing/extensionTestUtils.ts b/packages/code-editor/src/CodeEditor/hooks/hooks.testUtils.ts similarity index 100% rename from packages/code-editor/src/testing/extensionTestUtils.ts rename to packages/code-editor/src/CodeEditor/hooks/hooks.testUtils.ts diff --git a/packages/code-editor/src/Panel/Panel.spec.tsx b/packages/code-editor/src/Panel/Panel.spec.tsx index cbe2b65bb3..a92e0c2c36 100644 --- a/packages/code-editor/src/Panel/Panel.spec.tsx +++ b/packages/code-editor/src/Panel/Panel.spec.tsx @@ -2,7 +2,8 @@ import React from 'react'; import '@testing-library/jest-dom'; -import { renderPanel } from '../testing/panelTestUtils'; +import { renderPanel } from './Panel.testUtils'; +import { PanelProps } from './Panel.types'; // Mock Modal component to avoid HTMLDialogElement issues jest.mock('@leafygreen-ui/modal', () => { @@ -15,8 +16,6 @@ jest.mock('@leafygreen-ui/modal', () => { }; }); -import { PanelProps } from './Panel.types'; - const TestIcon = () =>
; TestIcon.displayName = 'TestIcon'; diff --git a/packages/code-editor/src/testing/panelTestUtils.tsx b/packages/code-editor/src/Panel/Panel.testUtils.tsx similarity index 98% rename from packages/code-editor/src/testing/panelTestUtils.tsx rename to packages/code-editor/src/Panel/Panel.testUtils.tsx index 4441e4aa21..f2ab65fd14 100644 --- a/packages/code-editor/src/testing/panelTestUtils.tsx +++ b/packages/code-editor/src/Panel/Panel.testUtils.tsx @@ -5,8 +5,10 @@ import userEvent from '@testing-library/user-event'; import LeafyGreenProvider from '@leafygreen-ui/leafygreen-provider'; import { CodeEditorProvider } from '../CodeEditor/CodeEditorContext'; -import { Panel } from '../Panel/Panel'; -import { PanelProps } from '../Panel/Panel.types'; +import { getLgIds, type GetLgIdsReturnType } from '../utils/getLgIds'; + +import { Panel } from './Panel'; +import { PanelProps } from './Panel.types'; // Default stub functions for CodeEditor context const defaultStubGetContents = () => 'test content'; @@ -39,6 +41,7 @@ export interface PanelTestContextConfig { undo?: () => boolean; redo?: () => boolean; downloadContent?: () => void; + lgIds?: GetLgIdsReturnType; } /** @@ -64,6 +67,7 @@ export function renderPanel(config: RenderPanelConfig = {}) { undo: defaultStubUndo, redo: defaultStubRedo, downloadContent: defaultStubDownloadContent, + lgIds: getLgIds(), ...contextConfig, }; diff --git a/packages/code-editor/src/Panel/Panel.tsx b/packages/code-editor/src/Panel/Panel.tsx index b2ca9e547b..ac5fc2c5af 100644 --- a/packages/code-editor/src/Panel/Panel.tsx +++ b/packages/code-editor/src/Panel/Panel.tsx @@ -93,7 +93,7 @@ export function Panel({ const { theme } = useDarkMode(darkMode); const baseFontSize = useBaseFontSize(); - const { getContents, formatCode, undo, redo, downloadContent } = + const { getContents, formatCode, undo, redo, downloadContent, lgIds } = useCodeEditorContext(); const handleFormatClick = async () => { @@ -142,12 +142,13 @@ export function Panel({ return ( <> -
+
{title}
@@ -161,6 +162,7 @@ export function Panel({ @@ -174,12 +176,16 @@ export function Panel({ variant={CopyButtonVariant.IconButton} getContentsToCopy={getContents ?? (() => '')} onCopy={onCopyClick} + data-lgid={lgIds.panelCopyButton} /> )} {showSecondaryMenuButton && ( + } @@ -187,6 +193,7 @@ export function Panel({ renderDarkMenu={false} open={menuOpen} setOpen={setMenuOpen} + data-lgid={lgIds.panelSecondaryMenu} > } diff --git a/packages/code-editor/src/testing/getTestUtils.spec.tsx b/packages/code-editor/src/testing/getTestUtils.spec.tsx new file mode 100644 index 0000000000..b000a08a68 --- /dev/null +++ b/packages/code-editor/src/testing/getTestUtils.spec.tsx @@ -0,0 +1,372 @@ +import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { act } from '@leafygreen-ui/testing-lib'; + +import { CodeEditor, CodeEditorProps } from '../CodeEditor'; +import { Panel } from '../Panel'; + +import { getTestUtils } from './getTestUtils'; + +// MutationObserver not supported in JSDOM +global.MutationObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + takeRecords: jest.fn().mockReturnValue([]), +})); + +const DEFAULT_LGID = 'lg-code-editor'; + +const TestComponent = (props: Partial) => { + return ( +
+

Code Editor

+ +
+ ); +}; + +describe('getTestUtils', () => { + beforeAll(() => { + HTMLDialogElement.prototype.show = jest.fn(function mock( + this: HTMLDialogElement, + ) { + this.open = true; + }); + + HTMLDialogElement.prototype.close = jest.fn(function mock( + this: HTMLDialogElement, + ) { + this.open = false; + }); + }); + + test('`getEditor` returns correct element', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const editor = utils.getEditor(); + expect(editor).toBeInTheDocument(); + }); + + test('`findEditor` returns correct element', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const editor = await utils.findEditor(); + expect(editor).toBeInTheDocument(); + }); + + test('`queryEditor` returns correct element', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const editor = utils.queryEditor(); + expect(editor).toBeInTheDocument(); + }); + + test('`queryEditor` returns `null` when editor is not present', async () => { + render(
); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const editor = utils.queryEditor(); + expect(editor).toBeNull(); + }); + + test('`getCopyButton` returns correct element when appearance is "hover"', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const copyButton = utils.getCopyButton(); + expect(copyButton).toBeInTheDocument(); + }); + + test('`queryCopyButton` returns correct element when appearance is "hover"', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const copyButton = utils.queryCopyButton(); + expect(copyButton).toBeInTheDocument(); + }); + + test('`findCopyButton` returns correct element when appearance is "hover"', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const copyButton = await utils.findCopyButton(); + expect(copyButton).toBeInTheDocument(); + }); + + test('`getCopyButton` returns correct element when appearance is "persist"', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const copyButton = utils.getCopyButton(); + expect(copyButton).toBeInTheDocument(); + }); + + test('`findCopyButton` returns correct element when appearance is "persist"', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const copyButton = await utils.findCopyButton(); + expect(copyButton).toBeInTheDocument(); + }); + + test('`queryCopyButton` returns correct element when appearance is "persist"', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const copyButton = utils.queryCopyButton(); + expect(copyButton).toBeInTheDocument(); + }); + + test('`queryCopyButton` returns `null` when appearance is "none"', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + await utils.waitForLoadingToComplete(); + const copyButton = utils.queryCopyButton(); + expect(copyButton).toBeNull(); + }); + + describe('getPanelUtils', () => { + test('`getPanelElement` returns correct element when panel is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.getPanelElement()).toBeInTheDocument(); + }); + + test('`findPanelElement` returns correct element when panel is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + const panelElement = await panelUtils.findPanelElement(); + expect(panelElement).toBeInTheDocument(); + }); + + test('`queryPanelElement` returns correct element when panel is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.queryPanelElement()).toBeInTheDocument(); + }); + + test('`queryPanelElement` returns `null` when panel is not present', async () => { + await act(() => { + render(); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.queryPanelElement()).toBeNull(); + }); + + test('`getFormatButton` returns correct element when format button is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.getFormatButton()).toBeInTheDocument(); + }); + + test('`findFormatButton` returns correct element when format button is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + const formatButton = await panelUtils.findFormatButton(); + expect(formatButton).toBeInTheDocument(); + }); + + test('`queryFormatButton` returns correct element when format button is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.queryFormatButton()).toBeInTheDocument(); + }); + + test('`queryFormatButton` returns `null` when format button is not present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.queryFormatButton()).toBeNull(); + }); + + test('`getPanelCopyButton` returns correct element when copy button is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.getPanelCopyButton()).toBeInTheDocument(); + }); + + test('`findPanelCopyButton` returns correct element when copy button is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + const panelCopyButton = await panelUtils.findPanelCopyButton(); + expect(panelCopyButton).toBeInTheDocument(); + }); + + test('`queryPanelCopyButton` returns correct element when copy button is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.queryPanelCopyButton()).toBeInTheDocument(); + }); + + test('`queryPanelCopyButton` returns `null` when copy button is not present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.queryPanelCopyButton()).toBeNull(); + }); + + test('`getSecondaryMenuButton` returns correct element when secondary menu button is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.getSecondaryMenuButton()).toBeInTheDocument(); + }); + + test('`findSecondaryMenuButton` returns correct element when secondary menu button is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + const secondaryMenuButton = await panelUtils.findSecondaryMenuButton(); + expect(secondaryMenuButton).toBeInTheDocument(); + }); + + test('`querySecondaryMenuButton` returns correct element when secondary menu button is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.querySecondaryMenuButton()).toBeInTheDocument(); + }); + + test('`querySecondaryMenuButton` returns `null` when secondary menu button is not present', async () => { + await act(() => { + render( + } />, + ); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.querySecondaryMenuButton()).toBeNull(); + }); + + test('`getSecondaryMenu` returns correct element when secondary menu is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + await userEvent.click(panelUtils.getSecondaryMenuButton() as HTMLElement); + await waitFor(() => { + expect(panelUtils.getSecondaryMenuButton()).toBeInTheDocument(); + }); + }); + + test('`findSecondaryMenu` returns correct element when secondary menu is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + await userEvent.click(panelUtils.getSecondaryMenuButton() as HTMLElement); + await waitFor(() => { + expect(panelUtils.getSecondaryMenu()).toBeInTheDocument(); + }); + }); + + test('`querySecondaryMenu` returns correct element when secondary menu is present', async () => { + await act(() => { + render(} />); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + await userEvent.click(panelUtils.getSecondaryMenuButton() as HTMLElement); + await waitFor(() => { + expect(panelUtils.getSecondaryMenu()).toBeInTheDocument(); + }); + }); + + test('`querySecondaryMenu` returns `null` when secondary menu is not present', async () => { + await act(() => { + render( + } />, + ); + }); + const utils = getTestUtils(DEFAULT_LGID); + const panelUtils = utils.getPanelUtils(); + await utils.waitForLoadingToComplete(); + expect(panelUtils.querySecondaryMenu()).toBeNull(); + }); + }); +}); diff --git a/packages/code-editor/src/testing/getTestUtils.ts b/packages/code-editor/src/testing/getTestUtils.ts new file mode 100644 index 0000000000..3149f16e0a --- /dev/null +++ b/packages/code-editor/src/testing/getTestUtils.ts @@ -0,0 +1,117 @@ +import { findByLgId, getByLgId, queryByLgId } from '@lg-tools/test-harnesses'; + +import { DEFAULT_LGID_ROOT, getLgIds } from '../utils'; + +import { GetTestUtilsReturnType } from './getTestUtils.types'; + +/** + * Returns a set of utility functions to query and get parts of a code editor component for testing. + * @param lgId - The base LeafyGreen ID prefix for the code editor. Defaults to `DEFAULT_LGID_ROOT`. + */ +export const getTestUtils = ( + lgId: `lg-${string}` = DEFAULT_LGID_ROOT, +): GetTestUtilsReturnType => { + const lgIds = getLgIds(lgId); + + /** Editor element utils */ + const getEditor = () => getByLgId!(lgIds.root); + const findEditor = () => findByLgId!(lgIds.root); + const queryEditor = () => queryByLgId!(lgIds.root); + + /** Content element utils */ + const getContentContainer = () => getByLgId!(lgIds.content); + const findContentContainer = () => findByLgId!(lgIds.content); + const queryContentContainer = () => queryByLgId!(lgIds.content); + + /** Copy button utils */ + const getCopyButton = () => getByLgId!(lgIds.copyButton); + const findCopyButton = () => findByLgId!(lgIds.copyButton); + const queryCopyButton = () => queryByLgId!(lgIds.copyButton); + + /** + * Gets panel-specific test utilities if panel is present + */ + const getPanelUtils = () => { + /** Copy button utils */ + const getPanelElement = () => getByLgId!(lgIds.panel); + const findPanelElement = () => findByLgId!(lgIds.panel); + const queryPanelElement = () => queryByLgId!(lgIds.panel); + + /** Format button utils */ + const getFormatButton = () => getByLgId!(lgIds.panelFormatButton); + const findFormatButton = () => findByLgId!(lgIds.panelFormatButton); + const queryFormatButton = () => queryByLgId!(lgIds.panelFormatButton); + + /** Panel copy button utils */ + const getPanelCopyButton = () => getByLgId!(lgIds.panelCopyButton); + const findPanelCopyButton = () => findByLgId!(lgIds.panelCopyButton); + const queryPanelCopyButton = () => queryByLgId!(lgIds.panelCopyButton); + + /** Secondary menu button utils */ + const getSecondaryMenuButton = () => + getByLgId!(lgIds.panelSecondaryMenuButton); + const findSecondaryMenuButton = () => + findByLgId!(lgIds.panelSecondaryMenuButton); + const querySecondaryMenuButton = () => + queryByLgId!(lgIds.panelSecondaryMenuButton); + + /** Secondary menu utils */ + const getSecondaryMenu = () => getByLgId!(lgIds.panelSecondaryMenu); + const findSecondaryMenu = () => findByLgId!(lgIds.panelSecondaryMenu); + const querySecondaryMenu = () => queryByLgId!(lgIds.panelSecondaryMenu); + + return { + getPanelElement, + findPanelElement, + queryPanelElement, + getFormatButton, + findFormatButton, + queryFormatButton, + getPanelCopyButton, + findPanelCopyButton, + queryPanelCopyButton, + getSecondaryMenuButton, + findSecondaryMenuButton, + querySecondaryMenuButton, + getSecondaryMenu, + findSecondaryMenu, + querySecondaryMenu, + }; + }; + + /** + * Waits for any loading states to complete (both user and internal loading) + */ + const waitForLoadingToComplete = async (timeout = 5000): Promise => { + const startTime = Date.now(); + + while (Date.now() - startTime < timeout) { + if (!isLoading()) { + return; + } + await new Promise(resolve => setTimeout(resolve, 50)); + } + + throw new Error(`Loading did not complete within ${timeout}ms`); + }; + + /** + * Checks if the editor is in a loading state + */ + const isLoading = (): boolean => !!queryByLgId!(lgIds.loader); + + return { + getEditor, + findEditor, + queryEditor, + getContentContainer, + findContentContainer, + queryContentContainer, + getCopyButton, + findCopyButton, + queryCopyButton, + getPanelUtils, + waitForLoadingToComplete, + isLoading, + }; +}; diff --git a/packages/code-editor/src/testing/getTestUtils.types.ts b/packages/code-editor/src/testing/getTestUtils.types.ts new file mode 100644 index 0000000000..dfdaee118d --- /dev/null +++ b/packages/code-editor/src/testing/getTestUtils.types.ts @@ -0,0 +1,74 @@ +export interface PanelTestUtilsReturnType { + /** + * Returns the panel element + */ + getPanelElement: () => T | null; + findPanelElement: () => Promise; + queryPanelElement: () => T | null; + + /** + * Returns the format button element + */ + getFormatButton: () => T | null; + findFormatButton: () => Promise; + queryFormatButton: () => T | null; + + /** + * Returns the panel copy button element + */ + getPanelCopyButton: () => T | null; + findPanelCopyButton: () => Promise; + queryPanelCopyButton: () => T | null; + + /** + * Returns the secondary menu button element + */ + getSecondaryMenuButton: () => T | null; + findSecondaryMenuButton: () => Promise; + querySecondaryMenuButton: () => T | null; + + /** + * Returns the secondary menu element + */ + getSecondaryMenu: () => T | null; + findSecondaryMenu: () => Promise; + querySecondaryMenu: () => T | null; +} + +export interface GetTestUtilsReturnType { + /** + * Returns the CodeEditor root element + */ + getEditor: () => T | null; + findEditor: () => Promise; + queryEditor: () => T | null; + + /** + * Returns the content container element + */ + getContentContainer: () => T | null; + findContentContainer: () => Promise; + queryContentContainer: () => T | null; + + /** + * Gets the copy button element (when not using panel) + */ + getCopyButton: () => T | null; + findCopyButton: () => Promise; + queryCopyButton: () => T | null; + + /** + * Gets panel-specific test utilities if panel is present + */ + getPanelUtils: () => PanelTestUtilsReturnType; + + /** + * Waits for any loading states to complete (both user and internal loading) + */ + waitForLoadingToComplete: (timeout?: number) => Promise; + + /** + * Checks if the editor is currently in a loading state + */ + isLoading: () => boolean; +} diff --git a/packages/code-editor/src/testing/index.ts b/packages/code-editor/src/testing/index.ts index 291a2c812b..c7da4bbb24 100644 --- a/packages/code-editor/src/testing/index.ts +++ b/packages/code-editor/src/testing/index.ts @@ -1,31 +1,6 @@ -import { codeSnippets } from './codeSnippets'; -import { - defaultPanelContextFunctions, - PanelSelectors, - renderPanel, -} from './panelTestUtils'; -import { renderCodeEditor } from './testUtils'; - -export { - codeSnippets, - defaultPanelContextFunctions, - PanelSelectors, - renderCodeEditor, - renderPanel, -}; - -// Extension testing utilities -export { - createComprehensiveFakeModules, - createDefaultTestProps, - createLanguageModuleFactory, - createMockAutoCompleteModule, - createMockExtension, - createMockHyperLinkModule, - createMockLanguageModule, - createMockLezerHighlightModule, - createMockLintModule, - createMockStateModule, - createMockViewModule, - FakeCompartment, -} from './extensionTestUtils'; +export { codeSnippets } from './codeSnippets'; +export { getTestUtils } from './getTestUtils'; +export type { + GetTestUtilsReturnType, + PanelTestUtilsReturnType, +} from './getTestUtils.types'; diff --git a/packages/code-editor/src/utils/getLgIds.ts b/packages/code-editor/src/utils/getLgIds.ts new file mode 100644 index 0000000000..4082d29e5d --- /dev/null +++ b/packages/code-editor/src/utils/getLgIds.ts @@ -0,0 +1,22 @@ +import { LgIdString } from '@leafygreen-ui/lib'; + +export const DEFAULT_LGID_ROOT = 'lg-code_editor'; + +export const getLgIds = (root: LgIdString = DEFAULT_LGID_ROOT) => { + const ids = { + root, + editor: `${root}-editor`, + content: `${root}-content`, + panel: `${root}-panel`, + panelTitle: `${root}-panel_title`, + panelFormatButton: `${root}-panel_format_button`, + panelCopyButton: `${root}-panel_copy_button`, + panelSecondaryMenuButton: `${root}-panel_secondary_menu_button`, + panelSecondaryMenu: `${root}-panel_secondary_menu`, + copyButton: `${root}-copy_button`, + loader: `${root}-loader`, + } as const; + return ids; +}; + +export type GetLgIdsReturnType = ReturnType; diff --git a/packages/code-editor/src/utils/index.ts b/packages/code-editor/src/utils/index.ts new file mode 100644 index 0000000000..22d2dd98d7 --- /dev/null +++ b/packages/code-editor/src/utils/index.ts @@ -0,0 +1,5 @@ +export { + DEFAULT_LGID_ROOT, + getLgIds, + type GetLgIdsReturnType, +} from './getLgIds'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50f17525a8..664b2af819 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1399,6 +1399,9 @@ importers: '@lezer/highlight': specifier: ^1.2.1 version: 1.2.1 + '@lg-tools/test-harnesses': + specifier: ^0.3.4 + version: 0.3.4 '@replit/codemirror-lang-csharp': specifier: ^6.2.0 version: 6.2.0(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.38.1)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2) @@ -2118,7 +2121,7 @@ importers: version: 4.16.1 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829) + version: 10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912) xml2json: specifier: ^0.12.0 version: 0.12.0 @@ -4184,10 +4187,10 @@ importers: version: 8.6.12(storybook@8.6.14(prettier@2.8.8)) '@storybook/react': specifier: 8.6.12 - version: 8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250829) + version: 8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250912) '@storybook/react-webpack5': specifier: 8.6.12 - version: 8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(esbuild@0.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250829) + version: 8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(esbuild@0.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250912) '@storybook/test': specifier: 8.6.12 version: 8.6.12(storybook@8.6.14(prettier@2.8.8)) @@ -4196,7 +4199,7 @@ importers: version: 8.6.12(storybook@8.6.14(prettier@2.8.8)) '@svgr/webpack': specifier: 8.0.1 - version: 8.0.1(typescript@6.0.0-dev.20250829) + version: 8.0.1(typescript@6.0.0-dev.20250912) assert: specifier: ^2.1.0 version: 2.1.0 @@ -4238,7 +4241,7 @@ importers: version: 18.3.1 react-docgen-typescript: specifier: 2.2.2 - version: 2.2.2(typescript@6.0.0-dev.20250829) + version: 2.2.2(typescript@6.0.0-dev.20250912) react-dom: specifier: ^17.0.0 || ^18.0.0 version: 18.3.1(react@18.3.1) @@ -4389,7 +4392,7 @@ importers: version: 11.1.1 jest: specifier: 29.6.2 - version: 29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)) + version: 29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)) jest-axe: specifier: 8.0.0 version: 8.0.0 @@ -5865,8 +5868,8 @@ packages: '@lezer/rust@1.0.2': resolution: {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==} - '@lg-tools/test-harnesses@0.3.2': - resolution: {integrity: sha512-SShuDQP8jK9NSK9M3PeBTQ8OoVihrq1/fyrM55B5EABZPjle7tIrMjM5n71YnmDsXAAX5z8PFBpZQHySJmJAgA==} + '@lg-tools/test-harnesses@0.3.4': + resolution: {integrity: sha512-JfJj2LSMe5vTSDQoLxWUHx2r4wUgKqU1UrgqjvNYM7iebXE0JCE7RvLiEg5SnsRO8xXQbEMjgISErmCDR4DS7Q==} '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -11213,8 +11216,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@6.0.0-dev.20250829: - resolution: {integrity: sha512-ZFuTlZD1rPkEXcOotbyJ5BmY08keW79T9tP+Qcp/ex8HtEuQgufUQGnMID4DoVELThNPZWPHGpEuNfL7qQO4Lw==} + typescript@6.0.0-dev.20250912: + resolution: {integrity: sha512-at705jUzCugXNOAVuBDUholnNKpGRs1lsBM0L+k6UVA5C3DQUOGyFSMLnKSXy4rJHdjmSLtMo1oOOXlsfcF6qQ==} engines: {node: '>=14.17'} hasBin: true @@ -13919,7 +13922,7 @@ snapshots: - ts-node optional: true - '@jest/core@29.6.2(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829))': + '@jest/core@29.6.2(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -13933,7 +13936,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)) + jest-config: 29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -13990,7 +13993,7 @@ snapshots: - ts-node optional: true - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829))': + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -14004,7 +14007,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)) + jest-config: 29.7.0(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -14256,7 +14259,7 @@ snapshots: '@leafygreen-ui/lib': 15.2.0(react@18.3.1) '@leafygreen-ui/palette': 5.0.0 '@leafygreen-ui/tokens': 3.2.1(@babel/core@7.28.0)(react@18.3.1) - '@lg-tools/test-harnesses': 0.3.2 + '@lg-tools/test-harnesses': 0.3.4 polished: 4.3.1 transitivePeerDependencies: - '@babel/core' @@ -14378,7 +14381,7 @@ snapshots: '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 - '@lg-tools/test-harnesses@0.3.2': + '@lg-tools/test-harnesses@0.3.4': dependencies: '@testing-library/dom': 9.3.4 @@ -14760,7 +14763,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-webpack5@8.6.12(esbuild@0.25.8)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250829)': + '@storybook/builder-webpack5@8.6.12(esbuild@0.25.8)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250912)': dependencies: '@storybook/core-webpack': 8.6.12(storybook@8.6.14(prettier@2.8.8)) '@types/semver': 7.7.0 @@ -14770,7 +14773,7 @@ snapshots: constants-browserify: 1.0.0 css-loader: 6.11.0(webpack@5.88.0(esbuild@0.25.8)) es-module-lexer: 1.7.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@6.0.0-dev.20250829)(webpack@5.88.0(esbuild@0.25.8)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@6.0.0-dev.20250912)(webpack@5.88.0(esbuild@0.25.8)) html-webpack-plugin: 5.6.3(webpack@5.88.0(esbuild@0.25.8)) magic-string: 0.30.17 path-browserify: 1.0.1 @@ -14788,7 +14791,7 @@ snapshots: webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: - typescript: 6.0.0-dev.20250829 + typescript: 6.0.0-dev.20250912 transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -14872,11 +14875,11 @@ snapshots: dependencies: storybook: 8.6.14(prettier@2.8.8) - '@storybook/preset-react-webpack@8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(esbuild@0.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250829)': + '@storybook/preset-react-webpack@8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(esbuild@0.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250912)': dependencies: '@storybook/core-webpack': 8.6.12(storybook@8.6.14(prettier@2.8.8)) - '@storybook/react': 8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250829) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@6.0.0-dev.20250829)(webpack@5.88.0(esbuild@0.25.8)) + '@storybook/react': 8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250912) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@6.0.0-dev.20250912)(webpack@5.88.0(esbuild@0.25.8)) '@types/semver': 7.7.0 find-up: 5.0.0 magic-string: 0.30.17 @@ -14889,7 +14892,7 @@ snapshots: tsconfig-paths: 4.2.0 webpack: 5.88.0(esbuild@0.25.8) optionalDependencies: - typescript: 6.0.0-dev.20250829 + typescript: 6.0.0-dev.20250912 transitivePeerDependencies: - '@storybook/test' - '@swc/core' @@ -14902,16 +14905,16 @@ snapshots: dependencies: storybook: 8.6.14(prettier@2.8.8) - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@6.0.0-dev.20250829)(webpack@5.88.0(esbuild@0.25.8))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@6.0.0-dev.20250912)(webpack@5.88.0(esbuild@0.25.8))': dependencies: debug: 4.4.1 endent: 2.1.0 find-cache-dir: 3.3.2 flat-cache: 3.2.0 micromatch: 4.0.8 - react-docgen-typescript: 2.2.2(typescript@6.0.0-dev.20250829) + react-docgen-typescript: 2.2.2(typescript@6.0.0-dev.20250912) tslib: 2.8.1 - typescript: 6.0.0-dev.20250829 + typescript: 6.0.0-dev.20250912 webpack: 5.88.0(esbuild@0.25.8) transitivePeerDependencies: - supports-color @@ -14922,16 +14925,16 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.6.14(prettier@2.8.8) - '@storybook/react-webpack5@8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(esbuild@0.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250829)': + '@storybook/react-webpack5@8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(esbuild@0.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250912)': dependencies: - '@storybook/builder-webpack5': 8.6.12(esbuild@0.25.8)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250829) - '@storybook/preset-react-webpack': 8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(esbuild@0.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250829) - '@storybook/react': 8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250829) + '@storybook/builder-webpack5': 8.6.12(esbuild@0.25.8)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250912) + '@storybook/preset-react-webpack': 8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(esbuild@0.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250912) + '@storybook/react': 8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250912) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) storybook: 8.6.14(prettier@2.8.8) optionalDependencies: - typescript: 6.0.0-dev.20250829 + typescript: 6.0.0-dev.20250912 transitivePeerDependencies: - '@rspack/core' - '@storybook/test' @@ -14956,7 +14959,7 @@ snapshots: '@storybook/test': 8.6.12(storybook@8.6.14(prettier@2.8.8)) typescript: 5.8.3 - '@storybook/react@8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250829)': + '@storybook/react@8.6.12(@storybook/test@8.6.12(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@6.0.0-dev.20250912)': dependencies: '@storybook/components': 8.6.12(storybook@8.6.14(prettier@2.8.8)) '@storybook/global': 5.0.0 @@ -14969,7 +14972,7 @@ snapshots: storybook: 8.6.14(prettier@2.8.8) optionalDependencies: '@storybook/test': 8.6.12(storybook@8.6.14(prettier@2.8.8)) - typescript: 6.0.0-dev.20250829 + typescript: 6.0.0-dev.20250912 '@storybook/test@8.5.3(storybook@8.6.14(prettier@2.8.8))': dependencies: @@ -15135,12 +15138,12 @@ snapshots: - supports-color - typescript - '@svgr/core@8.0.0(typescript@6.0.0-dev.20250829)': + '@svgr/core@8.0.0(typescript@6.0.0-dev.20250912)': dependencies: '@babel/core': 7.24.3 '@svgr/babel-preset': 8.0.0(@babel/core@7.24.3) camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@6.0.0-dev.20250829) + cosmiconfig: 8.3.6(typescript@6.0.0-dev.20250912) snake-case: 3.0.4 transitivePeerDependencies: - supports-color @@ -15185,11 +15188,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@svgr/plugin-jsx@8.0.1(@svgr/core@8.0.0(typescript@6.0.0-dev.20250829))': + '@svgr/plugin-jsx@8.0.1(@svgr/core@8.0.0(typescript@6.0.0-dev.20250912))': dependencies: '@babel/core': 7.24.3 '@svgr/babel-preset': 8.0.0(@babel/core@7.24.3) - '@svgr/core': 8.0.0(typescript@6.0.0-dev.20250829) + '@svgr/core': 8.0.0(typescript@6.0.0-dev.20250912) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: @@ -15220,10 +15223,10 @@ snapshots: transitivePeerDependencies: - typescript - '@svgr/plugin-svgo@8.0.1(@svgr/core@8.0.0(typescript@6.0.0-dev.20250829))(typescript@6.0.0-dev.20250829)': + '@svgr/plugin-svgo@8.0.1(@svgr/core@8.0.0(typescript@6.0.0-dev.20250912))(typescript@6.0.0-dev.20250912)': dependencies: - '@svgr/core': 8.0.0(typescript@6.0.0-dev.20250829) - cosmiconfig: 8.3.6(typescript@6.0.0-dev.20250829) + '@svgr/core': 8.0.0(typescript@6.0.0-dev.20250912) + cosmiconfig: 8.3.6(typescript@6.0.0-dev.20250912) deepmerge: 4.3.1 svgo: 3.3.2 transitivePeerDependencies: @@ -15254,16 +15257,16 @@ snapshots: - supports-color - typescript - '@svgr/webpack@8.0.1(typescript@6.0.0-dev.20250829)': + '@svgr/webpack@8.0.1(typescript@6.0.0-dev.20250912)': dependencies: '@babel/core': 7.24.3 '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.24.3) '@babel/preset-env': 7.24.3(@babel/core@7.24.3) '@babel/preset-react': 7.24.1(@babel/core@7.24.3) '@babel/preset-typescript': 7.24.1(@babel/core@7.24.3) - '@svgr/core': 8.0.0(typescript@6.0.0-dev.20250829) - '@svgr/plugin-jsx': 8.0.1(@svgr/core@8.0.0(typescript@6.0.0-dev.20250829)) - '@svgr/plugin-svgo': 8.0.1(@svgr/core@8.0.0(typescript@6.0.0-dev.20250829))(typescript@6.0.0-dev.20250829) + '@svgr/core': 8.0.0(typescript@6.0.0-dev.20250912) + '@svgr/plugin-jsx': 8.0.1(@svgr/core@8.0.0(typescript@6.0.0-dev.20250912)) + '@svgr/plugin-svgo': 8.0.1(@svgr/core@8.0.0(typescript@6.0.0-dev.20250912))(typescript@6.0.0-dev.20250912) transitivePeerDependencies: - supports-color - typescript @@ -16582,14 +16585,14 @@ snapshots: optionalDependencies: typescript: 5.8.3 - cosmiconfig@8.3.6(typescript@6.0.0-dev.20250829): + cosmiconfig@8.3.6(typescript@6.0.0-dev.20250912): dependencies: import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 6.0.0-dev.20250829 + typescript: 6.0.0-dev.20250912 create-ecdh@4.0.4: dependencies: @@ -16971,7 +16974,7 @@ snapshots: dependencies: semver: 7.7.2 shelljs: 0.8.5 - typescript: 6.0.0-dev.20250829 + typescript: 6.0.0-dev.20250912 dunder-proto@1.0.1: dependencies: @@ -17591,7 +17594,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@8.0.0(typescript@6.0.0-dev.20250829)(webpack@5.88.0(esbuild@0.25.8)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@6.0.0-dev.20250912)(webpack@5.88.0(esbuild@0.25.8)): dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -17605,7 +17608,7 @@ snapshots: schema-utils: 3.3.0 semver: 7.7.2 tapable: 2.2.2 - typescript: 6.0.0-dev.20250829 + typescript: 6.0.0-dev.20250912 webpack: 5.88.0(esbuild@0.25.8) form-data@2.5.5: @@ -18289,16 +18292,16 @@ snapshots: - ts-node optional: true - jest-cli@29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)): + jest-cli@29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.2.0 - jest-config: 29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)) + jest-config: 29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)) jest-util: 29.7.0 jest-validate: 29.7.0 prompts: 2.4.2 @@ -18341,7 +18344,7 @@ snapshots: - supports-color optional: true - jest-config@29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)): + jest-config@29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)): dependencies: '@babel/core': 7.24.3 '@jest/test-sequencer': 29.7.0 @@ -18367,7 +18370,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.19.9 - ts-node: 10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829) + ts-node: 10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -18404,7 +18407,7 @@ snapshots: - supports-color optional: true - jest-config@29.7.0(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)): + jest-config@29.7.0(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)): dependencies: '@babel/core': 7.24.3 '@jest/test-sequencer': 29.7.0 @@ -18430,7 +18433,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.19.9 - ts-node: 10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829) + ts-node: 10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -18698,12 +18701,12 @@ snapshots: - ts-node optional: true - jest@29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)): + jest@29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)): dependencies: - '@jest/core': 29.6.2(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)) + '@jest/core': 29.6.2(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829)) + jest-cli: 29.6.2(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -20096,9 +20099,9 @@ snapshots: dependencies: typescript: 5.8.3 - react-docgen-typescript@2.2.2(typescript@6.0.0-dev.20250829): + react-docgen-typescript@2.2.2(typescript@6.0.0-dev.20250912): dependencies: - typescript: 6.0.0-dev.20250829 + typescript: 6.0.0-dev.20250912 react-docgen@7.1.1: dependencies: @@ -21040,7 +21043,7 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250829): + ts-node@10.9.2(@types/node@20.19.9)(typescript@6.0.0-dev.20250912): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -21054,7 +21057,7 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 6.0.0-dev.20250829 + typescript: 6.0.0-dev.20250912 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -21166,7 +21169,7 @@ snapshots: typescript@5.8.3: {} - typescript@6.0.0-dev.20250829: {} + typescript@6.0.0-dev.20250912: {} unbox-primitive@1.1.0: dependencies: