diff --git a/bin/imports.js b/bin/imports.js index bc792f2692a..c6896080870 100644 --- a/bin/imports.js +++ b/bin/imports.js @@ -22,7 +22,15 @@ const devDependencies = new Set([ '@parcel/macros', '@adobe/spectrum-tokens', 'playwright', - 'axe-playwright' + 'axe-playwright', + 'vitest', + '@vitejs/plugin-react', + '@vitest/browser', + '@vitest/browser-playwright', + '@vitest/ui', + 'unplugin-parcel-macros', + 'vite', + 'vite-plugin-svgr' ]); module.exports = { diff --git a/package.json b/package.json index bcf6427197d..41ddbb4358c 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "start:mcp": "yarn workspace @react-spectrum/s2-docs generate:md && yarn build:mcp && node packages/dev/mcp/s2/dist/index.js && node packages/dev/mcp/react-aria/dist/index.js", "test:mcp": "yarn build:s2-docs && yarn build:mcp && node packages/dev/mcp/scripts/smoke-list-pages.mjs", "test": "cross-env STRICT_MODE=1 VIRT_ON=1 yarn jest", + "test:s2": "yarn workspace @react-spectrum/s2 test", "test:lint": "node packages/**/*.test-lint.js", "test-loose": "cross-env VIRT_ON=1 yarn jest", "test-storybook": "test-storybook --url http://localhost:9003 --browsers chromium --no-cache", @@ -138,6 +139,8 @@ "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@typescript/native-preview": "^7.0.0-dev.20251223.1", + "@vitest/browser-playwright": "^4.0.17", + "@vitest/browser-preview": "^4.0.17", "@yarnpkg/types": "^4.0.0", "autoprefixer": "^9.6.0", "axe-playwright": "^1.1.11", @@ -204,6 +207,9 @@ "typescript": "^5.8.2", "typescript-eslint": "^8.38.0", "verdaccio": "^6.0.0", + "vite-plugin-svgr": "^4.5.0", + "vitest": "^4.0.17", + "vitest-browser-react": "^2.0.2", "walk-object": "^4.0.0", "xml": "^1.0.1" }, diff --git a/packages/@react-spectrum/s2/package.json b/packages/@react-spectrum/s2/package.json index 544fb05905a..3c026e4beaf 100644 --- a/packages/@react-spectrum/s2/package.json +++ b/packages/@react-spectrum/s2/package.json @@ -137,16 +137,32 @@ "scripts": { "prepublishOnly": "rm -rf dist/page.cjs* && mv dist/page*.css page.css && mv dist/page.css.map page.css.map || true", "prepack": "npm pkg delete scripts devDependencies resolutions alias targets", - "postpack": "git checkout -- package.json" + "postpack": "git checkout -- package.json", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage" }, "devDependencies": { "@adobe/spectrum-tokens": "^14.0.0", "@react-aria/test-utils": "^1.0.0-alpha.8", "@storybook/jest": "^0.2.3", "@testing-library/dom": "^10.1.0", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", - "jest": "^29.5.0" + "@vitejs/plugin-react": "^4.3.4", + "@vitest/browser": "^4.0.0", + "@vitest/browser-playwright": "^4.0.0", + "@vitest/ui": "^4.0.0", + "glob": "^10.3.0", + "jsdom": "^25.0.0", + "playwright": "^1.45.3", + "unplugin-parcel-macros": "^0.1.2-alpha.1", + "vite": "^6.0.0", + "vite-plugin-svgr": "^4.3.0", + "vitest": "^4.0.0", + "vitest-browser-react": "^2.0.2" }, "dependencies": { "@internationalized/date": "^3.10.1", diff --git a/packages/@react-spectrum/s2/test/Accordion.test.tsx b/packages/@react-spectrum/s2/test/Accordion.test.tsx new file mode 100644 index 00000000000..bc75dd51a4f --- /dev/null +++ b/packages/@react-spectrum/s2/test/Accordion.test.tsx @@ -0,0 +1,41 @@ +/* + * 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 { + Accordion, + AccordionItem, + AccordionItemPanel, + AccordionItemTitle +} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; +import {style} from '../style' with {type: 'macro'}; + +describe('Accordion', () => { + it('renders', async () => { + const {container} = await render( + + + Personal Information + Personal information form here. + + + Billing Address + Billing address form here. + + + ); + const buttons = container.querySelectorAll('button'); + expect(buttons.length).toBe(2); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ActionBar.test.tsx b/packages/@react-spectrum/s2/test/ActionBar.test.tsx new file mode 100644 index 00000000000..08c2c320d35 --- /dev/null +++ b/packages/@react-spectrum/s2/test/ActionBar.test.tsx @@ -0,0 +1,81 @@ +/* + * 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 { + ActionBar, + ActionButton, + Cell, + Column, + Row, + TableBody, + TableHeader, + TableView, + Text +} from '../src'; +import Copy from '@react-spectrum/s2/icons/Copy'; +import Delete from '@react-spectrum/s2/icons/Delete'; +import {describe, expect, it} from 'vitest'; +import Edit from '@react-spectrum/s2/icons/Edit'; +import React from 'react'; +import {render} from './utils/render'; +import {style} from '../style' with {type: 'macro'}; + +describe('ActionBar', () => { + it('renders', async () => { + let rows = [ + {id: 1, name: 'Charizard', type: 'Fire, Flying', level: '67'}, + {id: 2, name: 'Blastoise', type: 'Water', level: '56'}, + {id: 3, name: 'Venusaur', type: 'Grass, Poison', level: '83'}, + {id: 4, name: 'Pikachu', type: 'Electric', level: '100'} + ]; + + const screen = await render( + ( + + alert('Edit action')}> + + Edit + + alert('Copy action')}> + + Copy + + alert('Delete action')}> + + Delete + + + )}> + + Name + Type + Level + + + {item => ( + + {item.name} + {item.type} + {item.level} + + )} + + + ); + expect(screen.getByText('Edit')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ActionButton.test.tsx b/packages/@react-spectrum/s2/test/ActionButton.test.tsx new file mode 100644 index 00000000000..aec4cf2a4cb --- /dev/null +++ b/packages/@react-spectrum/s2/test/ActionButton.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {ActionButton} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ActionButton', () => { + it('renders', async () => { + const screen = await render(Action); + await expect.element(screen.getByRole('button')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ActionButtonGroup.test.tsx b/packages/@react-spectrum/s2/test/ActionButtonGroup.test.tsx index 9565351a525..d1d2b17ddb9 100644 --- a/packages/@react-spectrum/s2/test/ActionButtonGroup.test.tsx +++ b/packages/@react-spectrum/s2/test/ActionButtonGroup.test.tsx @@ -11,55 +11,31 @@ */ import {ActionButton, ActionButtonGroup, Text} from '../src'; -import {render} from '@react-spectrum/test-utils-internal'; +import Copy from '@react-spectrum/s2/icons/Copy'; +import Cut from '@react-spectrum/s2/icons/Cut'; +import {describe, expect, it} from 'vitest'; +import Paste from '@react-spectrum/s2/icons/Paste'; +import {render} from './utils/render'; describe('ActionButtonGroup', () => { - - it('can disable all buttons from the group', async () => { - let {getAllByRole} = render( - - Bold - Italic - Underline - - ); - - - let buttons = getAllByRole('button'); - expect(buttons[0]).toBeDisabled(); - expect(buttons[1]).toBeDisabled(); - expect(buttons[2]).toBeDisabled(); - }); - - it('can set disable individually', async () => { - let {getAllByRole} = render( + it('renders', async () => { + const screen = await render( - Bold - Italic - Underline + + + Cut + + + + Copy + + + + Paste + ); - - - let buttons = getAllByRole('button'); - expect(buttons[0]).toBeDisabled(); - expect(buttons[1]).not.toBeDisabled(); - expect(buttons[2]).not.toBeDisabled(); - }); - - it('can override the group disable', async () => { - let {getAllByRole} = render( - - Bold - Italic - Underline - - ); - - - let buttons = getAllByRole('button'); - expect(buttons[0]).not.toBeDisabled(); - expect(buttons[1]).toBeDisabled(); - expect(buttons[2]).toBeDisabled(); + const buttons = screen.container.querySelectorAll('button'); + expect(buttons.length).toBe(3); }); }); diff --git a/packages/@react-spectrum/s2/test/ActionMenu.test.tsx b/packages/@react-spectrum/s2/test/ActionMenu.test.tsx new file mode 100644 index 00000000000..5611333661e --- /dev/null +++ b/packages/@react-spectrum/s2/test/ActionMenu.test.tsx @@ -0,0 +1,53 @@ +/* + * 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 {ActionMenu, Keyboard, MenuItem, Text} from '../src'; +import Copy from '@react-spectrum/s2/icons/Copy'; +import Cut from '@react-spectrum/s2/icons/Cut'; +import {describe, expect, it} from 'vitest'; +import Paste from '@react-spectrum/s2/icons/Paste'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ActionMenu', () => { + it('renders', async () => { + const screen = await render( + + alert('copy')}> + + Copy + Copy the selected text + ⌘C + + alert('cut')}> + + Cut + Cut the selected text + ⌘X + + alert('paste')}> + + Paste + Paste the copied text + ⌘V + + + ); + expect(screen.getByRole('button')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/AlertDialog.test.tsx b/packages/@react-spectrum/s2/test/AlertDialog.test.tsx new file mode 100644 index 00000000000..c167c45af30 --- /dev/null +++ b/packages/@react-spectrum/s2/test/AlertDialog.test.tsx @@ -0,0 +1,37 @@ +/* + * 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 {ActionButton, AlertDialog, DialogTrigger} from '../src'; +import {describe, expect, it, vi} from 'vitest'; +import React from 'react'; +import {render} from './utils'; + +describe('AlertDialog', () => { + it('renders', async () => { + vi.useFakeTimers(); + const screen = await render( + + Open + + Are you sure you want to delete this item? + + + ); + vi.runAllTimers(); + expect(screen.getByRole('alertdialog')).toBeInTheDocument(); + vi.useRealTimers(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Avatar.test.tsx b/packages/@react-spectrum/s2/test/Avatar.test.tsx new file mode 100644 index 00000000000..f23a46e38f7 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Avatar.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 {Avatar} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +const avatarSrc = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADMElEQVR4nOzVwQnAIBQFQYXff81RUkQCOyDj1YOPnbXWPmeTRef+/3O/OyBjzh3CD95BfqICMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMO0TAAD//2Anhf4QtqobAAAAAElFTkSuQmCC'; + +describe('Avatar', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.getByAltText('Avatar')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/AvatarGroup.test.tsx b/packages/@react-spectrum/s2/test/AvatarGroup.test.tsx new file mode 100644 index 00000000000..c66527c099c --- /dev/null +++ b/packages/@react-spectrum/s2/test/AvatarGroup.test.tsx @@ -0,0 +1,38 @@ +/* + * 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 {Avatar, AvatarGroup} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('AvatarGroup', () => { + it('renders', async () => { + const screen = await render( + + + + + + + ); + expect(screen.getByRole('group')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Badge.test.tsx b/packages/@react-spectrum/s2/test/Badge.test.tsx new file mode 100644 index 00000000000..407cdd1039c --- /dev/null +++ b/packages/@react-spectrum/s2/test/Badge.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {Badge} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Badge', () => { + it('renders', async () => { + const screen = await render(Licensed); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Breadcrumbs.test.tsx b/packages/@react-spectrum/s2/test/Breadcrumbs.test.tsx new file mode 100644 index 00000000000..5e65f7de8e2 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Breadcrumbs.test.tsx @@ -0,0 +1,29 @@ +/* + * 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 {Breadcrumb, Breadcrumbs} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Breadcrumbs', () => { + it('renders', async () => { + const screen = await render( + + Home + React Spectrum + Breadcrumbs + + ); + expect(screen.getByRole('list')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Button.test.tsx b/packages/@react-spectrum/s2/test/Button.test.tsx new file mode 100644 index 00000000000..5663096c030 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Button.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {Button} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Button', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.getByRole('button')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ButtonGroup.test.tsx b/packages/@react-spectrum/s2/test/ButtonGroup.test.tsx new file mode 100644 index 00000000000..5f4bb76506a --- /dev/null +++ b/packages/@react-spectrum/s2/test/ButtonGroup.test.tsx @@ -0,0 +1,30 @@ +/* + * 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 {Button, ButtonGroup} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ButtonGroup', () => { + it('renders', async () => { + const screen = await render( + + + + + + ); + const buttons = screen.container.querySelectorAll('button'); + expect(buttons.length).toBe(3); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Calendar.test.tsx b/packages/@react-spectrum/s2/test/Calendar.test.tsx new file mode 100644 index 00000000000..68702cc8857 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Calendar.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 {Calendar} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Calendar', () => { + it('renders', async () => { + const screen = await render( + + ); + expect(screen.getByRole('grid')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Card.test.tsx b/packages/@react-spectrum/s2/test/Card.test.tsx new file mode 100644 index 00000000000..cf724bff21d --- /dev/null +++ b/packages/@react-spectrum/s2/test/Card.test.tsx @@ -0,0 +1,51 @@ +/* + * 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 { + ActionMenu, + Card, + CardPreview, + Content, + Footer, + Image, + MenuItem, + StatusLight, + Text +} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Card', () => { + it('renders', async () => { + const screen = await render( + + + + + + Card title + + Edit + Share + Delete + + Card description. Give a concise overview of the context or functionality that's mentioned in the card title. + +
+ Published +
+
+ ); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/CardView.test.tsx b/packages/@react-spectrum/s2/test/CardView.test.tsx new file mode 100644 index 00000000000..411ee266ef6 --- /dev/null +++ b/packages/@react-spectrum/s2/test/CardView.test.tsx @@ -0,0 +1,123 @@ +/* + * 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 { + AssetCard, + CardPreview, + CardView, + Content, + Image, + Text +} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('CardView', () => { + it('renders', async () => { + const screen = await render( + + + + + + + Desert Sunset + PNG • 2/3/2024 + + + + + + + + Hiking Trail + JPEG • 1/10/2022 + + + + + + + + Lion + JPEG • 8/28/2021 + + + + + + + + Mountain Sunrise + PNG • 3/15/2015 + + + + + + + + Giraffe tongue + PNG • 11/27/2019 + + + + + + + + Golden Hour + WEBP • 7/24/2024 + + + + + + + + Architecture + PNG • 12/24/2016 + + + + + + + + Peeking leopard + JPEG • 3/2/2016 + + + + + + + + Roofs + JPEG • 4/24/2025 + + + + + + + + Half Dome Deer + DNG • 8/28/2018 + + + + ); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Checkbox.test.tsx b/packages/@react-spectrum/s2/test/Checkbox.test.tsx new file mode 100644 index 00000000000..06ef22e271a --- /dev/null +++ b/packages/@react-spectrum/s2/test/Checkbox.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {Checkbox} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Checkbox', () => { + it('renders', async () => { + const screen = await render(Unsubscribe); + expect(screen.getByRole('checkbox')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/CheckboxGroup.test.tsx b/packages/@react-spectrum/s2/test/CheckboxGroup.test.tsx index 8081ede4888..2c9e5d1fd3a 100644 --- a/packages/@react-spectrum/s2/test/CheckboxGroup.test.tsx +++ b/packages/@react-spectrum/s2/test/CheckboxGroup.test.tsx @@ -10,88 +10,20 @@ * governing permissions and limitations under the License. */ -import {act, pointerMap, render, User} from '@react-spectrum/test-utils-internal'; -import {Checkbox, CheckboxGroup, Form, Provider} from '../src'; +import {Checkbox, CheckboxGroup} from '../src'; +import {describe, expect, it} from 'vitest'; import React from 'react'; -import userEvent from '@testing-library/user-event'; +import {render} from './utils/render'; describe('CheckboxGroup', () => { - let testUtilUser = new User(); - let user; - beforeAll(() => { - user = userEvent.setup({delay: null, pointerMap}); - }); - - it('should not require all checkboxes to be checked when Form has isRequired', async () => { - let {getByRole, getAllByRole, getByTestId} = render( -
- - Soccer - Baseball - Basketball - -
- ); - - - let group = getByRole('group'); - let checkbox = getAllByRole('checkbox')[0]; - - await user.click(checkbox); - act(() => {(getByTestId('form') as HTMLFormElement).checkValidity();}); - expect(group).not.toHaveAttribute('aria-describedby'); - expect(group).not.toHaveAttribute('data-invalid'); - - await user.click(checkbox); - act(() => {(getByTestId('form') as HTMLFormElement).checkValidity();}); - expect(group).toHaveAttribute('data-invalid'); - expect(group).toHaveAttribute('aria-describedby'); - let errorMsg = document.getElementById(group.getAttribute('aria-describedby')!); - expect(errorMsg).toHaveTextContent('Constraints not satisfied'); - }); - - it.each` - Name | props - ${'ltr + vertical'} | ${{locale: 'de-DE', orientation: 'vertical'}} - ${'rtl + verfical'} | ${{locale: 'ar-AE', orientation: 'vertical'}} - ${'ltr + horizontal'} | ${{locale: 'de-DE', orientation: 'horizontal'}} - ${'rtl + horizontal'} | ${{locale: 'ar-AE', orientation: 'horizontal'}} - `('$Name should select the correct checkbox regardless of orientation and disabled checkboxes', async function ({props}) { - let {getByRole} = render( - - - Soccer - Baseball - Basketball - Tennis - Rugby - - + it('renders', async () => { + const screen = await render( + + Soccer + Baseball + Basketball + ); - - let checkboxGroupTester = testUtilUser.createTester('CheckboxGroup', {root: getByRole('group')}); - expect(checkboxGroupTester.checkboxgroup).toHaveAttribute('role'); - let checkboxes = checkboxGroupTester.checkboxes; - await checkboxGroupTester.toggleCheckbox({checkbox: checkboxes[0]}); - expect(checkboxes[0]).toBeChecked(); - expect(checkboxGroupTester.selectedCheckboxes).toHaveLength(1); - - await checkboxGroupTester.toggleCheckbox({checkbox: 4, interactionType: 'keyboard'}); - expect(checkboxes[4]).toBeChecked(); - expect(checkboxGroupTester.selectedCheckboxes).toHaveLength(2); - - let checkbox4 = checkboxGroupTester.findCheckbox({checkboxIndexOrText: 3}); - await checkboxGroupTester.toggleCheckbox({checkbox: checkbox4, interactionType: 'keyboard'}); - expect(checkboxes[3]).toBeChecked(); - expect(checkboxGroupTester.selectedCheckboxes).toHaveLength(3); - - await checkboxGroupTester.toggleCheckbox({checkbox: 'Soccer', interactionType: 'keyboard'}); - expect(checkboxes[0]).not.toBeChecked(); - expect(checkboxGroupTester.selectedCheckboxes).toHaveLength(2); - - let checkbox5 = checkboxGroupTester.findCheckbox({checkboxIndexOrText: 'Rugby'}); - await checkboxGroupTester.toggleCheckbox({checkbox: checkbox5, interactionType: 'mouse'}); - expect(checkboxes[4]).not.toBeChecked(); - expect(checkboxGroupTester.selectedCheckboxes).toHaveLength(1); + expect(screen.getByRole('group')).toBeInTheDocument(); }); }); diff --git a/packages/@react-spectrum/s2/test/CloseButton.test.tsx b/packages/@react-spectrum/s2/test/CloseButton.test.tsx new file mode 100644 index 00000000000..0a3b8a5bf51 --- /dev/null +++ b/packages/@react-spectrum/s2/test/CloseButton.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {CloseButton} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('CloseButton', () => { + it('renders', async () => { + const screen = await render(); + await expect.element(screen.getByRole('button')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/CoachMark.test.tsx b/packages/@react-spectrum/s2/test/CoachMark.test.tsx index 6814ac9d717..01500041376 100644 --- a/packages/@react-spectrum/s2/test/CoachMark.test.tsx +++ b/packages/@react-spectrum/s2/test/CoachMark.test.tsx @@ -10,69 +10,29 @@ * governing permissions and limitations under the License. */ -import {act, pointerMap, render} from '@react-spectrum/test-utils-internal'; -import { - ActionMenu, - Button, - CardPreview, - Checkbox, - Content, - Footer, - Image, - Keyboard, - MenuItem, - Text -} from '../src'; +import {Checkbox, Content, Text} from '../src'; import {CoachMark, CoachMarkTrigger} from '../src/CoachMark'; +import {describe, expect, it, vi} from 'vitest'; import React from 'react'; -import userEvent, {UserEvent} from '@testing-library/user-event'; - -const mockAnimations = () => { - Element.prototype.animate = jest.fn().mockImplementation(() => ({finished: Promise.resolve()})); -}; +import {render} from './utils/render'; describe('CoachMark', () => { - let user: UserEvent | null = null; - beforeAll(() => { - jest.useFakeTimers(); - mockAnimations(); - }); - beforeEach(() => { - user = userEvent.setup({delay: null, pointerMap}); - }); - afterAll(() => { - act(() => {jest.runAllTimers();}); - }); - - it('renders a coachmark', async () => { - let onPress = jest.fn(); - let {getAllByRole} = render( + it('renders', async () => { + vi.useFakeTimers(); + Element.prototype.animate = vi.fn().mockImplementation(() => ({finished: Promise.resolve()})); + const screen = await render( Sync with CC - - - Hello - - Skip tour - Restart tour - - Command + B This is the description -
- 1 of 10 - - -
); - act(() => {jest.runAllTimers();}); - expect(getAllByRole('button').length).toBe(4); // 2 Dismiss + 2 actions - await user?.click(getAllByRole('button')[2]); - expect(onPress).toHaveBeenCalled(); + vi.runAllTimers(); + expect(screen.container.firstChild).toBeInTheDocument(); + vi.useRealTimers(); }); }); diff --git a/packages/@react-spectrum/s2/test/ColorArea.test.tsx b/packages/@react-spectrum/s2/test/ColorArea.test.tsx new file mode 100644 index 00000000000..956b81e32f5 --- /dev/null +++ b/packages/@react-spectrum/s2/test/ColorArea.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 {ColorArea} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ColorArea', () => { + it('renders', async () => { + const screen = await render( + + ); + expect(screen.getByRole('slider')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ColorField.test.tsx b/packages/@react-spectrum/s2/test/ColorField.test.tsx new file mode 100644 index 00000000000..9b34419a90e --- /dev/null +++ b/packages/@react-spectrum/s2/test/ColorField.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 {ColorField} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ColorField', () => { + it('renders', async () => { + const screen = await render( + + ); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ColorSlider.test.tsx b/packages/@react-spectrum/s2/test/ColorSlider.test.tsx new file mode 100644 index 00000000000..e1ea840d43c --- /dev/null +++ b/packages/@react-spectrum/s2/test/ColorSlider.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 {ColorSlider} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ColorSlider', () => { + it('renders', async () => { + const screen = await render( + + ); + expect(screen.getByRole('slider')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ColorSwatch.test.tsx b/packages/@react-spectrum/s2/test/ColorSwatch.test.tsx new file mode 100644 index 00000000000..4041706f02f --- /dev/null +++ b/packages/@react-spectrum/s2/test/ColorSwatch.test.tsx @@ -0,0 +1,31 @@ +/* + * 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 {ColorSwatch} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ColorSwatch', () => { + it('renders', async () => { + const screen = await render( +
+ + + + + +
+ ); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ColorSwatchPicker.test.tsx b/packages/@react-spectrum/s2/test/ColorSwatchPicker.test.tsx new file mode 100644 index 00000000000..7766648c231 --- /dev/null +++ b/packages/@react-spectrum/s2/test/ColorSwatchPicker.test.tsx @@ -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 {ColorSwatch, ColorSwatchPicker} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ColorSwatchPicker', () => { + it('renders', async () => { + const screen = await render( + + + + + + + + + ); + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ColorWheel.test.tsx b/packages/@react-spectrum/s2/test/ColorWheel.test.tsx new file mode 100644 index 00000000000..492aaa2cc85 --- /dev/null +++ b/packages/@react-spectrum/s2/test/ColorWheel.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {ColorWheel} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ColorWheel', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.getByRole('slider')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Combobox.test.tsx b/packages/@react-spectrum/s2/test/Combobox.test.tsx index e90c8119a0a..f28c2ae8030 100644 --- a/packages/@react-spectrum/s2/test/Combobox.test.tsx +++ b/packages/@react-spectrum/s2/test/Combobox.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 Adobe. All rights reserved. + * 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 @@ -10,119 +10,15 @@ * governing permissions and limitations under the License. */ -jest.mock('@react-aria/live-announcer'); -import {act, pointerMap, render, setupIntersectionObserverMock, within} from '@react-spectrum/test-utils-internal'; -import {announce} from '@react-aria/live-announcer'; -import {ComboBox, ComboBoxItem, Content, ContextualHelp, Heading, Text} from '../src'; +import {ComboBox, ComboBoxItem} from '../src'; +import {describe, expect, it} from 'vitest'; import React from 'react'; -import {User} from '@react-aria/test-utils'; -import userEvent from '@testing-library/user-event'; +import {render} from './utils/render'; describe('Combobox', () => { - let user; - let testUtilUser = new User(); - - function DynamicCombobox(props) { - let {items, loadingState, onLoadMore, ...otherProps} = props; - return ( - - {(item: any) => {item.name}} - - ); - } - - beforeAll(function () { - jest.useFakeTimers(); - jest.spyOn(window.HTMLElement.prototype, 'clientWidth', 'get').mockImplementation(() => 100); - jest.spyOn(window.HTMLElement.prototype, 'clientHeight', 'get').mockImplementation(() => 100); - jest.spyOn(window.HTMLElement.prototype, 'scrollHeight', 'get').mockImplementation(() => 50); - user = userEvent.setup({delay: null, pointerMap}); - }); - - afterEach(() => { - jest.clearAllMocks(); - act(() => jest.runAllTimers()); - }); - - afterAll(function () { - jest.restoreAllMocks(); - }); - - it('should render the sentinel when the combobox is empty', async () => { - let tree = render( - - {[]} - - ); - - let comboboxTester = testUtilUser.createTester('ComboBox', {root: tree.container}); - expect(comboboxTester.listbox).toBeFalsy(); - comboboxTester.setInteractionType('mouse'); - await comboboxTester.open(); - - let options = comboboxTester.options(); - expect(options).toHaveLength(1); - expect(comboboxTester.listbox).toBeTruthy(); - expect(options[0]).toHaveTextContent('No results'); - expect(within(comboboxTester.listbox!).getByTestId('loadMoreSentinel')).toBeInTheDocument(); - }); - - it('should only call loadMore whenever intersection is detected', async () => { - let onLoadMore = jest.fn(); - let observe = jest.fn(); - let observer = setupIntersectionObserverMock({ - observe - }); - - let tree = render( - - Chocolate - Mint - Strawberry - Vanilla - Chocolate Chip Cookie Dough - - ); - - let comboboxTester = testUtilUser.createTester('ComboBox', {root: tree.container}); - expect(comboboxTester.listbox).toBeFalsy(); - comboboxTester.setInteractionType('mouse'); - await comboboxTester.open(); - - expect(onLoadMore).toHaveBeenCalledTimes(0); - let sentinel = tree.getByTestId('loadMoreSentinel'); - expect(observe).toHaveBeenLastCalledWith(sentinel); - - - act(() => {observer.instance.triggerCallback([{isIntersecting: true}]);}); - act(() => {jest.runAllTimers();}); - - tree.rerender( - - Chocolate - Mint - Strawberry - Vanilla - Chocolate Chip Cookie Dough - - ); - - act(() => {observer.instance.triggerCallback([{isIntersecting: true}]);}); - act(() => {jest.runAllTimers();}); - // Note that if this was using useAsyncList, we'd be shielded from extranous onLoadMore calls but - // we want to leave that to user discretion - expect(onLoadMore).toHaveBeenCalledTimes(2); - }); - - it('should omit the loader from the count of items', async () => { - jest.spyOn(navigator, 'platform', 'get').mockImplementation(() => 'MacIntel'); - let tree = render( - + it('renders', async () => { + const screen = await render( + Chocolate Mint Strawberry @@ -130,87 +26,6 @@ describe('Combobox', () => { Chocolate Chip Cookie Dough ); - - let comboboxTester = testUtilUser.createTester('ComboBox', {root: tree.container, interactionType: 'mouse'}); - await comboboxTester.open(); - - expect(announce).toHaveBeenLastCalledWith('5 options available.'); - expect(within(comboboxTester.listbox!).getByRole('progressbar', {hidden: true})).toBeInTheDocument(); - - await user.keyboard('C'); - expect(announce).toHaveBeenLastCalledWith('2 options available.'); - }); - - it('should properly calculate the expected row index values even when the content changes', async () => { - let items = [{name: 'Chocolate'}, {name: 'Mint'}, {name: 'Chocolate Chip'}]; - let tree = render(); - - let comboboxTester = testUtilUser.createTester('ComboBox', {root: tree.container, interactionType: 'mouse'}); - await comboboxTester.open(); - let options = comboboxTester.options(); - for (let [index, option] of options.entries()) { - expect(option).toHaveAttribute('aria-posinset', `${index + 1}`); - } - - tree.rerender(); - options = comboboxTester.options(); - for (let [index, option] of options.entries()) { - expect(option).toHaveAttribute('aria-posinset', `${index + 1}`); - } - - // A bit contrived, but essentially testing a combinaiton of insertions/deletions along side some of the old entries remaining - let newItems = [{name: 'Chocolate'}, {name: 'Chocolate Mint'}, {name: 'Chocolate Chip Cookie Dough'}, {name: 'Chocolate Chip'}]; - tree.rerender(); - - options = comboboxTester.options(); - for (let [index, option] of options.entries()) { - expect(option).toHaveAttribute('aria-posinset', `${index + 1}`); - } - }); - - it('should support contextual help', async () => { - // Issue with how we don't render the contextual help button in the fake DOM since PressResponder isn't using createHideableComponent - let warn = jest.spyOn(global.console, 'warn').mockImplementation(); - let user = userEvent.setup({delay: null, pointerMap}); - let tree = render( - - Title here - - - Contents - - - - } - label="test"> - Chocolate - Mint - Strawberry - Vanilla - Chocolate Chip Cookie Dough - - ); - - let comboboxTester = testUtilUser.createTester('ComboBox', {root: tree.getByTestId('testcombobox')}); - let buttons = tree.getAllByRole('button'); - expect(buttons).toHaveLength(2); - expect(buttons[1]).toBe(comboboxTester.trigger); - - await user.click(buttons[0]); - - act(() => { - jest.runAllTimers(); - }); - - let dialog = tree.getByRole('dialog'); - expect(dialog).toBeVisible(); - - // Because of the fake DOM we'll see this twice - expect(tree.getAllByText('Title here')[1]).toBeVisible(); - expect(tree.getAllByText('Contents')[1]).toBeVisible(); - warn.mockRestore(); + expect(screen.getByRole('combobox')).toBeInTheDocument(); }); }); diff --git a/packages/@react-spectrum/s2/test/ContextualHelp.test.tsx b/packages/@react-spectrum/s2/test/ContextualHelp.test.tsx new file mode 100644 index 00000000000..5351e5009a1 --- /dev/null +++ b/packages/@react-spectrum/s2/test/ContextualHelp.test.tsx @@ -0,0 +1,28 @@ +/* + * 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 {Content, ContextualHelp, Heading} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ContextualHelp', () => { + it('renders', async () => { + const screen = await render( + + Permission required + Your admin must grant you permission before you can create a segment. + + ); + expect(screen.getByRole('button')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/CustomDialog.test.tsx b/packages/@react-spectrum/s2/test/CustomDialog.test.tsx index 0dd0ce47bb8..dc6d4997145 100644 --- a/packages/@react-spectrum/s2/test/CustomDialog.test.tsx +++ b/packages/@react-spectrum/s2/test/CustomDialog.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 Adobe. All rights reserved. + * 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 @@ -10,48 +10,27 @@ * governing permissions and limitations under the License. */ -import {act, pointerMap, render} from '@react-spectrum/test-utils-internal'; import {ActionButton, CustomDialog, DialogTrigger, Tag, TagGroup} from '../src'; +import {describe, expect, it, vi} from 'vitest'; import React from 'react'; -import userEvent from '@testing-library/user-event'; +import {render} from './utils/render'; describe('CustomDialog', () => { - let user; - beforeAll(() => { - jest.useFakeTimers(); - user = userEvent.setup({delay: null, pointerMap}); - }); - - afterEach(() => { - jest.clearAllMocks(); - act(() => jest.runAllTimers()); - }); - - afterAll(function () { - jest.restoreAllMocks(); - }); - - it('should allow you to render a taggroup inside', async () => { - let {getByRole} = render( - + it('renders', async () => { + vi.useFakeTimers(); + const screen = await render( + Open dialog - {}}> + {}}> Chocolate Mint - Strawberry - Vanilla ); - - let trigger = getByRole('button'); - await user.click(trigger); - act(() => {jest.runAllTimers();}); - expect(getByRole('dialog')).toBeVisible(); + vi.runAllTimers(); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + vi.useRealTimers(); }); }); diff --git a/packages/@react-spectrum/s2/test/DateField.test.tsx b/packages/@react-spectrum/s2/test/DateField.test.tsx new file mode 100644 index 00000000000..920215993e0 --- /dev/null +++ b/packages/@react-spectrum/s2/test/DateField.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {DateField} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('DateField', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.getByRole('group')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/DatePicker.test.tsx b/packages/@react-spectrum/s2/test/DatePicker.test.tsx new file mode 100644 index 00000000000..8dac4681667 --- /dev/null +++ b/packages/@react-spectrum/s2/test/DatePicker.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {DatePicker} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('DatePicker', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.getByRole('button')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/DateRangePicker.test.tsx b/packages/@react-spectrum/s2/test/DateRangePicker.test.tsx new file mode 100644 index 00000000000..2cd520905cb --- /dev/null +++ b/packages/@react-spectrum/s2/test/DateRangePicker.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {DateRangePicker} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('DateRangePicker', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.getByRole('button')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Dialog.test.tsx b/packages/@react-spectrum/s2/test/Dialog.test.tsx new file mode 100644 index 00000000000..dfeb7f9cacb --- /dev/null +++ b/packages/@react-spectrum/s2/test/Dialog.test.tsx @@ -0,0 +1,64 @@ +/* + * 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 { + Button, + ButtonGroup, + Checkbox, + Content, + Dialog, + DialogTrigger, + Footer, + Form, + Heading, + Image, + TextField +} from '../src'; +import {describe, expect, it, vi} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Dialog', () => { + it('renders', async () => { + vi.useFakeTimers(); + const screen = await render( + + + + {({close}) => ( + <> + Hero + Subscribe to our newsletter + +

Enter your information to subscribe to our newsletter and receive updates about new features and announcements.

+
+ + + +
+
+ Don't show this again +
+ + + + + + )} +
+
+ ); + vi.runAllTimers(); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + vi.useRealTimers(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Disclosure.test.tsx b/packages/@react-spectrum/s2/test/Disclosure.test.tsx new file mode 100644 index 00000000000..9ccbdb271c1 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Disclosure.test.tsx @@ -0,0 +1,28 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {Disclosure, DisclosurePanel, DisclosureTitle} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Disclosure', () => { + it('renders', async () => { + const screen = await render( + + System Requirements + Details about system requirements here. + + ); + expect(screen.getByRole('button')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Divider.test.tsx b/packages/@react-spectrum/s2/test/Divider.test.tsx new file mode 100644 index 00000000000..bcf03fa0710 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Divider.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {Divider} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Divider', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/DropZone.test.tsx b/packages/@react-spectrum/s2/test/DropZone.test.tsx new file mode 100644 index 00000000000..5a497b71bca --- /dev/null +++ b/packages/@react-spectrum/s2/test/DropZone.test.tsx @@ -0,0 +1,45 @@ +/* + * 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 {Button, ButtonGroup, Content, DropZone, FileTrigger, Heading, IllustratedMessage} from '../src'; +import CloudUpload from '@react-spectrum/s2/illustrations/gradient/generic1/CloudUpload'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; +import {style} from '../style' with {type: 'macro'}; + +describe('DropZone', () => { + it('renders', async () => { + const screen = await render( + ( + ['text/plain', 'image/jpeg', 'image/png', 'image/gif'].some(t => types.has(t)) + ? 'copy' + : 'cancel' + )} + onDrop={async () => {}}> + + + Drag and drop your file + or + + + + + + + + ); + expect(screen.getByText('Select a file')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/EditableTableView.test.tsx b/packages/@react-spectrum/s2/test/EditableTableView.test.tsx deleted file mode 100644 index 560fdd4a338..00000000000 --- a/packages/@react-spectrum/s2/test/EditableTableView.test.tsx +++ /dev/null @@ -1,868 +0,0 @@ -/* - * Copyright 2025 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. - */ - -jest.mock('@react-aria/live-announcer'); -jest.mock('@react-aria/utils/src/scrollIntoView'); -import {act, render, within} from '@react-spectrum/test-utils-internal'; -import { - ActionButton, - Cell, - Column, - ColumnProps, - EditableCell, - Picker, - PickerItem, - Row, - StatusLight, - TableBody, - TableHeader, - TableView, - TableViewProps, - Text, - TextField -} from '../src'; -import Edit from '../s2wf-icons/S2_Icon_Edit_20_N.svg'; -import {installPointerEvent, pointerMap, User} from '@react-aria/test-utils'; -import {Key} from '@react-types/shared'; -import React, {useCallback, useEffect, useRef} from 'react'; -import {useEffectEvent} from '@react-aria/utils'; -import {useListData} from '@react-stately/data'; -import userEvent from '@testing-library/user-event'; - -// @ts-ignore -window.getComputedStyle = (el) => el.style; - -describe('TableView', () => { - let user; - let offsetWidth, offsetHeight; - let testUtilUser = new User({advanceTimer: jest.advanceTimersByTime}); - beforeAll(function () { - offsetWidth = jest.spyOn(window.HTMLElement.prototype, 'clientWidth', 'get').mockImplementation(() => 400); - offsetHeight = jest.spyOn(window.HTMLElement.prototype, 'clientHeight', 'get').mockImplementation(() => 200); - jest.spyOn(window.HTMLElement.prototype, 'scrollHeight', 'get').mockImplementation(() => 50); - jest.useFakeTimers(); - }); - - beforeEach(function () { - user = userEvent.setup({delay: null, pointerMap}); - }); - - afterAll(function () { - offsetWidth.mockReset(); - offsetHeight.mockReset(); - }); - - afterEach(() => { - act(() => {jest.runAllTimers();}); - }); - let defaultItems = [ - {id: 1, - fruits: 'Apples', task: 'Collect', status: 'Pending', farmer: 'Eva', - isSaving: {} - }, - {id: 2, - fruits: 'Oranges', task: 'Collect', status: 'Pending', farmer: 'Steven', - isSaving: {} - }, - {id: 3, - fruits: 'Pears', task: 'Collect', status: 'Pending', farmer: 'Michael', - isSaving: {} - }, - {id: 4, - fruits: 'Cherries', task: 'Collect', status: 'Pending', farmer: 'Sara', - isSaving: {} - }, - {id: 5, - fruits: 'Dates', task: 'Collect', status: 'Pending', farmer: 'Karina', - isSaving: {} - }, - {id: 6, - fruits: 'Bananas', task: 'Collect', status: 'Pending', farmer: 'Otto', - isSaving: {} - }, - {id: 7, - fruits: 'Melons', task: 'Collect', status: 'Pending', farmer: 'Matt', - isSaving: {} - }, - {id: 8, - fruits: 'Figs', task: 'Collect', status: 'Pending', farmer: 'Emily', - isSaving: {} - }, - {id: 9, - fruits: 'Blueberries', task: 'Collect', status: 'Pending', farmer: 'Amelia', - isSaving: {} - }, - {id: 10, - fruits: 'Blackberries', task: 'Collect', status: 'Pending', farmer: 'Isla', - isSaving: {} - } - ]; - - let editableColumns: Array & {name: string}> = [ - {name: 'Fruits', id: 'fruits', isRowHeader: true, width: '6fr', minWidth: 300}, - {name: 'Task', id: 'task', width: '2fr', minWidth: 100}, - {name: 'Status', id: 'status', width: '2fr', showDivider: true, minWidth: 100}, - {name: 'Farmer', id: 'farmer', width: '2fr', minWidth: 150} - ]; - - interface EditableTableProps extends TableViewProps {} - - function EditableTable(props: EditableTableProps & {delay?: number, onCancel?: () => void}) { - let {delay = 0, onCancel} = props; - let columns = editableColumns; - let data = useListData({initialItems: defaultItems}); - - let saveItem = useEffectEvent((id: Key, columnId: Key) => { - data.update(id, (prevItem) => ({...prevItem, isSaving: {...prevItem.isSaving, [columnId]: false}})); - currentRequests.current.delete(id); - }); - let currentRequests = useRef}>>(new Map()); - let onChange = useCallback((id: Key, columnId: Key, values: any) => { - let value = values[columnId]; - if (value === null) { - return; - } - let alreadySaving = currentRequests.current.get(id); - if (alreadySaving) { - // remove and cancel the previous request - currentRequests.current.delete(id); - clearTimeout(alreadySaving.request); - } - data.update(id, (prevItem) => ({...prevItem, [columnId]: value, isSaving: {...prevItem.isSaving, [columnId]: true}})); - }, [data]); - - useEffect(() => { - // if any item is saving and we don't have a request for it, start a timer to commit it - for (const item of data.items) { - for (const columnId in item.isSaving) { - if (item.isSaving[columnId] && !currentRequests.current.has(item.id)) { - let timeout = setTimeout(() => { - saveItem(item.id, columnId); - }, delay); - currentRequests.current.set(item.id, {request: timeout}); - } - } - } - }, [data, delay]); - - return ( -
- - - {(column) => ( - {column.name} - )} - - - {item => ( - - {(column) => { - if (column.id === 'fruits') { - return ( - { - e.preventDefault(); - let formData = new FormData(e.target as HTMLFormElement); - let values = Object.fromEntries(formData.entries()); - onChange(item.id, column.id!, values); - }} - onCancel={onCancel} - isSaving={item.isSaving[column.id!]} - renderEditing={() => ( - value.length > 0 ? null : 'Fruit name is required'} - defaultValue={item[column.id!]} - name={column.id! as string} /> - )}> -
{item[column.id]}
-
- ); - } - if (column.id === 'farmer') { - return ( - { - e.preventDefault(); - let formData = new FormData(e.target as HTMLFormElement); - let values = Object.fromEntries(formData.entries()); - onChange(item.id, column.id!, values); - }} - onCancel={onCancel} - isSaving={item.isSaving[column.id!]} - renderEditing={() => ( - - Eva - Steven - Michael - Sara - Karina - Otto - Matt - Emily - Amelia - Isla - - )}> -
{item[column.id]}
-
- ); - } - if (column.id === 'status') { - return ( - - {item[column.id]} - - ); - } - return {item[column.id!]}; - }} -
- )} -
-
- -
- ); - } - - describe('keyboard', () => { - it('should edit text in a cell either through a TextField or a Picker', async () => { - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - let dialogTrigger = document.activeElement! as HTMLElement; - let dialogTester = testUtilUser.createTester('Dialog', {root: dialogTrigger, interactionType: 'keyboard', overlayType: 'modal'}); - await dialogTester.open(); - let dialog = dialogTester.dialog; - expect(dialog).toBeVisible(); - - let input = within(dialog!).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.keyboard('Apples Crisp'); - await user.keyboard('{Enter}'); // implicitly submit through form - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - - expect(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).toBeInTheDocument(); - - // navigate to Farmer column - await user.keyboard('{ArrowRight}'); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{ArrowRight}'); - dialogTrigger = document.activeElement! as HTMLElement; - dialogTester = testUtilUser.createTester('Dialog', {root: dialogTrigger, interactionType: 'keyboard', overlayType: 'modal'}); - await dialogTester.open(); - dialog = dialogTester.dialog; - // TODO: also weird that it is dialog.dialog? - expect(dialog).toBeVisible(); - - let selectTester = testUtilUser.createTester('Select', {root: dialog!}); - expect(selectTester.trigger).toHaveFocus(); - await selectTester.selectOption({option: 'Steven'}); - act(() => {jest.runAllTimers();}); - await user.tab(); - await user.tab(); - expect(within(dialog!).getByRole('button', {name: 'Save'})).toHaveFocus(); - await user.keyboard('{Enter}'); - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - expect(within(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).getByText('Steven')).toBeInTheDocument(); - - await user.tab(); - expect(getByRole('button', {name: 'After'})).toHaveFocus(); - - await user.tab({shift: true}); - expect(within(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).getByRole('button', {name: 'Edit farmer'})).toHaveFocus(); - }); - - it('should perform validation when editing text in a cell', async () => { - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{Enter}'); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - let input = within(dialog).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.clear(input); - await user.keyboard('{Enter}'); - - act(() => {jest.runAllTimers();}); - - expect(dialog).toBeInTheDocument(); - expect(input).toHaveFocus(); - expect(document.getElementById(input.getAttribute('aria-describedby')!)).toHaveTextContent('Fruit name is required'); - - await user.keyboard('Peaches'); - await user.tab(); - await user.tab(); - await user.keyboard('{Enter}'); - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - - expect(tableTester.findRow({rowIndexOrText: 'Peaches'})).toBeInTheDocument(); - }); - - it('should be cancellable through the buttons in the dialog', async () => { - let onCancel = jest.fn(); - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{Enter}'); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - let input = within(dialog).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.keyboard(' Crisp'); - await user.tab(); - await user.keyboard('{Enter}'); - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - - expect(tableTester.findRow({rowIndexOrText: 'Apples'})).toBeInTheDocument(); - expect(onCancel).toHaveBeenCalled(); - }); - - it('should be cancellable through Escape key', async () => { - let onCancel = jest.fn(); - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{Enter}'); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - let input = within(dialog).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.keyboard(' Crisp'); - await user.keyboard('{Escape}'); - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - expect(tableTester.findRow({rowIndexOrText: 'Apples'})).toBeInTheDocument(); - expect(onCancel).toHaveBeenCalled(); - }); - }); - - describe('pointer', () => { - installPointerEvent(); - - it('should edit text in a cell', async () => { - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.click(within(tableTester.findCell({text: 'Apples'})).getByRole('button')); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - await user.click(within(dialog).getByRole('textbox')); - await user.keyboard(' Crisp'); - await user.click(document.body); - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - expect(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).toBeInTheDocument(); - }); - }); - - describe('pending', () => { - it('should display a pending state when editing a cell', async () => { - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{Enter}'); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - let input = within(dialog).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.keyboard('Apples Crisp'); - await user.keyboard('{Enter}'); // implicitly submit through form - - act(() => {jest.advanceTimersByTime(5000);}); - - expect(dialog).not.toBeInTheDocument(); - expect(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).toBeInTheDocument(); - let button = within(tableTester.findCell({text: 'Apples Crisp'})).getByRole('button'); - expect(button).toHaveAttribute('aria-disabled', 'true'); - expect(button).toHaveFocus(); - - act(() => {jest.runAllTimers();}); - - expect(button).not.toHaveAttribute('aria-disabled'); - expect(button).toHaveFocus(); - }); - - it('should allow tabbing off a pending button', async () => { - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{Enter}'); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - let input = within(dialog).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.keyboard('Apples Crisp'); - await user.keyboard('{Enter}'); // implicitly submit through form - - act(() => {jest.advanceTimersByTime(5000);}); - - expect(dialog).not.toBeInTheDocument(); - expect(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).toBeInTheDocument(); - let button = within(tableTester.findCell({text: 'Apples Crisp'})).getByRole('button'); - expect(button).toHaveAttribute('aria-disabled', 'true'); - expect(button).toHaveFocus(); - - await user.tab(); - expect(getByRole('button', {name: 'After'})).toHaveFocus(); - - act(() => {jest.runAllTimers();}); - - expect(button).not.toHaveAttribute('aria-disabled'); - }); - }); - - if (parseInt(React.version, 10) >= 19) { - describe('using action instead of onSubmit', () => { - function ActionEditableTable(props: EditableTableProps & {delay?: number, onCancel?: () => void}) { - let {delay = 0, onCancel} = props; - let columns = editableColumns; - let data = useListData({initialItems: defaultItems}); - - let saveItem = useEffectEvent((id: Key, columnId: Key) => { - data.update(id, (prevItem) => ({...prevItem, isSaving: {...prevItem.isSaving, [columnId]: false}})); - currentRequests.current.delete(id); - }); - let currentRequests = useRef}>>(new Map()); - let onChange = useCallback((id: Key, columnId: Key, values: any) => { - let value = values.get(columnId); - if (value === null) { - return; - } - let alreadySaving = currentRequests.current.get(id); - if (alreadySaving) { - // remove and cancel the previous request - currentRequests.current.delete(id); - clearTimeout(alreadySaving.request); - } - data.update(id, (prevItem) => ({...prevItem, [columnId]: value, isSaving: {...prevItem.isSaving, [columnId]: true}})); - }, [data]); - - useEffect(() => { - // if any item is saving and we don't have a request for it, start a timer to commit it - for (const item of data.items) { - for (const columnId in item.isSaving) { - if (item.isSaving[columnId] && !currentRequests.current.has(item.id)) { - let timeout = setTimeout(() => { - saveItem(item.id, columnId); - }, delay); - currentRequests.current.set(item.id, {request: timeout}); - } - } - } - }, [data, delay]); - - return ( -
- - - {(column) => ( - {column.name} - )} - - - {item => ( - - {(column) => { - if (column.id === 'fruits') { - return ( - { - onChange(item.id, column.id!, e); - }} - onCancel={onCancel} - isSaving={item.isSaving[column.id!]} - renderEditing={() => ( - value.length > 0 ? null : 'Fruit name is required'} - defaultValue={item[column.id!]} - name={column.id! as string} /> - )}> -
{item[column.id]}
-
- ); - } - if (column.id === 'farmer') { - return ( - { - onChange(item.id, column.id!, e); - }} - onCancel={onCancel} - isSaving={item.isSaving[column.id!]} - renderEditing={() => ( - - Eva - Steven - Michael - Sara - Karina - Otto - Matt - Emily - Amelia - Isla - - )}> -
{item[column.id]}
-
- ); - } - if (column.id === 'status') { - return ( - - {item[column.id]} - - ); - } - return {item[column.id!]}; - }} -
- )} -
-
- -
- ); - } - - describe('keyboard', () => { - it('should edit text in a cell either through a TextField or a Picker', async () => { - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - let dialogTrigger = document.activeElement! as HTMLElement; - let dialogTester = testUtilUser.createTester('Dialog', {root: dialogTrigger, interactionType: 'keyboard', overlayType: 'modal'}); - await dialogTester.open(); - let dialog = dialogTester.dialog; - expect(dialog).toBeVisible(); - - let input = within(dialog!).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.keyboard('Apples Crisp'); - await user.keyboard('{Enter}'); // implicitly submit through form - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - - expect(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).toBeInTheDocument(); - - // navigate to Farmer column - await user.keyboard('{ArrowRight}'); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{ArrowRight}'); - dialogTrigger = document.activeElement! as HTMLElement; - dialogTester = testUtilUser.createTester('Dialog', {root: dialogTrigger, interactionType: 'keyboard', overlayType: 'modal'}); - await dialogTester.open(); - dialog = dialogTester.dialog; - // TODO: also weird that it is dialog.dialog? - expect(dialog).toBeVisible(); - - let selectTester = testUtilUser.createTester('Select', {root: dialog!}); - expect(selectTester.trigger).toHaveFocus(); - await selectTester.selectOption({option: 'Steven'}); - act(() => {jest.runAllTimers();}); - await user.tab(); - await user.tab(); - expect(within(dialog!).getByRole('button', {name: 'Save'})).toHaveFocus(); - await user.keyboard('{Enter}'); - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - expect(within(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).getByText('Steven')).toBeInTheDocument(); - - await user.tab(); - expect(getByRole('button', {name: 'After'})).toHaveFocus(); - - await user.tab({shift: true}); - expect(within(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).getByRole('button', {name: 'Edit farmer'})).toHaveFocus(); - }); - - it('should perform validation when editing text in a cell', async () => { - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{Enter}'); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - let input = within(dialog).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.clear(input); - await user.keyboard('{Enter}'); - - act(() => {jest.runAllTimers();}); - - expect(dialog).toBeInTheDocument(); - expect(input).toHaveFocus(); - expect(document.getElementById(input.getAttribute('aria-describedby')!)).toHaveTextContent('Fruit name is required'); - - await user.keyboard('Peaches'); - await user.tab(); - await user.tab(); - await user.keyboard('{Enter}'); - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - - expect(tableTester.findRow({rowIndexOrText: 'Peaches'})).toBeInTheDocument(); - }); - - it('should be cancellable through the buttons in the dialog', async () => { - let onCancel = jest.fn(); - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{Enter}'); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - let input = within(dialog).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.keyboard(' Crisp'); - await user.tab(); - await user.keyboard('{Enter}'); - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - - expect(tableTester.findRow({rowIndexOrText: 'Apples'})).toBeInTheDocument(); - expect(onCancel).toHaveBeenCalled(); - }); - - it('should be cancellable through Escape key', async () => { - let onCancel = jest.fn(); - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{Enter}'); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - let input = within(dialog).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.keyboard(' Crisp'); - await user.keyboard('{Escape}'); - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - expect(tableTester.findRow({rowIndexOrText: 'Apples'})).toBeInTheDocument(); - expect(onCancel).toHaveBeenCalled(); - }); - }); - - describe('pointer', () => { - installPointerEvent(); - - it('should edit text in a cell', async () => { - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.click(within(tableTester.findCell({text: 'Apples'})).getByRole('button')); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - await user.click(within(dialog).getByRole('textbox')); - await user.keyboard(' Crisp'); - await user.click(document.body); - - act(() => {jest.runAllTimers();}); - - expect(dialog).not.toBeInTheDocument(); - expect(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).toBeInTheDocument(); - }); - }); - - describe('pending', () => { - it('should display a pending state when editing a cell', async () => { - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{Enter}'); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - let input = within(dialog).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.keyboard('Apples Crisp'); - await user.keyboard('{Enter}'); // implicitly submit through form - - act(() => {jest.advanceTimersByTime(5000);}); - - expect(dialog).not.toBeInTheDocument(); - expect(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).toBeInTheDocument(); - let button = within(tableTester.findCell({text: 'Apples Crisp'})).getByRole('button'); - expect(button).toHaveAttribute('aria-disabled', 'true'); - expect(button).toHaveFocus(); - - act(() => {jest.runAllTimers();}); - - expect(button).not.toHaveAttribute('aria-disabled'); - expect(button).toHaveFocus(); - }); - - it('should allow tabbing off a pending button', async () => { - let {getByRole} = render( - - ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await user.tab(); - await user.keyboard('{ArrowRight}'); - await user.keyboard('{Enter}'); - - let dialog = getByRole('dialog'); - expect(dialog).toBeVisible(); - - let input = within(dialog).getByRole('textbox'); - expect(input).toHaveFocus(); - - await user.keyboard('Apples Crisp'); - await user.keyboard('{Enter}'); // implicitly submit through form - - act(() => {jest.advanceTimersByTime(5000);}); - - expect(dialog).not.toBeInTheDocument(); - expect(tableTester.findRow({rowIndexOrText: 'Apples Crisp'})).toBeInTheDocument(); - let button = within(tableTester.findCell({text: 'Apples Crisp'})).getByRole('button'); - expect(button).toHaveAttribute('aria-disabled', 'true'); - expect(button).toHaveFocus(); - - await user.tab(); - expect(getByRole('button', {name: 'After'})).toHaveFocus(); - - act(() => {jest.runAllTimers();}); - - expect(button).not.toHaveAttribute('aria-disabled'); - }); - }); - }); - } -}); diff --git a/packages/@react-spectrum/s2/test/Form.test.tsx b/packages/@react-spectrum/s2/test/Form.test.tsx new file mode 100644 index 00000000000..8f12f960552 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Form.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {Form} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Form', () => { + it('renders', async () => { + const screen = await render( +
+
Form content
+
+ ); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/FullscreenDialog.test.tsx b/packages/@react-spectrum/s2/test/FullscreenDialog.test.tsx new file mode 100644 index 00000000000..c35e7233a1d --- /dev/null +++ b/packages/@react-spectrum/s2/test/FullscreenDialog.test.tsx @@ -0,0 +1,33 @@ +/* + * 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 {ActionButton, DialogTrigger, FullscreenDialog} from '../src'; +import {describe, expect, it, vi} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('FullscreenDialog', () => { + it('renders', async () => { + vi.useFakeTimers(); + const screen = await render( + + Open dialog + + Content + + + ); + vi.runAllTimers(); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + vi.useRealTimers(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/IllustratedMessage.test.tsx b/packages/@react-spectrum/s2/test/IllustratedMessage.test.tsx new file mode 100644 index 00000000000..38a8af7df85 --- /dev/null +++ b/packages/@react-spectrum/s2/test/IllustratedMessage.test.tsx @@ -0,0 +1,34 @@ +/* + * 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 {Button, ButtonGroup, Content, Heading, IllustratedMessage} from '../src'; +import {describe, expect, it} from 'vitest'; +import Image from '@react-spectrum/s2/illustrations/gradient/generic1/Image'; +import React from 'react'; +import {render} from './utils/render'; + +describe('IllustratedMessage', () => { + it('renders', async () => { + const screen = await render( + + + Create your first asset. + Get started by uploading or importing some assets. + + + + + + ); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Image.test.tsx b/packages/@react-spectrum/s2/test/Image.test.tsx index 014a0f01cbf..5e9a769639f 100644 --- a/packages/@react-spectrum/s2/test/Image.test.tsx +++ b/packages/@react-spectrum/s2/test/Image.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 Adobe. All rights reserved. + * 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 @@ -10,56 +10,17 @@ * governing permissions and limitations under the License. */ -import {Image, Provider} from '../src'; -import {render} from '@react-spectrum/test-utils-internal'; +import {describe, expect, it} from 'vitest'; +import {Image} from '../src'; +import {render} from './utils/render'; -describe('Image', () => { - it('should support conditional sources', async () => { - let {getByRole} = render( - test= 500px)'}, - {srcSet: 'default.png'} - ]} /> - ); - - let img = getByRole('img'); - let picture = img.parentElement!; - expect(picture.tagName).toBe('PICTURE'); - let sources = picture.querySelectorAll('source'); - - expect(sources).toHaveLength(3); - expect(sources[0]).toHaveAttribute('srcset', 'foo.png'); - expect(sources[0]).toHaveAttribute('type', 'image/png'); - expect(sources[0]).toHaveAttribute('media', '(prefers-color-scheme: light)'); - expect(sources[1]).toHaveAttribute('srcset', 'bar.png'); - expect(sources[1]).toHaveAttribute('media', '(width >= 500px) and (prefers-color-scheme: dark)'); - expect(sources[2]).toHaveAttribute('srcset', 'default.png'); - }); +const imageSrc = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADMElEQVR4nOzVwQnAIBQFQYXff81RUkQCOyDj1YOPnbXWPmeTRef+/3O/OyBjzh3CD95BfqICMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMO0TAAD//2Anhf4QtqobAAAAAElFTkSuQmCC'; - it('should support conditional sources with Provider colorScheme override', async () => { - let {getByRole} = render( - - test= 500px)'}, - {srcSet: 'default.png'} - ]} /> - +describe('Image', () => { + it('renders', async () => { + const screen = await render( + Test ); - - let img = getByRole('img'); - let picture = img.parentElement!; - expect(picture.tagName).toBe('PICTURE'); - let sources = picture.querySelectorAll('source'); - - expect(sources).toHaveLength(2); - expect(sources[0]).toHaveAttribute('srcset', 'bar.png'); - expect(sources[0]).toHaveAttribute('media', '(width >= 500px)'); - expect(sources[1]).toHaveAttribute('srcset', 'default.png'); + expect(screen.getByAltText('Test')).toBeInTheDocument(); }); }); diff --git a/packages/@react-spectrum/s2/test/ImageCoordinator.test.tsx b/packages/@react-spectrum/s2/test/ImageCoordinator.test.tsx new file mode 100644 index 00000000000..4e4b40d5125 --- /dev/null +++ b/packages/@react-spectrum/s2/test/ImageCoordinator.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {Image, ImageCoordinator} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ImageCoordinator', () => { + it('renders', async () => { + const screen = await render( + + test + + ); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/InlineAlert.test.tsx b/packages/@react-spectrum/s2/test/InlineAlert.test.tsx new file mode 100644 index 00000000000..f927db8d66e --- /dev/null +++ b/packages/@react-spectrum/s2/test/InlineAlert.test.tsx @@ -0,0 +1,28 @@ +/* + * 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 {Content, Heading, InlineAlert} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('InlineAlert', () => { + it('renders', async () => { + const screen = await render( + + Payment Information + Enter your billing address, shipping address, and payment method to complete your purchase. + + ); + expect(screen.getByRole('alert')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Link.test.tsx b/packages/@react-spectrum/s2/test/Link.test.tsx new file mode 100644 index 00000000000..9cc135a3db2 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Link.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {Link} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Link', () => { + it('renders', async () => { + const screen = await render( + Link + ); + expect(screen.getByRole('link')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/LinkButton.test.tsx b/packages/@react-spectrum/s2/test/LinkButton.test.tsx new file mode 100644 index 00000000000..8a2bd6e7857 --- /dev/null +++ b/packages/@react-spectrum/s2/test/LinkButton.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {LinkButton} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('LinkButton', () => { + it('renders', async () => { + const screen = await render(Link); + expect(screen.getByRole('link')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Menu.test.tsx b/packages/@react-spectrum/s2/test/Menu.test.tsx index 32bfae1ee75..885148d1d7a 100644 --- a/packages/@react-spectrum/s2/test/Menu.test.tsx +++ b/packages/@react-spectrum/s2/test/Menu.test.tsx @@ -10,181 +10,33 @@ * governing permissions and limitations under the License. */ -import {AriaMenuTests} from '../../../react-aria-components/test/AriaMenu.test-util'; -import {Button, Collection, Header, Heading, Menu, MenuItem, MenuSection, MenuTrigger, SubmenuTrigger} from '../src'; +import {Button, Menu, MenuItem, MenuSection, MenuTrigger} from '../src'; +import {describe, expect, it, vi} from 'vitest'; import React from 'react'; -import {render} from '@react-spectrum/test-utils-internal'; -import {Selection} from '@react-types/shared'; - -// better to accept items from the test? or just have the test have a requirement that you render a certain-ish structure? -// what about the button label? -// where and how can i define the requirements/assumptions for setup for the test? -let withSection = [ - {id: 'heading 1', name: 'Heading 1', children: [ - {id: 'foo', name: 'Foo'}, - {id: 'bar', name: 'Bar'}, - {id: 'baz', name: 'Baz'} - ]} -]; - -let items = [ - {id: 'foo', name: 'Foo'}, - {id: 'bar', name: 'Bar'}, - {id: 'baz', name: 'Baz'} -]; - -function SelectionStatic(props) { - let {selectionMode = 'single'} = props; - let [selected, setSelected] = React.useState(new Set()); - return ( - - - - -
Heading 1
- Foo - Bar - Baz - Fizz -
-
-
- ); -} - -AriaMenuTests({ - prefix: 'spectrum2-static', - renderers: { - standard: () => render( - - - +import {render} from './utils/render'; + +describe('Menu', () => { + it('renders', async () => { + vi.useFakeTimers(); + const screen = await render( + + + -
Heading 1
- Foo - Bar - Baz + alert('Quick export')}> + Quick Export + + Open a copy
-
-
- ), - disabledTrigger: () => render( - - - - -
Heading 1
- Foo - Bar - Baz -
-
-
- ), - singleSelection: () => render( - - ), - multipleSelection: () => render( - - ), - submenus: () => render( - - - - -
Heading 1
- - Open - Rename… - Duplicate - - Share… - - -
Subheading 1
- Email… - - Share… - - -
Subheading 1
- Work - Personal -
-
-
- SMS - X -
-
-
+ + Show files + Show folders
- ) - } -}); - -AriaMenuTests({ - prefix: 'spectrum2-dynamic', - renderers: { - standard: () => render( - - - - {(section) => { - return ( - -
{section.name}
- - {item => { - return {item.name}; - }} - -
- ); - }} -
-
- ), - disabledTrigger: () => render( - - - - {(section) => { - return ( - -
{section.name}
- - {item => { - return {item.name}; - }} - -
- ); - }} -
-
- ) - } -}); - -AriaMenuTests({ - prefix: 'spectrum2-dynamic-no-section', - renderers: { - standard: () => render( - - - - {(item) => - {item.name} - } - - - ) - } + ); + vi.runAllTimers(); + expect(screen.getByRole('menu')).toBeInTheDocument(); + vi.useRealTimers(); + }); }); diff --git a/packages/@react-spectrum/s2/test/Meter.test.tsx b/packages/@react-spectrum/s2/test/Meter.test.tsx new file mode 100644 index 00000000000..ab6588a19bb --- /dev/null +++ b/packages/@react-spectrum/s2/test/Meter.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {Meter} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Meter', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.getByRole('meter')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/NotificationBadge.test.tsx b/packages/@react-spectrum/s2/test/NotificationBadge.test.tsx new file mode 100644 index 00000000000..d96b7ee3f80 --- /dev/null +++ b/packages/@react-spectrum/s2/test/NotificationBadge.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {NotificationBadge} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('NotificationBadge', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/NumberField.test.tsx b/packages/@react-spectrum/s2/test/NumberField.test.tsx new file mode 100644 index 00000000000..fbce2536077 --- /dev/null +++ b/packages/@react-spectrum/s2/test/NumberField.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {NumberField} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('NumberField', () => { + it('renders', async () => { + const screen = await render( + + ); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Picker.test.tsx b/packages/@react-spectrum/s2/test/Picker.test.tsx index cc6769f7475..9dd273be3e9 100644 --- a/packages/@react-spectrum/s2/test/Picker.test.tsx +++ b/packages/@react-spectrum/s2/test/Picker.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 Adobe. All rights reserved. + * 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 @@ -9,76 +9,16 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ - -import {act, render, setupIntersectionObserverMock} from '@react-spectrum/test-utils-internal'; -import {Content, ContextualHelp, Heading, Picker, PickerItem, Text} from '../src'; -import {pointerMap, User} from '@react-aria/test-utils'; +import {describe, expect, it, vi} from 'vitest'; +import {Picker, PickerItem} from '../src'; import React from 'react'; -import userEvent from '@testing-library/user-event'; +import {render} from './utils/render'; describe('Picker', () => { - let testUtilUser = new User(); - function DynamicPicker(props) { - let {items, loadingState, onLoadMore, ...otherProps} = props; - return ( - - {(item: any) => {item.name}} - - ); - } - - beforeAll(function () { - jest.useFakeTimers(); - jest.spyOn(window.HTMLElement.prototype, 'clientWidth', 'get').mockImplementation(() => 100); - jest.spyOn(window.HTMLElement.prototype, 'clientHeight', 'get').mockImplementation(() => 100); - jest.spyOn(window.HTMLElement.prototype, 'scrollHeight', 'get').mockImplementation(() => 50); - }); - - afterEach(() => { - jest.clearAllMocks(); - act(() => jest.runAllTimers()); - }); - - afterAll(function () { - jest.restoreAllMocks(); - }); - - it('should only call loadMore whenever intersection is detected', async () => { - let onLoadMore = jest.fn(); - let observe = jest.fn(); - let observer = setupIntersectionObserverMock({ - observe - }); - - let tree = render( - - Chocolate - Mint - Strawberry - Vanilla - Chocolate Chip Cookie Dough - - ); - - let selectTester = testUtilUser.createTester('Select', {root: tree.container}); - expect(selectTester.listbox).toBeFalsy(); - selectTester.setInteractionType('mouse'); - await selectTester.open(); - - expect(onLoadMore).toHaveBeenCalledTimes(0); - let sentinel = tree.getByTestId('loadMoreSentinel'); - expect(observe).toHaveBeenLastCalledWith(sentinel); - - act(() => {observer.instance.triggerCallback([{isIntersecting: true}]);}); - act(() => {jest.runAllTimers();}); - - tree.rerender( - + it('renders', async () => { + vi.useFakeTimers(); + const screen = await render( + Chocolate Mint Strawberry @@ -86,92 +26,8 @@ describe('Picker', () => { Chocolate Chip Cookie Dough ); - - act(() => {observer.instance.triggerCallback([{isIntersecting: true}]);}); - act(() => {jest.runAllTimers();}); - // Note that if this was using useAsyncList, we'd be shielded from extranous onLoadMore calls but - // we want to leave that to user discretion - expect(onLoadMore).toHaveBeenCalledTimes(2); - }); - - it('should properly calculate the expected row index values', async () => { - let items = [{name: 'Chocolate'}, {name: 'Mint'}, {name: 'Chocolate Chip'}]; - let tree = render(); - - let selectTester = testUtilUser.createTester('Select', {root: tree.container}); - await selectTester.open(); - let options = selectTester.options(); - for (let [index, option] of options.entries()) { - expect(option).toHaveAttribute('aria-posinset', `${index + 1}`); - expect(option).toHaveAttribute('aria-setsize', `${items.length}`); - } - - tree.rerender(); - options = selectTester.options(); - for (let [index, option] of options.entries()) { - if (index === options.length - 1) { - // The last row is the loader here which shouldn't have posinset - expect(option).not.toHaveAttribute('aria-posinset'); - expect(option).not.toHaveAttribute('aria-setsize'); - } else { - expect(option).toHaveAttribute('aria-posinset', `${index + 1}`); - expect(option).toHaveAttribute('aria-setsize', `${items.length}`); - } - } - - let newItems = [...items, {name: 'Chocolate Mint'}, {name: 'Chocolate Chip Cookie Dough'}]; - tree.rerender(); - - options = selectTester.options(); - for (let [index, option] of options.entries()) { - expect(option).toHaveAttribute('aria-posinset', `${index + 1}`); - expect(option).toHaveAttribute('aria-setsize', `${newItems.length}`); - } - }); - - it('should support contextual help', async () => { - // Issue with how we don't render the contextual help button in the fake DOM since PressResponder isn't using createHideableComponent - let warn = jest.spyOn(global.console, 'warn').mockImplementation(); - let user = userEvent.setup({delay: null, pointerMap}); - let tree = render( - - Title here - - - Contents - - - - } - label="test"> - Chocolate - Mint - Strawberry - Vanilla - Chocolate Chip Cookie Dough - - ); - - let selectTester = testUtilUser.createTester('Select', {root: tree.getByTestId('testpicker')}); - let buttons = tree.getAllByRole('button'); - expect(buttons).toHaveLength(2); - expect(buttons[1]).toBe(selectTester.trigger); - - await user.click(buttons[0]); - - act(() => { - jest.runAllTimers(); - }); - - let dialog = tree.getByRole('dialog'); - expect(dialog).toBeVisible(); - - // Because of the fake DOM we'll see this twice - expect(tree.getAllByText('Title here')[1]).toBeVisible(); - expect(tree.getAllByText('Contents')[1]).toBeVisible(); - warn.mockRestore(); + vi.runAllTimers(); + expect(screen.getByRole('button')).toBeInTheDocument(); + vi.useRealTimers(); }); }); diff --git a/packages/@react-spectrum/s2/test/Popover.test.tsx b/packages/@react-spectrum/s2/test/Popover.test.tsx new file mode 100644 index 00000000000..11e39f47887 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Popover.test.tsx @@ -0,0 +1,52 @@ +/* + * 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 { + ActionButton, + Button, + DialogTrigger, + Form, + Popover, + Switch, + TextField +} from '../src'; +import {describe, expect, it, vi} from 'vitest'; +import Feedback from '@react-spectrum/s2/icons/Feedback'; +import React from 'react'; +import {render} from './utils'; + +describe('Popover', () => { + it('renders', async () => { + vi.useFakeTimers(); + const screen = await render( + + + + + +
+

How are we doing? Share your feedback here.

+
+ + + Adobe can contact me for further questions concerning this feedback + + +
+
+
+ ); + vi.runAllTimers(); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + vi.useRealTimers(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ProgressBar.test.tsx b/packages/@react-spectrum/s2/test/ProgressBar.test.tsx new file mode 100644 index 00000000000..b484ef71578 --- /dev/null +++ b/packages/@react-spectrum/s2/test/ProgressBar.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {ProgressBar} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ProgressBar', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/ProgressCircle.test.tsx b/packages/@react-spectrum/s2/test/ProgressCircle.test.tsx new file mode 100644 index 00000000000..c8e524bb6a4 --- /dev/null +++ b/packages/@react-spectrum/s2/test/ProgressCircle.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {ProgressCircle} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('ProgressCircle', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Provider.test.tsx b/packages/@react-spectrum/s2/test/Provider.test.tsx new file mode 100644 index 00000000000..d030b905351 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Provider.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {Provider} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('Provider', () => { + it('renders', async () => { + const screen = await render( + +
Hello React Spectrum!
+
+ ); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/RadioGroup.test.tsx b/packages/@react-spectrum/s2/test/RadioGroup.test.tsx index 1868fe10e7e..9983844465f 100644 --- a/packages/@react-spectrum/s2/test/RadioGroup.test.tsx +++ b/packages/@react-spectrum/s2/test/RadioGroup.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 Adobe. All rights reserved. + * 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 @@ -10,68 +10,20 @@ * governing permissions and limitations under the License. */ -jest.mock('@react-aria/live-announcer'); -import {Direction} from '@react-types/shared'; -import {pointerMap, render, User} from '@react-spectrum/test-utils-internal'; -import {Provider, Radio, RadioGroup} from '../src'; +import {describe, expect, it} from 'vitest'; +import {Radio, RadioGroup} from '../src'; import React from 'react'; -import userEvent from '@testing-library/user-event'; +import {render} from './utils/render'; describe('RadioGroup', () => { - let testUtilUser = new User(); - let user; - beforeAll(() => { - user = userEvent.setup({delay: null, pointerMap}); - }); - - it.each` - Name | props - ${'ltr + vertical'} | ${{locale: 'de-DE', orientation: 'vertical'}} - ${'rtl + verfical'} | ${{locale: 'ar-AE', orientation: 'vertical'}} - ${'ltr + horizontal'} | ${{locale: 'de-DE', orientation: 'horizontal'}} - ${'rtl + horizontal'} | ${{locale: 'ar-AE', orientation: 'horizontal'}} - `('$Name should select the correct radio via keyboard regardless of orientation and disabled radios', async function ({props}) { - let {getByRole} = render( - - - Dogs - Cats - Dragons - Unicorns - Chocobo - - + it('renders', async () => { + const screen = await render( + + Cat + Dog + Dragon + ); - let direction = props.locale === 'ar-AE' ? 'rtl' : 'ltr' as Direction; - let radioGroupTester = testUtilUser.createTester('RadioGroup', {root: getByRole('radiogroup'), direction}); - expect(radioGroupTester.radiogroup).toHaveAttribute('aria-orientation', props.orientation); - let radios = radioGroupTester.radios; - await radioGroupTester.triggerRadio({radio: radios[0]}); - expect(radios[0]).toBeChecked(); - - await radioGroupTester.triggerRadio({radio: 4, interactionType: 'keyboard'}); - expect(radios[4]).toBeChecked(); - - let radio4 = radioGroupTester.findRadio({radioIndexOrText: 3}); - await radioGroupTester.triggerRadio({radio: radio4, interactionType: 'keyboard'}); - expect(radios[3]).toBeChecked(); - - await radioGroupTester.triggerRadio({radio: 'Dogs', interactionType: 'mouse'}); - expect(radios[0]).toBeChecked(); - - let radio5 = radioGroupTester.findRadio({radioIndexOrText: 'Chocobo'}); - await radioGroupTester.triggerRadio({radio: radio5, interactionType: 'mouse'}); - expect(radios[4]).toBeChecked(); - - // This isn't using the radioGroup tester because the tester uses the attached aria-orientation to determine - // what arrow to press, which won't reproduce the original bug where we forgot to pass the orientation to the RadioGroup. - // The tester ends up still pressing the correct keys (ArrowUp/ArrowDown) to navigate properly even in the horizontal orientation - // instead of using ArrowLeft/ArrowRight - await user.keyboard('[ArrowLeft]'); - if (props.locale === 'ar-AE' && props.orientation === 'horizontal') { - expect(radioGroupTester.selectedRadio).toBe(radios[0]); - } else { - expect(radioGroupTester.selectedRadio).toBe(radios[3]); - } + expect(screen.getByRole('radiogroup')).toBeInTheDocument(); }); }); diff --git a/packages/@react-spectrum/s2/test/RangeCalendar.test.tsx b/packages/@react-spectrum/s2/test/RangeCalendar.test.tsx new file mode 100644 index 00000000000..8cc26883cad --- /dev/null +++ b/packages/@react-spectrum/s2/test/RangeCalendar.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {RangeCalendar} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('RangeCalendar', () => { + it('renders', async () => { + const screen = await render( + + ); + expect(screen.getByRole('grid')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/RangeSlider.test.tsx b/packages/@react-spectrum/s2/test/RangeSlider.test.tsx new file mode 100644 index 00000000000..c7e86d652e4 --- /dev/null +++ b/packages/@react-spectrum/s2/test/RangeSlider.test.tsx @@ -0,0 +1,29 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import {RangeSlider} from '../src'; +import React from 'react'; +import {render} from './utils/render'; + +describe('RangeSlider', () => { + it('renders', async () => { + const screen = await render( + + ); + // Check for sliders using input[type="range"] since RangeSlider uses native range inputs + const sliders = screen.container.querySelectorAll('input[type="range"]'); + expect(sliders.length).toBe(2); + }); +}); diff --git a/packages/@react-spectrum/s2/test/SearchField.test.tsx b/packages/@react-spectrum/s2/test/SearchField.test.tsx index a8ccddc2ae3..6ac8a9c471b 100644 --- a/packages/@react-spectrum/s2/test/SearchField.test.tsx +++ b/packages/@react-spectrum/s2/test/SearchField.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 Adobe. All rights reserved. + * 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 @@ -10,35 +10,18 @@ * governing permissions and limitations under the License. */ -import {Autocomplete} from 'react-aria-components'; -import {Menu, MenuItem, SearchField} from '../src'; -import {pointerMap, render} from '@react-spectrum/test-utils-internal'; +import {describe, expect, it} from 'vitest'; import React from 'react'; -import userEvent from '@testing-library/user-event'; +import {render} from './utils/render'; +import {SearchField} from '../src'; describe('SearchField', () => { - let user; - beforeAll(() => { - user = userEvent.setup({delay: null, pointerMap}); - }); - - it('should not apply the focus visible styles on the group when typing in the Autocomplete wrapped SearchField', async () => { - let {getByRole} = render( - - - - Foo - Bar - Baz - - + it('renders', async () => { + const screen = await render( + ); - - let input = getByRole('searchbox'); - await user.click(input); - let group = getByRole('group'); - expect(group).not.toHaveAttribute('data-focus-visible'); - await user.keyboard('Foo'); - expect(group).not.toHaveAttribute('data-focus-visible'); + expect(screen.getByRole('searchbox')).toBeInTheDocument(); }); }); diff --git a/packages/@react-spectrum/s2/test/SegmentedControl.test.tsx b/packages/@react-spectrum/s2/test/SegmentedControl.test.tsx new file mode 100644 index 00000000000..8f5c0d591bd --- /dev/null +++ b/packages/@react-spectrum/s2/test/SegmentedControl.test.tsx @@ -0,0 +1,31 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; +import {SegmentedControl, SegmentedControlItem} from '../src'; + +describe('SegmentedControl', () => { + it('renders', async () => { + const screen = await render( + + Day + Week + Month + Year + + ); + const radios = screen.container.querySelectorAll('[role="radio"]'); + expect(radios.length).toBe(4); + }); +}); diff --git a/packages/@react-spectrum/s2/test/SelectBoxGroup.test.tsx b/packages/@react-spectrum/s2/test/SelectBoxGroup.test.tsx index 8fd18a33e3e..9e10b272aa7 100644 --- a/packages/@react-spectrum/s2/test/SelectBoxGroup.test.tsx +++ b/packages/@react-spectrum/s2/test/SelectBoxGroup.test.tsx @@ -1,913 +1,38 @@ -import {act, pointerMap, render, screen, waitFor} from '@react-spectrum/test-utils-internal'; -import Calendar from '../spectrum-illustrations/linear/Calendar'; +/* + * 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 {describe, expect, it} from 'vitest'; import React from 'react'; +import {render} from './utils/render'; import {SelectBox, SelectBoxGroup, Text} from '../src'; -import {Selection} from '@react-types/shared'; -import {User} from '@react-aria/test-utils'; -import userEvent from '@testing-library/user-event'; - -function ControlledSingleSelectBox() { - const [selectedKeys, setSelectedKeys] = React.useState(new Set()); - return ( - - - Option 1 - - - Option 2 - - - Option 3 - - - ); -} - -function ControlledMultiSelectBox() { - const [selectedKeys, setSelectedKeys] = React.useState(new Set()); - return ( - - - Option 1 - - - Option 2 - - - Option 3 - - - ); -} - -function UncontrolledSelectBox({selectionMode = 'single'}: {selectionMode?: 'single' | 'multiple'}) { - return ( - - - Option 1 - - - Option 2 - - - Option 3 - - - ); -} - -function DisabledSelectBox() { - return ( - {}} - selectedKeys={new Set()} - isDisabled> - - Option 1 - - - Option 2 - - - ); -} describe('SelectBoxGroup', () => { - let user; - let testUtilUser = new User(); - - beforeAll(function () { - jest.useFakeTimers(); - user = userEvent.setup({delay: null, pointerMap}); - }); - - afterEach(() => { - jest.clearAllMocks(); - act(() => jest.runAllTimers()); - }); - - afterAll(function () { - jest.restoreAllMocks(); - }); - - describe('Basic functionality', () => { - it('renders as a listbox with options', () => { - render(); - expect(screen.getByRole('listbox')).toBeInTheDocument(); - expect(screen.getAllByRole('option')).toHaveLength(3); - expect(screen.getByText('Option 1')).toBeInTheDocument(); - }); - - it('renders multiple selection mode', () => { - render(); - expect(screen.getByRole('listbox')).toBeInTheDocument(); - expect(screen.getAllByRole('option')).toHaveLength(3); - expect(screen.getByText('Option 1')).toBeInTheDocument(); - }); - }); - - describe('Uncontrolled behavior', () => { - it('handles uncontrolled click selection in single mode', async () => { - render(); - let listboxTester = testUtilUser.createTester('ListBox', {root: screen.getByRole('listbox')}); - - await listboxTester.toggleOptionSelection({option: 0}); - expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'true'); - }); - - it('handles uncontrolled click selection in multiple mode', async () => { - render(); - let listboxTester = testUtilUser.createTester('ListBox', {root: screen.getByRole('listbox')}); - - await listboxTester.toggleOptionSelection({option: 0}); - await listboxTester.toggleOptionSelection({option: 1}); - - expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'true'); - expect(listboxTester.options()[1]).toHaveAttribute('aria-selected', 'true'); - }); - - it('handles uncontrolled selection toggle', async () => { - render(); - let listboxTester = testUtilUser.createTester('ListBox', {root: screen.getByRole('listbox')}); - - await listboxTester.toggleOptionSelection({option: 0}); - expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'true'); - - // Toggle off in single mode by selecting another - await listboxTester.toggleOptionSelection({option: 1}); - expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'false'); - expect(listboxTester.options()[1]).toHaveAttribute('aria-selected', 'true'); - }); - - it('handles uncontrolled keyboard selection', async () => { - render(); - await user.tab(); - - await act(async () => { - await user.keyboard(' '); - }); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - expect(option1).toHaveAttribute('aria-selected', 'true'); - }); - }); - - describe('Controlled behavior', () => { - it('handles controlled selection in single mode', async () => { - render(); - let listboxTester = testUtilUser.createTester('ListBox', {root: screen.getByRole('listbox')}); - - await listboxTester.toggleOptionSelection({option: 0}); - expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'true'); - }); - - it('handles controlled multiple selection', async () => { - render(); - let listboxTester = testUtilUser.createTester('ListBox', {root: screen.getByRole('listbox')}); - - await listboxTester.toggleOptionSelection({option: 0}); - await listboxTester.toggleOptionSelection({option: 1}); - - expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'true'); - expect(listboxTester.options()[1]).toHaveAttribute('aria-selected', 'true'); - }); - - it('calls onSelectionChange when selection changes in controlled mode', async () => { - const onSelectionChange = jest.fn(); - const TestComponent = () => { - const [selectedKeys, setSelectedKeys] = React.useState(new Set()); - return ( - { - setSelectedKeys(keys); - onSelectionChange(keys); - }} - selectedKeys={selectedKeys}> - - Option 1 - - - Option 2 - - - ); - }; - - render(); - const option1 = screen.getByRole('option', {name: 'Option 1'}); - await user.click(option1); - - expect(onSelectionChange).toHaveBeenCalledTimes(1); - const receivedSelection = onSelectionChange.mock.calls[0][0]; - expect(Array.from(receivedSelection)).toEqual(['option1']); - }); - - it('calls onSelectionChange with Set for multiple selection in controlled mode', async () => { - const onSelectionChange = jest.fn(); - const TestComponent = () => { - const [selectedKeys, setSelectedKeys] = React.useState(new Set()); - return ( - { - setSelectedKeys(keys); - onSelectionChange(keys); - }} - selectedKeys={selectedKeys}> - - Option 1 - - - Option 2 - - - ); - }; - - render(); - const option1 = screen.getByRole('option', {name: 'Option 1'}); - await user.click(option1); - - expect(onSelectionChange).toHaveBeenCalledTimes(1); - const receivedSelection = onSelectionChange.mock.calls[0][0]; - expect(Array.from(receivedSelection)).toEqual(['option1']); - }); - }); - - describe('Disabled behavior', () => { - it('renders disabled state correctly', () => { - render(); - const listbox = screen.getByRole('listbox'); - expect(listbox).toBeInTheDocument(); - - const options = screen.getAllByRole('option'); - expect(options.length).toBeGreaterThan(0); - }); - - it('prevents interaction when group is disabled', async () => { - const onSelectionChange = jest.fn(); - render( - - - Option 1 - - - Option 2 - - - ); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - const option2 = screen.getByRole('option', {name: 'Option 2'}); - await user.click(option1); - await user.click(option2); - expect(onSelectionChange).not.toHaveBeenCalled(); - - expect(option1).toHaveAttribute('aria-disabled', 'true'); - expect(option2).toHaveAttribute('aria-disabled', 'true'); - }); - - it('prevents uncontrolled interaction when group is disabled', async () => { - render( - - - Option 1 - - - Option 2 - - - ); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - const option2 = screen.getByRole('option', {name: 'Option 2'}); - - await user.click(option1); - await user.click(option2); - - // should have disabled attributes and no selection - expect(option1).toHaveAttribute('aria-disabled', 'true'); - expect(option2).toHaveAttribute('aria-disabled', 'true'); - expect(option1).toHaveAttribute('aria-selected', 'false'); - expect(option2).toHaveAttribute('aria-selected', 'false'); - }); - }); - - describe('Checkbox functionality', () => { - it('shows checkbox when item is selected in controlled mode', async () => { - render( - {}} - selectedKeys={new Set(['option1'])}> - - Option 1 - - - Option 2 - - - ); - - const selectedRow = screen.getByRole('option', {name: 'Option 1'}); - expect(selectedRow).toHaveAttribute('aria-selected', 'true'); - - const checkboxDiv = selectedRow.querySelector('[aria-hidden="true"]'); - expect(checkboxDiv).toBeInTheDocument(); - }); - - it('shows checkbox when item is selected in uncontrolled mode', async () => { - render(); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - await user.click(option1); - - expect(option1).toHaveAttribute('aria-selected', 'true'); - const checkboxDiv = option1.querySelector('[aria-hidden="true"]'); - expect(checkboxDiv).toBeInTheDocument(); - }); - - it('shows checkbox on hover for non-disabled items in controlled mode', async () => { - render( - {}} - selectedKeys={new Set()}> - - Option 1 - - - ); - - const row = screen.getByRole('option', {name: 'Option 1'}); - - await user.hover(row); - await waitFor(() => { - const checkboxDiv = row.querySelector('[aria-hidden="true"]'); - expect(checkboxDiv).toBeInTheDocument(); - }); - }); - - it('shows checkbox on hover for non-disabled items in uncontrolled mode', async () => { - render(); - - const row = screen.getByRole('option', {name: 'Option 1'}); - - await user.hover(row); - await waitFor(() => { - const checkboxDiv = row.querySelector('[aria-hidden="true"]'); - expect(checkboxDiv).toBeInTheDocument(); - }); - }); - - it('shows checkbox for disabled but selected items', () => { - render( - {}} - defaultSelectedKeys={new Set(['option1'])}> - - Option 1 - - - ); - - const row = screen.getByRole('option', {name: 'Option 1'}); - - const checkboxDiv = row.querySelector('[aria-hidden="true"]'); - expect(checkboxDiv).toBeInTheDocument(); - }); - - it('shows checkbox for disabled items (always show checkboxes)', async () => { - render( - {}} - selectedKeys={new Set()}> - - Option 1 - - - ); - - const row = screen.getByRole('option', {name: 'Option 1'}); - - // checkbox always present - const checkboxDiv = row.querySelector('[aria-hidden="true"]'); - expect(checkboxDiv).toBeInTheDocument(); - }); - }); - - describe('Props and configuration', () => { - it('supports different orientations', () => { - render( - {}} - selectedKeys={new Set()} - orientation="horizontal"> - - Option 1 - - - ); - expect(screen.getByRole('listbox')).toBeInTheDocument(); - }); - - it('auto-fits columns based on orientation (vertical)', () => { - render( -
- {}} - selectedKeys={new Set()} - orientation="vertical"> - - Option 1 - - - Option 2 - - - Option 3 - - -
- ); - - const listbox = screen.getByRole('listbox'); - expect(listbox).toBeInTheDocument(); - }); - - it('auto-fits columns based on orientation (horizontal)', () => { - render( -
- {}} - selectedKeys={new Set()} - orientation="horizontal"> - - Option 1 - - - Option 2 - - -
- ); - - const listbox = screen.getByRole('listbox'); - expect(listbox).toBeInTheDocument(); - }); - }); - - describe('Controlled behavior', () => { - it('handles initial id selection', () => { - render( - {}} - selectedKeys={new Set(['option1'])}> - - Option 1 - - - Option 2 - - - ); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - const option2 = screen.getByRole('option', {name: 'Option 2'}); - - expect(option1).toHaveAttribute('aria-selected', 'true'); - expect(option2).toHaveAttribute('aria-selected', 'false'); - }); - - it('handles multiple selection with initial ids', () => { - render( - {}} - selectedKeys={new Set(['option1', 'option2'])}> - - Option 1 - - - Option 2 - - - Option 3 - - - ); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - const option2 = screen.getByRole('option', {name: 'Option 2'}); - const option3 = screen.getByRole('option', {name: 'Option 3'}); - - expect(option1).toHaveAttribute('aria-selected', 'true'); - expect(option2).toHaveAttribute('aria-selected', 'true'); - expect(option3).toHaveAttribute('aria-selected', 'false'); - }); - - it('handles uncontrolled selection with defaultSelectedKeys', async () => { - render( - - - Option 1 - - - Option 2 - - - ); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - const option2 = screen.getByRole('option', {name: 'Option 2'}); - - expect(option1).toHaveAttribute('aria-selected', 'true'); - expect(option2).toHaveAttribute('aria-selected', 'false'); - - // click should update selection - await user.click(option2); - expect(option1).toHaveAttribute('aria-selected', 'false'); - expect(option2).toHaveAttribute('aria-selected', 'true'); - }); - - it('handles uncontrolled multiple selection with defaultSelectedKeys', async () => { - render( - - - Option 1 - - - Option 2 - - - Option 3 - - - ); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - const option2 = screen.getByRole('option', {name: 'Option 2'}); - const option3 = screen.getByRole('option', {name: 'Option 3'}); - - expect(option1).toHaveAttribute('aria-selected', 'true'); - expect(option2).toHaveAttribute('aria-selected', 'true'); - expect(option3).toHaveAttribute('aria-selected', 'false'); - - await user.click(option3); - expect(option1).toHaveAttribute('aria-selected', 'true'); - expect(option2).toHaveAttribute('aria-selected', 'true'); - expect(option3).toHaveAttribute('aria-selected', 'true'); - - // click should remove from selection - await user.click(option1); - expect(option1).toHaveAttribute('aria-selected', 'false'); - expect(option2).toHaveAttribute('aria-selected', 'true'); - expect(option3).toHaveAttribute('aria-selected', 'true'); - }); - - it('handles controlled component updates', async () => { - function ControlledTest() { - const [selectedKeys, setSelectedKeys] = React.useState(new Set()); - - return ( -
- - - - Option 1 - - - Option 2 - - -
- ); - } - - render(); - - const button = screen.getByRole('button', {name: 'Select Option 2'}); - await user.click(button); - - const option2 = screen.getByRole('option', {name: 'Option 2'}); - expect(option2).toHaveAttribute('aria-selected', 'true'); - }); - - it('handles "all" selection', () => { - render( - {}} - selectedKeys="all"> - - Option 1 - - - Option 2 - - - ); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - const option2 = screen.getByRole('option', {name: 'Option 2'}); - - expect(option1).toHaveAttribute('aria-selected', 'true'); - expect(option2).toHaveAttribute('aria-selected', 'true'); - }); - }); - - describe('Grid navigation', () => { - it('supports keyboard navigation and grid layout', async () => { - render( - {}} - selectedKeys={new Set()}> - - Option 1 - - - Option 2 - - - Option 3 - - - Option 4 - - - ); - - const listbox = screen.getByRole('listbox'); - const options = screen.getAllByRole('option'); - - expect(listbox).toBeInTheDocument(); - expect(options).toHaveLength(4); - }); - - it('supports space key selection in uncontrolled mode', async () => { - render(); - const option1 = screen.getByRole('option', {name: 'Option 1'}); - - await user.tab(); - await user.keyboard(' '); - - expect(option1).toHaveAttribute('aria-selected', 'true'); - }); - - it('supports arrow key navigation', async () => { - render( - {}} - selectedKeys={new Set()}> - - Option 1 - - - Option 2 - - - ); - - await user.tab(); - - await user.keyboard('{ArrowDown}'); - - // check that navigation works by verifying an option has focus - const option1 = screen.getByRole('option', {name: 'Option 1'}); - expect(option1).toHaveFocus(); - }); - }); - - describe('Accessibility', () => { - it('has proper listbox structure', () => { - render( - {}} - selectedKeys={new Set()}> - - Option 1 - - - Option 2 - - - ); - - expect(screen.getByRole('listbox')).toBeInTheDocument(); - expect(screen.getAllByRole('option')).toHaveLength(2); - }); - - it('supports aria-label and aria-labelledby', () => { - render( -
-

My SelectBoxGroup

- {}} - selectedKeys={new Set()}> - - Option 1 - - -
- ); - - const listbox = screen.getByRole('listbox'); - // verify the listbox has an aria-labelledby attribute - expect(listbox).toHaveAttribute('aria-labelledby'); - expect(listbox.getAttribute('aria-labelledby')).toBeTruthy(); - }); - }); - - describe('Edge cases', () => { - it('handles complex children with slots', () => { - render( - {}} - selectedKeys={new Set()} - orientation="horizontal"> - - - Complex Option - With description - - - ); - - expect(screen.getByText('Complex Option')).toBeInTheDocument(); - expect(screen.getByText('With description')).toBeInTheDocument(); - }); - - it('handles different id types', () => { - render( - {}} - selectedKeys={new Set()}> - - Option 1 - - - Option 2 - - - ); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - const option2 = screen.getByRole('option', {name: 'Option 2'}); - expect(option1).toBeInTheDocument(); - expect(option2).toBeInTheDocument(); - }); - - it('handles empty children gracefully', () => { - render( - {}} - selectedKeys={new Set()}> - {null} - {undefined} - - Valid Option - - {false} - - ); - - expect(screen.getByRole('listbox')).toBeInTheDocument(); - expect(screen.getAllByRole('option')).toHaveLength(1); - expect(screen.getByText('Valid Option')).toBeInTheDocument(); - }); - - it('handles individual disabled items', () => { - render( - {}} - selectedKeys={new Set()}> - - Option 1 - - - Option 2 - - - ); - - const rows = screen.getAllByRole('option'); - expect(rows.length).toBe(2); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - expect(option1).toHaveAttribute('aria-disabled', 'true'); - }); - - it('prevents interaction with individually disabled items', async () => { - const onSelectionChange = jest.fn(); - render( - - - Option 1 - - - ); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - await user.click(option1); - - expect(onSelectionChange).not.toHaveBeenCalled(); - }); - - it('prevents uncontrolled interaction with individually disabled items', async () => { - render( - - - Option 1 - - - Option 2 - - - ); - - const option1 = screen.getByRole('option', {name: 'Option 1'}); - const option2 = screen.getByRole('option', {name: 'Option 2'}); - - await user.click(option1); - expect(option1).toHaveAttribute('aria-disabled', 'true'); - expect(option1).toHaveAttribute('aria-selected', 'false'); - - // clicking enabled item should still work - await user.click(option2); - expect(option2).toHaveAttribute('aria-selected', 'true'); - }); + it('renders', async () => { + const screen = await render( + + + Amazon Web Services + + + Microsoft Azure + + + Google Cloud Platform + + + IBM Cloud + + + ); + expect(screen.getByRole('listbox')).toBeInTheDocument(); }); }); diff --git a/packages/@react-spectrum/s2/test/Skeleton.test.tsx b/packages/@react-spectrum/s2/test/Skeleton.test.tsx new file mode 100644 index 00000000000..bdaad4579de --- /dev/null +++ b/packages/@react-spectrum/s2/test/Skeleton.test.tsx @@ -0,0 +1,29 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; +import {Skeleton, Text} from '../src'; + +describe('Skeleton', () => { + it('renders', async () => { + const screen = await render( + + + Placeholder title + + + ); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/SkeletonCollection.test.tsx b/packages/@react-spectrum/s2/test/SkeletonCollection.test.tsx new file mode 100644 index 00000000000..f16c99b6167 --- /dev/null +++ b/packages/@react-spectrum/s2/test/SkeletonCollection.test.tsx @@ -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 {Card, CardView, Collection, SkeletonCollection} from '../src'; +import {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; + +describe('SkeletonCollection', () => { + it('renders', async () => { + const screen = await render( + + + {() => Item} + + + {() => Loading} + + + ); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Slider.test.tsx b/packages/@react-spectrum/s2/test/Slider.test.tsx new file mode 100644 index 00000000000..21341701a14 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Slider.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; +import {Slider} from '../src'; + +describe('Slider', () => { + it('renders', async () => { + const screen = await render(); + expect(screen.getByRole('slider')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/StatusLight.test.tsx b/packages/@react-spectrum/s2/test/StatusLight.test.tsx new file mode 100644 index 00000000000..f97c6f5a469 --- /dev/null +++ b/packages/@react-spectrum/s2/test/StatusLight.test.tsx @@ -0,0 +1,23 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; +import {StatusLight} from '../src'; + +describe('StatusLight', () => { + it('renders', async () => { + const screen = await render(Status); + expect(screen.container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/Switch.test.tsx b/packages/@react-spectrum/s2/test/Switch.test.tsx new file mode 100644 index 00000000000..78579ff2b48 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Switch.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; +import {Switch} from '../src'; + +describe('Switch', () => { + it('renders', async () => { + const screen = await render( + + Low power mode + + ); + expect(screen.getByRole('switch')).toBeInTheDocument(); + }); +}); diff --git a/packages/@react-spectrum/s2/test/TableView.test.tsx b/packages/@react-spectrum/s2/test/TableView.test.tsx index 5b49c9d40d4..27831d2dfe4 100644 --- a/packages/@react-spectrum/s2/test/TableView.test.tsx +++ b/packages/@react-spectrum/s2/test/TableView.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 Adobe. All rights reserved. + * 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 @@ -10,102 +10,51 @@ * governing permissions and limitations under the License. */ -jest.mock('@react-aria/live-announcer'); -jest.mock('@react-aria/utils/src/scrollIntoView'); -import {act, render} from '@react-spectrum/test-utils-internal'; import { Cell, Column, - MenuItem, - MenuSection, Row, TableBody, TableHeader, - TableView, - Text + TableView } from '../src'; -import Filter from '../s2wf-icons/S2_Icon_Filter_20_N.svg'; +import {describe, expect, it} from 'vitest'; import React from 'react'; -import {User} from '@react-aria/test-utils'; - -// @ts-ignore -window.getComputedStyle = (el) => el.style; +import {render} from './utils/render'; describe('TableView', () => { - let offsetWidth, offsetHeight; - let testUtilUser = new User({advanceTimer: jest.advanceTimersByTime}); - beforeAll(function () { - offsetWidth = jest.spyOn(window.HTMLElement.prototype, 'clientWidth', 'get').mockImplementation(() => 400); - offsetHeight = jest.spyOn(window.HTMLElement.prototype, 'clientHeight', 'get').mockImplementation(() => 200); - jest.useFakeTimers(); - }); - - afterAll(function () { - offsetWidth.mockReset(); - offsetHeight.mockReset(); - }); - - afterEach(() => { - act(() => {jest.runAllTimers();}); - }); - - let columns = [ - {name: 'Foo', id: 'foo', isRowHeader: true}, - {name: 'Bar', id: 'bar'}, - {name: 'Baz', id: 'baz'}, - {name: 'Yah', id: 'yah'} - ]; - - let items = [ - {id: 1, foo: 'Foo 1', bar: 'Bar 1', baz: 'Baz 1', yah: 'Yah long long long 1'}, - {id: 2, foo: 'Foo 2', bar: 'Bar 2', baz: 'Baz 2', yah: 'Yah long long long 2'}, - {id: 3, foo: 'Foo 3', bar: 'Bar 3', baz: 'Baz 3', yah: 'Yah long long long 3'}, - {id: 4, foo: 'Foo 4', bar: 'Bar 4', baz: 'Baz 4', yah: 'Yah long long long 4'}, - {id: 5, foo: 'Foo 5', bar: 'Bar 5', baz: 'Baz 5', yah: 'Yah long long long 5'}, - {id: 6, foo: 'Foo 6', bar: 'Bar 6', baz: 'Baz 6', yah: 'Yah long long long 6'}, - {id: 7, foo: 'Foo 7', bar: 'Bar 7', baz: 'Baz 7', yah: 'Yah long long long 7'}, - {id: 8, foo: 'Foo 8', bar: 'Bar 8', baz: 'Baz 8', yah: 'Yah long long long 8'}, - {id: 9, foo: 'Foo 9', bar: 'Bar 9', baz: 'Baz 9', yah: 'Yah long long long 9'}, - {id: 10, foo: 'Foo 10', bar: 'Bar 10', baz: 'Baz 10', yah: 'Yah long long long 10'} - ]; - - it('should render custom menus', async () => { - let onAction = jest.fn(); - let {getByRole} = render( - - - {(column) => ( - - - Filter - - - Hide column - Manage columns - - - }>{column.name} - )} + it('renders', async () => { + const screen = await render( + + + Name + Type + Date Modified - - {item => ( - - {(column) => { - return {item[column.id]}; - }} - - )} + + + Projects + File folder + 6/7/2025 + + + Pictures + File folder + 4/7/2025 + + + 2024 Annual Financial Report + Text document + 12/30/2024 + + + Job Posting + Text Document + 1/18/2025 + ); - - let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')}); - await tableTester.triggerColumnHeaderAction({column: 1, action: 0, interactionType: 'keyboard'}); - expect(onAction).toHaveBeenCalledTimes(1); + expect(screen.getByRole('grid')).toBeInTheDocument(); }); }); diff --git a/packages/@react-spectrum/s2/test/Tabs.test.tsx b/packages/@react-spectrum/s2/test/Tabs.test.tsx new file mode 100644 index 00000000000..41bc02c0c76 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Tabs.test.tsx @@ -0,0 +1,46 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; +import {Tab, TabList, TabPanel, Tabs} from '../src'; + +describe('Tabs', () => { + it('renders', async () => { + const screen = await render( + + + Home + Files + Search + Settings + + +
Home content
+
+ +
Files content
+
+ +
Search content
+
+ +
Settings content
+
+
+ ); + expect(screen.getByRole('tablist')).toBeInTheDocument(); + const tabs = screen.container.querySelectorAll('[role="tab"]'); + expect(tabs.length).toBe(4); + }); +}); diff --git a/packages/@react-spectrum/s2/test/TagGroup.test.tsx b/packages/@react-spectrum/s2/test/TagGroup.test.tsx index 9deff9e855d..9c5023ad1aa 100644 --- a/packages/@react-spectrum/s2/test/TagGroup.test.tsx +++ b/packages/@react-spectrum/s2/test/TagGroup.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 Adobe. All rights reserved. + * 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 @@ -10,66 +10,34 @@ * governing permissions and limitations under the License. */ -import {act, pointerMap, render} from '@react-spectrum/test-utils-internal'; -import React from 'react'; +import {describe, expect, it} from 'vitest'; +import {render} from './utils/render'; import {Tag, TagGroup} from '../src'; -import userEvent from '@testing-library/user-event'; - - -let TestTagGroup = ({tagGroupProps, itemProps}) => ( - - Cat - Dog - Kangaroo - -); - -let renderTagGroup = (tagGroupProps = {}, itemProps = {}) => render(); - describe('TagGroup', () => { - let user; - beforeAll(() => { - jest.useFakeTimers(); - user = userEvent.setup({delay: null, pointerMap}); - }); - - afterEach(() => { - jest.clearAllMocks(); - act(() => jest.runAllTimers()); - }); - - afterAll(function () { - jest.restoreAllMocks(); - }); - - it('remove button should work', async () => { - let onRemove = jest.fn(); - let {getAllByLabelText} = render( - - Chocolate - Mint - Strawberry - Vanilla + it('renders', async () => { + const screen = await render( + + Chocolate + Mint + Strawberry + Vanilla + Chocolate Chip Cookie Dough + Rocky Road + Butter Pecan + Neapolitan + Salted Caramel + Mint Chocolate Chip + Tonight Dough + Lemon Cookie + Cookies and Cream + Phish Food + Peanut Butter Cup + Coffee + Pistachio + Cherry ); - - let removeButtons = getAllByLabelText('Remove'); - expect(removeButtons).toHaveLength(4); - await user.click(removeButtons[0]); - act(() => {jest.runAllTimers();}); - expect(onRemove).toHaveBeenCalledTimes(1); - expect(onRemove).toHaveBeenCalledWith(new Set(['chocolate'])); - }); - - it('should aria label on tags', () => { - let {getAllByRole} = renderTagGroup({label: 'TagGroup label'}, {'aria-label': 'Test'}); - - for (let row of getAllByRole('row')) { - expect(row).toHaveAttribute('aria-label', 'Test'); - } + expect(screen.container.firstChild).toBeInTheDocument(); }); }); diff --git a/packages/@react-spectrum/s2/test/TextArea.test.tsx b/packages/@react-spectrum/s2/test/TextArea.test.tsx new file mode 100644 index 00000000000..92ce941916f --- /dev/null +++ b/packages/@react-spectrum/s2/test/TextArea.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 {describe, expect, it} from 'vitest'; +import React from 'react'; +import {render} from './utils/render'; +import {TextArea} from '../src'; + +describe('TextArea', () => { + it('renders', async () => { + const screen = await render( +