diff --git a/packages/@react-spectrum/s2/exports/index.ts b/packages/@react-spectrum/s2/exports/index.ts index a7c8c7c9f7c..1d0a90f0685 100644 --- a/packages/@react-spectrum/s2/exports/index.ts +++ b/packages/@react-spectrum/s2/exports/index.ts @@ -164,7 +164,7 @@ export { isFileDropItem, isTextDropItem } from 'react-aria/useDrop'; -export {useDragAndDrop} from 'react-aria-components/useDragAndDrop'; +export {useDragAndDrop} from './useDragAndDrop'; export type { AccordionProps, @@ -315,7 +315,8 @@ export type { AsyncListLoadOptions, AsyncListStateUpdate } from 'react-stately/useAsyncList'; -export type {DragAndDropHooks, DragAndDropOptions} from 'react-aria-components/useDragAndDrop'; +export type {DragAndDropHooks} from 'react-aria-components/useDragAndDrop'; +export type {DragAndDropOptions} from './useDragAndDrop'; export type { DirectoryDropItem, DraggableCollectionEndEvent, diff --git a/packages/@react-spectrum/s2/exports/useDragAndDrop.ts b/packages/@react-spectrum/s2/exports/useDragAndDrop.ts index 0c8fc0a7eb7..dbf89aacacd 100644 --- a/packages/@react-spectrum/s2/exports/useDragAndDrop.ts +++ b/packages/@react-spectrum/s2/exports/useDragAndDrop.ts @@ -1,11 +1,11 @@ -export {useDragAndDrop} from 'react-aria-components/useDragAndDrop'; +export {useDragAndDrop} from '../src/useDragAndDrop'; export { DIRECTORY_DRAG_TYPE, isDirectoryDropItem, isFileDropItem, isTextDropItem } from 'react-aria/useDrop'; -export type {DragAndDropHooks, DragAndDropOptions} from 'react-aria-components/useDragAndDrop'; +export type {DragAndDropHooks} from 'react-aria-components/useDragAndDrop'; export type { DirectoryDropItem, DraggableCollectionEndEvent, @@ -30,3 +30,5 @@ export type { RootDropTarget, TextDropItem } from '@react-types/shared'; +export type {DragAndDropOptions} from '../src/useDragAndDrop'; +export type {DragAndDrop} from 'react-aria-components/useDragAndDrop'; diff --git a/packages/@react-spectrum/s2/src/useDragAndDrop.ts b/packages/@react-spectrum/s2/src/useDragAndDrop.ts new file mode 100644 index 00000000000..56070b334e0 --- /dev/null +++ b/packages/@react-spectrum/s2/src/useDragAndDrop.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import type { + DragAndDrop, + DragAndDropOptions as RACDragAndDropOptions +} from 'react-aria-components/useDragAndDrop'; +import {useDragAndDrop as useRACDragAndDrop} from 'react-aria-components/useDragAndDrop'; + +export type {DragAndDrop}; + +export interface DragAndDropOptions extends Omit< + RACDragAndDropOptions, + 'renderDropIndicator' +> {} + +/** + * Provides the hooks required to enable drag and drop behavior for a drag and drop compatible + * collection component. + */ +export function useDragAndDrop(options: DragAndDropOptions): DragAndDrop { + return useRACDragAndDrop(options); +} diff --git a/packages/dev/codemods/src/index.ts b/packages/dev/codemods/src/index.ts index 0e73081d38e..33308d09c05 100644 --- a/packages/dev/codemods/src/index.ts +++ b/packages/dev/codemods/src/index.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node const {parseArgs} = require('node:util'); import {s1_to_s2} from './s1-to-s2/src'; +import {test_utils_rc_update} from './test-utils-rc-update/src'; import {use_monopackages} from './use-monopackages/src'; import {use_subpaths} from './use-subpaths/src'; @@ -57,15 +58,22 @@ export interface UseMonopackagesCodemodOptions extends JSCodeshiftOptions { export interface UseSubpathsCodemodOptions extends JSCodeshiftOptions {} +export interface TestUtilsRcUpdateOptions extends JSCodeshiftOptions {} + const codemods: Record< string, ( - options: S1ToS2CodemodOptions | UseMonopackagesCodemodOptions | UseSubpathsCodemodOptions + options: + | S1ToS2CodemodOptions + | UseMonopackagesCodemodOptions + | UseSubpathsCodemodOptions + | TestUtilsRcUpdateOptions ) => void > = { 's1-to-s2': s1_to_s2, 'use-monopackages': use_monopackages, - 'use-subpaths': use_subpaths + 'use-subpaths': use_subpaths, + 'test-utils-rc-update': test_utils_rc_update }; // https://github.com/facebook/jscodeshift?tab=readme-ov-file#usage-cli diff --git a/packages/dev/codemods/src/test-utils-rc-update/README.md b/packages/dev/codemods/src/test-utils-rc-update/README.md new file mode 100644 index 00000000000..5e4342a9f02 --- /dev/null +++ b/packages/dev/codemods/src/test-utils-rc-update/README.md @@ -0,0 +1,30 @@ +# Codemod for @react-aria/test-utils RC API update + +Updates test files that use the `@react-aria/test-utils` or `@react-spectrum/test-utils` testers when upgrading the test utils from beta to RC. This updates your tests to use the +renamed getters alongside other changes. + +```diff +- tester.listbox; ++ tester.getListbox(); + +- tester.options(); ++ tester.getOptions(); + +- await tester.selectOption({option: 'Save'}); ++ await tester.toggleOptionSelection({option: 'Save'}); + +- tester.findOption({optionIndexOrText: 'Save'}); ++ tester.findOption({indexOrText: 'Save'}); +``` + +Handles all tester types: `ListBox`, `ComboBox`, `Select`, `Menu`, `Table`, `GridList`, `Tree`, `Tabs`, `RadioGroup`, `CheckboxGroup`, and `Dialog`. + +## Usage + +Run `npx @react-spectrum/codemods test-utils-rc-update` from the directory you want to update test files in. + +### Options + +- `--ignore-pattern` - A [glob pattern](https://github.com/facebook/jscodeshift?tab=readme-ov-file#ignoring-files-and-directories) of files to ignore. Defaults to `**/node_modules/**`. +- `--dry` - Run the codemod without making changes to the files. +- `--path` - The path to the directory to run the codemod in. Defaults to the current directory. diff --git a/packages/dev/codemods/src/test-utils-rc-update/src/codemod.test.ts b/packages/dev/codemods/src/test-utils-rc-update/src/codemod.test.ts new file mode 100644 index 00000000000..b0a55622c95 --- /dev/null +++ b/packages/dev/codemods/src/test-utils-rc-update/src/codemod.test.ts @@ -0,0 +1,333 @@ +// @ts-ignore +import {defineInlineTest} from 'jscodeshift/dist/testUtils'; +import transform from './codemod'; + +function test(name: string, input: string, output: string) { + defineInlineTest(transform, {}, input, output, name); +} + +test( + 'checkboxgroup: renames getters and findCheckbox param key', + ` +const tester = user.createTester('CheckboxGroup', {root: el}); +tester.checkboxgroup; +tester.checkboxes; +tester.selectedCheckboxes; +tester.findCheckbox({checkboxIndexOrText: 3}); +`, + ` +const tester = user.createTester('CheckboxGroup', {root: el}); +tester.getCheckboxGroup(); +tester.getCheckboxes(); +tester.getSelectedCheckboxes(); +tester.findCheckbox({indexOrText: 3}); +` +); + +test( + 'combobox: renames getters, getOptions, toggleOptionSelection, and findOption param key', + ` +const tester = user.createTester('ComboBox', {root: el}); +tester.combobox; +tester.trigger; +tester.listbox; +tester.sections; +tester.options(); +tester.focusedOption; +await tester.selectOption({option: 'Two'}); +tester.findOption({optionIndexOrText: 'Two'}); +`, + ` +const tester = user.createTester('ComboBox', {root: el}); +tester.getCombobox(); +tester.getTrigger(); +tester.getListbox(); +tester.getSections(); +tester.getOptions(); +tester.getFocusedOption(); +await tester.toggleOptionSelection({option: 'Two'}); +tester.findOption({indexOrText: 'Two'}); +` +); + +test( + 'dialog: renames getters', + ` +const tester = user.createTester('Dialog', {root: el}); +tester.trigger; +tester.dialog; +`, + ` +const tester = user.createTester('Dialog', {root: el}); +tester.getTrigger(); +tester.getDialog(); +` +); + +test( + 'gridlist: renames getters, getCells, and findRow param key', + ` +const tester = user.createTester('GridList', {root: el}); +tester.gridlist; +tester.rows; +tester.selectedRows; +tester.cells(); +tester.findRow({rowIndexOrText: 'Item 1'}); +`, + ` +const tester = user.createTester('GridList', {root: el}); +tester.getGridlist(); +tester.getRows(); +tester.getSelectedRows(); +tester.getCells(); +tester.findRow({indexOrText: 'Item 1'}); +` +); + +test( + 'listbox: renames getters, getOptions, and findOption param key', + ` +const tester = user.createTester('ListBox', {root: el}); +tester.listbox; +tester.selectedOptions; +tester.sections; +tester.options(); +tester.findOption({optionIndexOrText: 'Foo'}); +tester.findOption({optionIndexOrText: 3}); +`, + ` +const tester = user.createTester('ListBox', {root: el}); +tester.getListbox(); +tester.getSelectedOptions(); +tester.getSections(); +tester.getOptions(); +tester.findOption({indexOrText: 'Foo'}); +tester.findOption({indexOrText: 3}); +` +); + +test( + 'menu: renames getters, getOptions, and toggleOptionSelection', + ` +const tester = user.createTester('Menu', {root: el}); +tester.trigger; +tester.menu; +tester.sections; +tester.options(); +tester.submenuTriggers; +await tester.selectOption({option: 'Bar', menuSelectionMode: 'single'}); +`, + ` +const tester = user.createTester('Menu', {root: el}); +tester.getTrigger(); +tester.getMenu(); +tester.getSections(); +tester.getOptions(); +tester.getSubmenuTriggers(); +await tester.toggleOptionSelection({option: 'Bar', menuSelectionMode: 'single'}); +` +); + +test( + 'radiogroup: renames getters and findRadio param key', + ` +const tester = user.createTester('RadioGroup', {root: el}); +tester.radiogroup; +tester.radios; +tester.selectedRadio; +tester.findRadio({radioIndexOrText: 3}); +tester.findRadio({radioIndexOrText: 'Chocobo'}); +`, + ` +const tester = user.createTester('RadioGroup', {root: el}); +tester.getRadioGroup(); +tester.getRadios(); +tester.getSelectedRadio(); +tester.findRadio({indexOrText: 3}); +tester.findRadio({indexOrText: 'Chocobo'}); +` +); + +test( + 'select: renames getters, getOptions, toggleOptionSelection, and findOption param key', + ` +const tester = user.createTester('Select', {root: el}); +tester.trigger; +tester.listbox; +tester.sections; +tester.options(); +await tester.selectOption({option: 'Three'}); +tester.findOption({optionIndexOrText: 'Three'}); +`, + ` +const tester = user.createTester('Select', {root: el}); +tester.getTrigger(); +tester.getListbox(); +tester.getSections(); +tester.getOptions(); +await tester.toggleOptionSelection({option: 'Three'}); +tester.findOption({indexOrText: 'Three'}); +` +); + +test( + 'table: renames getters, getCells, and findRow param key', + ` +const tester = user.createTester('Table', {root: el}); +tester.table; +tester.rowGroups; +tester.columns; +tester.rows; +tester.selectedRows; +tester.rowHeaders; +tester.cells(); +tester.findRow({rowIndexOrText: 'Apples'}); +`, + ` +const tester = user.createTester('Table', {root: el}); +tester.getTable(); +tester.getRowGroups(); +tester.getColumns(); +tester.getRows(); +tester.getSelectedRows(); +tester.getRowHeaders(); +tester.getCells(); +tester.findRow({indexOrText: 'Apples'}); +` +); + +test( + 'tabs: renames getters and findTab param key', + ` +const tester = user.createTester('Tabs', {root: el}); +tester.tablist; +tester.tabs; +tester.tabpanels; +tester.selectedTab; +tester.activeTabpanel; +tester.findTab({tabIndexOrText: 3}); +tester.findTab({tabIndexOrText: 'Tab 5'}); +`, + ` +const tester = user.createTester('Tabs', {root: el}); +tester.getTablist(); +tester.getTabs(); +tester.getTabpanels(); +tester.getSelectedTab(); +tester.getActiveTabpanel(); +tester.findTab({indexOrText: 3}); +tester.findTab({indexOrText: 'Tab 5'}); +` +); + +test( + 'tree: renames getters, getCells, and findRow param key', + ` +const tester = user.createTester('Tree', {root: el}); +tester.tree; +tester.rows; +tester.selectedRows; +tester.cells(); +tester.findRow({rowIndexOrText: 'Folder'}); +`, + ` +const tester = user.createTester('Tree', {root: el}); +tester.getTree(); +tester.getRows(); +tester.getSelectedRows(); +tester.getCells(); +tester.findRow({indexOrText: 'Folder'}); +` +); + +test( + 'does not rename accessors on non-tester variables', + ` +const rows = someOtherObject.rows; +const options = config.options(); +const table = ui.table; +someOtherObj.findOption({optionIndexOrText: 'Foo'}); +someOtherObj.findRow({rowIndexOrText: 3}); +`, + ` +const rows = someOtherObject.rows; +const options = config.options(); +const table = ui.table; +someOtherObj.findOption({optionIndexOrText: 'Foo'}); +someOtherObj.findRow({rowIndexOrText: 3}); +` +); + +test( + 'handles accessor used inline without intermediate variable', + ` +const tester = user.createTester('Tree', {root: el}); +expect(tester.tree).toBeDefined(); +expect(tester.rows.length).toBe(3); +expect(tester.selectedRows).toHaveLength(1); +expect(tester.cells()).toHaveLength(6); +`, + ` +const tester = user.createTester('Tree', {root: el}); +expect(tester.getTree()).toBeDefined(); +expect(tester.getRows().length).toBe(3); +expect(tester.getSelectedRows()).toHaveLength(1); +expect(tester.getCells()).toHaveLength(6); +` +); + +test( + 'handles optional chaining on direct tester variables', + ` +const tester = user.createTester('Menu', {root: el}); +expect(tester?.menu).toBeInTheDocument(); +expect(tester?.options()).toHaveLength(3); +expect(tester?.submenuTriggers[0]).toBeDefined(); +`, + ` +const tester = user.createTester('Menu', {root: el}); +expect(tester?.getMenu()).toBeInTheDocument(); +expect(tester?.getOptions()).toHaveLength(3); +expect(tester?.getSubmenuTriggers()[0]).toBeDefined(); +` +); + +test( + 'handles variables assigned from openSubmenu with non-null assertion', + ` +const menuTester = user.createTester('Menu', {root: el}); +let submenuUtil = (await menuTester.openSubmenu({submenuTrigger: 'Share…'}))!; +let submenu = submenuUtil.menu; +expect(submenuUtil.options()).toHaveLength(2); +`, + ` +const menuTester = user.createTester('Menu', {root: el}); +let submenuUtil = (await menuTester.openSubmenu({submenuTrigger: 'Share…'}))!; +let submenu = submenuUtil.getMenu(); +expect(submenuUtil.getOptions()).toHaveLength(2); +` +); + +test( + 'handles variables assigned from openSubmenu and optional chaining on them', + ` +const menuTester = user.createTester('Menu', {root: el}); +let submenuTester = await menuTester.openSubmenu({submenuTrigger: 'Copy'}); +let nestedSubmenu = await submenuTester?.openSubmenu({submenuTrigger: 'Email'}); +expect(submenuTester?.menu).toBeInTheDocument(); +expect(submenuTester?.options()).toHaveLength(3); +expect(nestedSubmenu?.menu).toBeInTheDocument(); +expect(document.activeElement).toBe(nestedSubmenu?.trigger); +expect(nestedSubmenu?.options()[0]).toBeFocused(); +`, + ` +const menuTester = user.createTester('Menu', {root: el}); +let submenuTester = await menuTester.openSubmenu({submenuTrigger: 'Copy'}); +let nestedSubmenu = await submenuTester?.openSubmenu({submenuTrigger: 'Email'}); +expect(submenuTester?.getMenu()).toBeInTheDocument(); +expect(submenuTester?.getOptions()).toHaveLength(3); +expect(nestedSubmenu?.getMenu()).toBeInTheDocument(); +expect(document.activeElement).toBe(nestedSubmenu?.getTrigger()); +expect(nestedSubmenu?.getOptions()[0]).toBeFocused(); +` +); diff --git a/packages/dev/codemods/src/test-utils-rc-update/src/codemod.ts b/packages/dev/codemods/src/test-utils-rc-update/src/codemod.ts new file mode 100644 index 00000000000..0a39400ef95 --- /dev/null +++ b/packages/dev/codemods/src/test-utils-rc-update/src/codemod.ts @@ -0,0 +1,274 @@ +import {API, FileInfo} from 'jscodeshift'; +import {parse} from '@babel/parser'; +import {parse as recastParse} from 'recast'; + +const RENAME_MAP: Record = { + checkboxgroup: 'getCheckboxGroup', + checkboxes: 'getCheckboxes', + selectedCheckboxes: 'getSelectedCheckboxes', + combobox: 'getCombobox', + trigger: 'getTrigger', + listbox: 'getListbox', + sections: 'getSections', + options: 'getOptions', + focusedOption: 'getFocusedOption', + selectOption: 'toggleOptionSelection', + dialog: 'getDialog', + gridlist: 'getGridlist', + rows: 'getRows', + selectedRows: 'getSelectedRows', + cells: 'getCells', + selectedOptions: 'getSelectedOptions', + menu: 'getMenu', + submenuTriggers: 'getSubmenuTriggers', + radiogroup: 'getRadioGroup', + radios: 'getRadios', + selectedRadio: 'getSelectedRadio', + table: 'getTable', + rowGroups: 'getRowGroups', + columns: 'getColumns', + rowHeaders: 'getRowHeaders', + tablist: 'getTablist', + tabpanels: 'getTabpanels', + selectedTab: 'getSelectedTab', + tabs: 'getTabs', + activeTabpanel: 'getActiveTabpanel', + tree: 'getTree' +}; + +const FIND_PARAM_KEY_MAP: Record = { + findCheckbox: 'checkboxIndexOrText', + findOption: 'optionIndexOrText', + findRow: 'rowIndexOrText', + findRadio: 'radioIndexOrText', + findTab: 'tabIndexOrText' +}; + +// Methods on a tester that return another tester object +const TESTER_RETURNING_METHODS = new Set(['openSubmenu']); + +export default function transformer(file: FileInfo, api: API): string { + let j = api.jscodeshift.withParser({ + parse(source: string) { + return recastParse(source, { + parser: { + parse(innerSource: string) { + return parse(innerSource, { + sourceType: 'module', + plugins: [ + 'jsx', + 'typescript', + 'importAssertions', + 'dynamicImport', + 'decorators-legacy', + 'classProperties', + 'classPrivateProperties', + 'classPrivateMethods', + 'exportDefaultFrom', + 'exportNamespaceFrom', + 'objectRestSpread', + 'optionalChaining', + 'nullishCoalescingOperator', + 'topLevelAwait' + ], + tokens: true, + errorRecovery: true + }); + } + } + }); + } + }); + + let root = j(file.source); + + let testerVarNames = new Set(); + + function unwrapAwait(node: any): any { + while (node?.type === 'AwaitExpression' || node?.type === 'TSNonNullExpression') { + node = node.type === 'TSNonNullExpression' ? node.expression : node.argument; + } + return node; + } + + function isCallOf(node: any): boolean { + return node?.type === 'CallExpression' || node?.type === 'OptionalCallExpression'; + } + + function isMemberOf(node: any): boolean { + return node?.type === 'MemberExpression' || node?.type === 'OptionalMemberExpression'; + } + + // Initial pass: find variables directly from createTester(...) + root.find(j.VariableDeclarator).forEach(path => { + let id = path.node.id; + if (id.type !== 'Identifier') { + return; + } + let call = unwrapAwait(path.node.init as any); + if (!isCallOf(call)) { + return; + } + let callee = call.callee; + let isCreateTester = + (isMemberOf(callee) && callee.property?.name === 'createTester') || + (callee?.type === 'Identifier' && callee?.name === 'createTester'); + if (isCreateTester) { + testerVarNames.add(id.name); + } + }); + + if (testerVarNames.size === 0) { + return file.source; + } + + // Multi-pass: propagate tracking through methods that return testers (e.g. openSubmenu) + let propagating = true; + while (propagating) { + propagating = false; + root.find(j.VariableDeclarator).forEach(path => { + let id = path.node.id; + if (id.type !== 'Identifier' || testerVarNames.has(id.name)) { + return; + } + let call = unwrapAwait(path.node.init as any); + if (!isCallOf(call) || !isMemberOf(call.callee)) { + return; + } + let obj = call.callee.object; + let methodName = call.callee.property?.type === 'Identifier' ? call.callee.property.name : ''; + if ( + obj?.type === 'Identifier' && + testerVarNames.has(obj.name) && + TESTER_RETURNING_METHODS.has(methodName) + ) { + testerVarNames.add(id.name); + propagating = true; + } + }); + } + + let didChange = false; + + function renamePropInCallee(callee: any): boolean { + if ( + !isMemberOf(callee) || + callee.object?.type !== 'Identifier' || + !testerVarNames.has(callee.object.name) + ) { + return false; + } + let propName: string = callee.property?.type === 'Identifier' ? callee.property.name : ''; + let newName = RENAME_MAP[propName]; + if (!newName) { + return false; + } + callee.property = j.identifier(newName); + return true; + } + + // Pass 1: Rename method calls — tester.options() → tester.getOptions() + root.find(j.CallExpression, {callee: {type: 'MemberExpression'}}).forEach(path => { + if (path.node.type !== 'CallExpression') { + return; // skip OptionalCallExpression matched by babel parser + } + if (renamePropInCallee(path.node.callee)) { + didChange = true; + } + }); + + // Pass 1B: tester?.options() → tester?.getOptions() + root.find(j.OptionalCallExpression).forEach(path => { + if (renamePropInCallee(path.node.callee as any)) { + didChange = true; + } + }); + + // Pass 2: Property access → call — tester.rows → tester.getRows() + root.find(j.MemberExpression).forEach(path => { + let node = path.node as any; + if (node.type !== 'MemberExpression') { + return; // skip OptionalMemberExpression matched by babel parser + } + if (node.object?.type !== 'Identifier' || !testerVarNames.has(node.object.name)) { + return; + } + let propName: string = node.property?.type === 'Identifier' ? node.property.name : ''; + let newName = RENAME_MAP[propName]; + if (!newName) { + return; + } + let parent = path.parent?.node; + if (parent?.type === 'CallExpression' && parent.callee === path.node) { + return; + } + j(path).replaceWith( + j.callExpression(j.memberExpression(node.object, j.identifier(newName)), []) + ); + didChange = true; + }); + + // Pass 2B: tester?.rows → tester?.getRows() + root.find(j.OptionalMemberExpression).forEach(path => { + let node = path.node as any; + if (node.object?.type !== 'Identifier' || !testerVarNames.has(node.object.name)) { + return; + } + let propName: string = node.property?.type === 'Identifier' ? node.property.name : ''; + let newName = RENAME_MAP[propName]; + if (!newName) { + return; + } + let parent = path.parent?.node; + if ( + (parent?.type === 'OptionalCallExpression' || parent?.type === 'CallExpression') && + parent.callee === path.node + ) { + return; + } + j(path).replaceWith( + j.optionalCallExpression( + j.optionalMemberExpression(node.object, j.identifier(newName), false, true), + [], + false + ) + ); + didChange = true; + }); + + // Pass 3: Rename param keys inside find* calls — {optionIndexOrText: x} → {indexOrText: x} + function renameFindParamKey(callPath: any): void { + let callee = callPath.node.callee as any; + if ( + !isMemberOf(callee) || + callee.object?.type !== 'Identifier' || + !testerVarNames.has(callee.object.name) + ) { + return; + } + let methodName: string = callee.property?.type === 'Identifier' ? callee.property.name : ''; + let oldKey = FIND_PARAM_KEY_MAP[methodName]; + if (!oldKey) { + return; + } + let firstArg = callPath.node.arguments[0] as any; + if (firstArg?.type !== 'ObjectExpression') { + return; + } + for (let prop of firstArg.properties) { + if ( + prop.type === 'ObjectProperty' && + prop.key?.type === 'Identifier' && + prop.key.name === oldKey + ) { + prop.key = j.identifier('indexOrText'); + didChange = true; + } + } + } + + root.find(j.CallExpression, {callee: {type: 'MemberExpression'}}).forEach(renameFindParamKey); + root.find(j.OptionalCallExpression).forEach(renameFindParamKey); + + return didChange ? root.toSource({quote: 'single'}) : file.source; +} diff --git a/packages/dev/codemods/src/test-utils-rc-update/src/index.ts b/packages/dev/codemods/src/test-utils-rc-update/src/index.ts new file mode 100644 index 00000000000..a02db687a0f --- /dev/null +++ b/packages/dev/codemods/src/test-utils-rc-update/src/index.ts @@ -0,0 +1,12 @@ +import {run as jscodeshift} from 'jscodeshift/src/Runner.js'; +import path from 'path'; +import {TestUtilsRcUpdateOptions} from '../..'; + +const transformPath = path.join(__dirname, 'codemod.js'); + +export async function test_utils_rc_update( + options: TestUtilsRcUpdateOptions +): Promise> { + let {path: filePath = '.', ...rest} = options; + return await jscodeshift(transformPath, [filePath], rest); +} diff --git a/packages/react-aria-components/exports/useDragAndDrop.ts b/packages/react-aria-components/exports/useDragAndDrop.ts index 194da728e63..55a19ecb3f5 100644 --- a/packages/react-aria-components/exports/useDragAndDrop.ts +++ b/packages/react-aria-components/exports/useDragAndDrop.ts @@ -27,6 +27,7 @@ export { isTextDropItem } from 'react-aria/useDrop'; export type { + DragAndDrop, DragAndDropHooks, DragAndDropOptions, DropIndicatorProps,