diff --git a/.ci/.vo-test-hash.txt b/.ci/.vo-test-hash.txt deleted file mode 100644 index ffa77a3cb1..0000000000 --- a/.ci/.vo-test-hash.txt +++ /dev/null @@ -1 +0,0 @@ -755618255 \ No newline at end of file diff --git a/.ci/vo-tests-ci.ts b/.ci/vo-tests-ci.ts deleted file mode 100644 index c57b1d4b59..0000000000 --- a/.ci/vo-tests-ci.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { execSync } from 'child_process'; -import fs from 'fs/promises'; - -import { generateVoTestsHash } from './vo-tests-hash'; - -const oldHash = await fs.readFile('./.ci/.vo-test-hash.txt', 'utf-8'); -const newHash = await generateVoTestsHash(); - -if (oldHash === newHash) { - console.log('vo-test hash is not updated, skipping vo-test'); -} else { - execSync('pnpm vo-test', { stdio: 'inherit' }); -} - -await fs.writeFile('./.ci/.vo-test-hash.txt', newHash); diff --git a/.ci/vo-tests-hash.ts b/.ci/vo-tests-hash.ts deleted file mode 100644 index 142fd90a22..0000000000 --- a/.ci/vo-tests-hash.ts +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env tsm -const seed = 0; - -/** - * Generate hash of *.vo-test.ts files and it's dependencies (excluding node_modules) to - * skip vo tests runs when no files changed - */ - -import fs from 'fs/promises'; - -import esbuild from 'esbuild'; -import glob from 'fast-glob'; -import { fastHashCode as hash } from 'fast-hash-code'; - -import { esbuildPluginSemcoreSourcesResolve } from '../tools/esbuild-plugin-semcore/src/esbuild-plugin-semcore-sources-resolve'; - -export const generateVoTestsHash = async () => { - const voTestFiles = await glob([ - 'semcore/*/*.vo-test.ts', - 'semcore/*/*/*.vo-test.ts', - 'semcore/*/*/*/*.vo-test.ts', - ]); - const additionalFiles: string[] = []; - - await Promise.all( - voTestFiles.map(async (filePath) => { - const content = await fs.readFile(filePath, 'utf-8'); - const lines = content.split('\n'); - for (const line of lines) { - if (!line.includes('website/docs')) continue; - const standPath = line.substring(line.indexOf('\'website/docs') + 1, line.lastIndexOf('\'')); - if (standPath.endsWith('.jsx') || standPath.endsWith('.tsx')) { - additionalFiles.push(standPath); - } - } - }), - ); - const inputFiles = [...voTestFiles, ...additionalFiles]; - const metas = await Promise.all( - inputFiles.map((filePath) => - esbuild.build({ - entryPoints: [filePath], - write: false, - platform: 'node', - bundle: true, - metafile: true, - logLevel: 'error', - plugins: [esbuildPluginSemcoreSourcesResolve('.')], - external: ['@semcore/testing-utils/e2e-stand', '@playwright/*', '*.png', '*.css'], - }), - ), - ); - let usedFiles = [...inputFiles]; - for (const meta of metas) { - if (!meta.metafile) continue; - for (const inputFile in meta.metafile.inputs) { - if (!inputFile.includes('node_modules')) { - usedFiles.push(inputFile); - } - } - } - usedFiles = [...new Set(usedFiles)]; - usedFiles.sort(); - - const hashes = await Promise.all( - usedFiles.map(async (filePath) => { - const fileContent = await fs.readFile(filePath, 'utf-8'); - const hashed = hash(filePath + fileContent, { seed }); - return hashed; - }), - ); - - return hash(hashes.join(','), { seed }).toString(); -}; diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index b17d528e16..a1f62e2fc2 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -17,7 +17,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4.0.1 with: - node-version: 24 + node-version: 24.15.0 - uses: pnpm/action-setup@v4.0.0 name: Install pnpm id: pnpm-install @@ -95,7 +95,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4.0.1 with: - node-version: 24 + node-version: 24.15.0 - uses: pnpm/action-setup@v4.0.0 name: Install pnpm id: pnpm-install @@ -122,7 +122,7 @@ jobs: semcore/illustration/**/*.js semcore/illustration/**/*.mjs semcore/illustration/**/*.d.ts - key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-5 + key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-6 enableCrossOsArchive: true - name: Install dependencies run: | @@ -140,7 +140,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4.0.1 with: - node-version: 24 + node-version: 24.15.0 - uses: pnpm/action-setup@v4.0.0 name: Install pnpm id: pnpm-install @@ -167,7 +167,7 @@ jobs: semcore/illustration/**/*.js semcore/illustration/**/*.mjs semcore/illustration/**/*.d.ts - key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-5 + key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-6 enableCrossOsArchive: true - name: Install dependencies run: | @@ -223,7 +223,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4.0.1 with: - node-version: 24 + node-version: 24.15.0 - uses: pnpm/action-setup@v4.0.0 name: Install pnpm id: pnpm-install @@ -250,19 +250,22 @@ jobs: semcore/illustration/**/*.js semcore/illustration/**/*.mjs semcore/illustration/**/*.d.ts - key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-5 + key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-6 enableCrossOsArchive: true - name: Install dependencies run: | pnpm install --frozen-lockfile - - name: Test setup - id: test-setup - run: pnpm test:setup - name: Test id: test env: CHANGED_COMPONENTS: ${{ needs.defineChangedComponents.outputs.changedComponents }} - run: pnpm test:docker run $CHANGED_COMPONENTS -- --allowOnly=false + run: | + PROJECT_ARGS=() + for COMPONENT_NAME in $CHANGED_COMPONENTS; do + PROJECT_ARGS+=(--project "@semcore/$COMPONENT_NAME") + done + + pnpm test run "${PROJECT_ARGS[@]}" -- --allowOnly=false continue-on-error: true - name: Save test results as artifacts if: steps.test.outcome != 'success' @@ -274,69 +277,6 @@ jobs: - name: Fail if test step actually failed if: steps.test.outcome != 'success' run: exit 1 -# a11y-tests: -# runs-on: macos-14 -# needs: build -# steps: -# - uses: actions/checkout@v4.1.1 -# - name: Install Node.js -# uses: actions/setup-node@v4.0.1 -# with: -# node-version: 24 -# - uses: pnpm/action-setup@v4.0.0 -# name: Install pnpm -# id: pnpm-install -# with: -# version: 10.32.1 -# run_install: false -# - name: Get pnpm store directory -# id: pnpm-cache -# run: | -# echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT -# - uses: actions/cache@v4 -# name: Setup pnpm cache -# with: -# path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} -# key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} -# - name: Restore cached build -# uses: actions/cache@v4 -# id: cache-build -# with: -# path: | -# semcore/*/lib -# tools/*/lib -# semcore/icon/**/*.js -# semcore/icon/**/*.mjs -# semcore/icon/**/*.d.ts -# semcore/illustration/**/*.js -# semcore/illustration/**/*.mjs -# semcore/illustration/**/*.d.ts -# key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-5 -# - name: Install dependencies -# run: | -# pnpm install --frozen-lockfile -# - name: Install Browser -# run: npx playwright install webkit -# - name: Enable VoiceOver Automation -# uses: guidepup/setup-action@0.15.3 -# - name: Voice Over testing -# id: a11y-testing -# uses: nick-fields/retry@v2.8.3 -# with: -# timeout_minutes: 40 -# max_attempts: 3 -# command: pnpm run vo-test:ci -# continue-on-error: true -# - name: Save test results as artifacts -# if: steps.a11y-testing.outcome != 'success' -# uses: actions/upload-artifact@v4 -# with: -# name: a11y-testing-failure-output -# path: test-results -# retention-days: 3 -# - name: Fail if test step actually failed -# if: steps.a11y-testing.outcome != 'success' -# run: exit 1 browser-tests-visual: runs-on: ubuntu-latest needs: [ build, defineChangedComponents ] @@ -387,7 +327,7 @@ jobs: semcore/illustration/**/*.js semcore/illustration/**/*.mjs semcore/illustration/**/*.d.ts - key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-5 + key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-6 enableCrossOsArchive: true - name: Build basic packages if: steps.cache-build.outputs.cache-hit != 'true' @@ -462,7 +402,7 @@ jobs: semcore/illustration/**/*.js semcore/illustration/**/*.mjs semcore/illustration/**/*.d.ts - key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-5 + key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-6 - name: Build basic packages if: steps.cache-build.outputs.cache-hit != 'true' run: | @@ -532,7 +472,7 @@ jobs: semcore/illustration/**/*.js semcore/illustration/**/*.mjs semcore/illustration/**/*.d.ts - key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-5 + key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-6 enableCrossOsArchive: true - name: Build basic packages if: steps.cache-build.outputs.cache-hit != 'true' @@ -568,7 +508,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4.0.1 with: - node-version: 24 + node-version: 24.15.0 - uses: pnpm/action-setup@v4.0.0 name: Install pnpm id: pnpm-install @@ -597,13 +537,13 @@ jobs: semcore/illustration/**/*.js semcore/illustration/**/*.mjs semcore/illustration/**/*.d.ts - key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-5 + key: build-${{ hashFiles('**/pnpm-lock.yaml', '**/CHANGELOG.md') }}-6 enableCrossOsArchive: true - name: Install dependencies run: | pnpm install --frozen-lockfile --ignore-scripts - - name: Install Playwright Browsers - run: npx playwright install + - name: Install Playwright Firefox + run: pnpm exec playwright install firefox - name: NVDA setup run: pnpm nvda-test:setup - name: NVDA testing diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d451256e4..b2c9c683d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,11 +59,7 @@ To preview the website locally, run `pnpm website`. The site will be accessible ## Screen reader tests -Ensuring the accessibility of our components is a priority. We conduct automated screen reader tests, focusing on VoiceOver screen reader in Safari on macOS. Here's how to set up and run these tests: - -- To set up the environment, execute `pnpm vo-test:setup`. -- Open `voiceOver Utility > General` on your mac and enable the setting "Allow VoiceOver to be controlled with AppleScript" -- Run the tests using the command `pnpm vo-test`. +We use automated NVDA tests to check screen reader support on Windows. To prepare the environment for the first run, use `pnpm nvda-test:setup`. After setup, run the tests with `pnpm nvda-test`. ## Caveats diff --git a/package.json b/package.json index 39f81c2def..2d2d2ca53f 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,6 @@ "axe-test": "pnpm playwright test --config playwright.axe.config.ts", "axe-test:docker": "pnpm run-in-docker pnpm install-n-axe-test", "test:types": "vitest typecheck", - "vo-test:setup": "npx @guidepup/setup", - "vo-test": "playwright test --config playwright.vo.config.ts", - "vo-test:ci": "pnpm tsm --require=./.ci/tsm-filter-warnings.js ./.ci/vo-tests-ci.ts", "nvda-test:setup": "npx @guidepup/setup", "nvda-test": "playwright test --config playwright.nvda.config.ts", "run-in-docker": "docker run -e TEST_TAG=\"$TEST_TAG\" -e PLAYWRIGHT_ARGS= --rm --network host -v $(pwd)/package.json:/work/package.json -v $(pwd)/vite.config.ts:/work/vite.config.ts -v $(pwd)/playwright.browser.config.ts:/work/playwright.browser.config.ts -v $(pwd)/playwright.axe.config.ts:/work/playwright.axe.config.ts -v $(pwd)/.storybook:/work/.storybook -v $(pwd)/allure-results:/work/allure-results/ -v $(pwd)/semcore:/work/semcore/ -v $(pwd)/stories:/work/stories/ -v $(pwd)/tools:/work/tools/ -v $(pwd)/website:/work/website/ -v $(pwd)/test-results:/work/test-results/ -w /work/ intergalactic-playwright:17.0.1", diff --git a/playwright.vo.config.ts b/playwright.vo.config.ts deleted file mode 100644 index 1e684bdd94..0000000000 --- a/playwright.vo.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { screenReaderConfig } from '@guidepup/playwright'; -import type { PlaywrightTestConfig } from '@playwright/test'; -import { devices } from '@playwright/test'; -import { testPlanFilter } from 'allure-playwright/testplan'; - -const config: PlaywrightTestConfig = { - ...screenReaderConfig, - reportSlowTests: null, - workers: 1, - timeout: 2 * 60 * 1000, - testMatch: /\.vo-test.ts(x){0,1}$/, - retries: process.env.CI ? 2 : 0, - grep: testPlanFilter(), - reporter: [['list'], ['allure-playwright']], - projects: [ - { - name: 'webkit', - use: { ...devices['Desktop Safari'], headless: false, video: 'off' }, - }, - ], -}; - -export default config; diff --git a/semcore/accordion/__tests__/__snapshots__/index.test.tsx.snap b/semcore/accordion/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..70d885e92f --- /dev/null +++ b/semcore/accordion/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,22 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Accordion > Verify data-ui-name 1`] = ` +{ + "children": [ + { + "children": [ + { + "children": [], + "uiName": "Item.ToggleButton", + }, + ], + "uiName": "Item.Toggle", + }, + { + "children": [], + "uiName": "Item.Collapse", + }, + ], + "uiName": null, +} +`; diff --git a/semcore/accordion/__tests__/index.test.tsx b/semcore/accordion/__tests__/index.test.tsx index fae843ee56..fde8254dc7 100644 --- a/semcore/accordion/__tests__/index.test.tsx +++ b/semcore/accordion/__tests__/index.test.tsx @@ -1,7 +1,8 @@ import type { Intergalactic } from '@semcore/core'; +import { extractUIName } from '@semcore/testing-utils/shared/extractUINameTree.ts'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; -import { render, fireEvent, cleanup } from '@semcore/testing-utils/testing-library'; -import { expect, test, describe, beforeEach, vi, assertType } from '@semcore/testing-utils/vitest'; +import { render, cleanup, userEvent } from '@semcore/testing-utils/testing-library'; +import { expect, describe, beforeEach, vi, assertType } from '@semcore/testing-utils/vitest'; import React from 'react'; import Accordion from '../src'; @@ -10,42 +11,64 @@ describe('Accordion Dependency imports', () => { runDependencyCheckTests('accordion'); }); -describe('Accordion', () => { - describe('Types', () => { - const any: any = null; - test('Verify props nesting', () => { - const Link: Intergalactic.Component<'a', { xProp1: 1 }> = any; +describe('Accordion types', (test) => { + const any: any = null; + test('Verify props nesting', () => { + const Link: Intergalactic.Component<'a', { xProp1: 1 }> = any; - assertType(); - // @ts-expect-error - assertType(); - }); - test('Verify value&onChange relation', () => { - assertType( { }} />); + assertType(); + // @ts-expect-error + assertType(); + }); + test('Verify value&onChange relation', () => { + assertType( { }} />); + // @ts-expect-error + assertType( { }} />); + }); + test('Verify value&onChange relation with useState', () => { + const value: number[] = any; + const setValue: React.Dispatch> = any; + + assertType(); + }); + test('Verify value&children relation', () => { + assertType({(props, handlers) => any}); + assertType( + {({ value }: { value: number }) => any}, + ); + assertType( // @ts-expect-error - assertType( { }} />); - }); - test('Verify value&onChange relation with useState', () => { - const value: number[] = any; - const setValue: React.Dispatch> = any; - - assertType(); - }); - test('Verify value&children relation', () => { - assertType({(props, handlers) => any}); - assertType( - {({ value }: { value: number }) => any}, - ); - assertType( - // @ts-expect-error - {({ value }: { value: string }) => any}, - ); - }); + {({ value }: { value: string }) => any}, + ); }); +}); +describe('Accordion', (test) => { beforeEach(cleanup); - test.concurrent('Verify supports uncontrolled mode with single expandable item', () => { + test('Verify data-ui-name', ({ expect }) => { + const accordion = ( + + + + + Section 1 + + + + This is section 1 + + + + ); + + const { container } = render(accordion); + const result = extractUIName(container); + + expect(result).toMatchSnapshot(); + }); + + test.concurrent('Verify supports uncontrolled mode with single expandable item', async () => { const spy = vi.fn(); const { getByText } = render( @@ -58,17 +81,17 @@ describe('Accordion', () => { , ); - fireEvent.click(getByText('Item 1')); + await userEvent.click(getByText('Item 1')); expect(spy).toBeCalledWith(1); - fireEvent.click(getByText('Item 2')); + await userEvent.click(getByText('Item 2')); expect(spy).toBeCalledWith(2); - fireEvent.click(getByText('Item 2')); + await userEvent.click(getByText('Item 2')); expect(spy).toBeCalledWith(null); }); - test('Verify supports controlled mode with single expandable item', () => { + test('Verify supports controlled mode with single expandable item', async () => { const spy = vi.fn(); const { getByText, rerender } = render( @@ -82,7 +105,7 @@ describe('Accordion', () => { , ); - fireEvent.click(getByText('Item 1')); + await userEvent.click(getByText('Item 1')); expect(spy).toBeCalledWith(1); rerender( @@ -95,7 +118,7 @@ describe('Accordion', () => { , ); - fireEvent.click(getByText('Item 2')); + await userEvent.click(getByText('Item 2')); expect(spy).toBeCalledWith(2); rerender( @@ -108,11 +131,11 @@ describe('Accordion', () => { , ); - fireEvent.click(getByText('Item 2')); + await userEvent.click(getByText('Item 2')); expect(spy).toBeCalledWith(null); }); - test('Verify supports uncontrolled mode with multiple expandable items', () => { + test('Verify supports uncontrolled mode with multiple expandable items', async () => { const spy = vi.fn(); const { getByText } = render( @@ -125,20 +148,20 @@ describe('Accordion', () => { , ); - fireEvent.click(getByText('Item 1')); + await userEvent.click(getByText('Item 1')); expect(spy).toBeCalledWith([1]); - fireEvent.click(getByText('Item 2')); + await userEvent.click(getByText('Item 2')); expect(spy).toBeCalledWith([1, 2]); - fireEvent.click(getByText('Item 1')); + await userEvent.click(getByText('Item 1')); expect(spy).toBeCalledWith([2]); - fireEvent.click(getByText('Item 2')); + await userEvent.click(getByText('Item 2')); expect(spy).toBeCalledWith([]); }); - test('Verify supports controlled mode with multiple expandable items', () => { + test('Verify supports controlled mode with multiple expandable items', async () => { const spy = vi.fn(); const { getByText, rerender } = render( @@ -151,7 +174,7 @@ describe('Accordion', () => { , ); - fireEvent.click(getByText('Item 1')); + await userEvent.click(getByText('Item 1')); expect(spy).toBeCalledWith([1]); rerender( @@ -164,7 +187,7 @@ describe('Accordion', () => { , ); - fireEvent.click(getByText('Item 2')); + await userEvent.click(getByText('Item 2')); expect(spy).toBeCalledWith([1, 2]); rerender( @@ -177,7 +200,7 @@ describe('Accordion', () => { , ); - fireEvent.click(getByText('Item 1')); + await userEvent.click(getByText('Item 1')); expect(spy).toBeCalledWith([2]); rerender( @@ -190,7 +213,7 @@ describe('Accordion', () => { , ); - fireEvent.click(getByText('Item 2')); + await userEvent.click(getByText('Item 2')); expect(spy).toBeCalledWith([]); }); }); diff --git a/semcore/accordion/vitest.config.mts b/semcore/accordion/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/accordion/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/add-filter/__tests__/index.test.tsx b/semcore/add-filter/__tests__/index.test.tsx index 3ac4f422ca..d87ad43b60 100644 --- a/semcore/add-filter/__tests__/index.test.tsx +++ b/semcore/add-filter/__tests__/index.test.tsx @@ -1,6 +1,6 @@ import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; -import { render, fireEvent, cleanup, waitFor, act } from '@semcore/testing-utils/testing-library'; -import { expect, test, describe, beforeEach, vi } from '@semcore/testing-utils/vitest'; +import { render, cleanup, waitFor, userEvent } from '@semcore/testing-utils/testing-library'; +import { expect, describe, beforeEach, vi } from '@semcore/testing-utils/vitest'; import React from 'react'; import AddFilter from '../src'; @@ -9,7 +9,7 @@ describe('AddFilter Dependency imports', () => { runDependencyCheckTests('add-filter'); }); -describe('AddFilter', () => { +describe('AddFilter', (test) => { beforeEach(() => { cleanup(); @@ -34,7 +34,7 @@ describe('AddFilter', () => { , ); - fireEvent.click(getByText('Add filter')); + await userEvent.click(getByText('Add filter')); await waitFor(() => { expect(queryByText('Name')).toBeInTheDocument(); @@ -54,7 +54,7 @@ describe('AddFilter', () => { , ); - fireEvent.click(getByText('Add filter')); + await userEvent.click(getByText('Add filter')); await waitFor(() => { expect(getByText('name')).toBeInTheDocument(); @@ -62,56 +62,48 @@ describe('AddFilter', () => { }); }); - test('should open Select and Dropdown filters after mount timer', () => { - vi.useFakeTimers(); + test('should open Select and Dropdown filters after mount timer', async () => { + const selectVisibleChange = vi.fn(); + const dropdownVisibleChange = vi.fn(); - try { - const selectVisibleChange = vi.fn(); - const dropdownVisibleChange = vi.fn(); - - const { queryByText, getByText } = render( - <> - - - - Blue - - - - - {}}> - Keywords - - - Dropdown content - - - , - ); - - expect(queryByText('Blue')).not.toBeInTheDocument(); - expect(queryByText('Dropdown content')).not.toBeInTheDocument(); - expect(selectVisibleChange).not.toHaveBeenCalled(); - expect(dropdownVisibleChange).not.toHaveBeenCalled(); + const { queryByText, getByText } = render( + <> + + + + Blue + + + + + {}}> + Keywords + + + Dropdown content + + + , + ); - act(() => { - vi.advanceTimersByTime(0); - }); + expect(queryByText('Blue')).not.toBeInTheDocument(); + expect(queryByText('Dropdown content')).not.toBeInTheDocument(); + expect(selectVisibleChange).not.toHaveBeenCalled(); + expect(dropdownVisibleChange).not.toHaveBeenCalled(); + await waitFor(() => { expect(selectVisibleChange).toHaveBeenCalledWith(true); expect(dropdownVisibleChange).toHaveBeenCalledWith(true); expect(getByText('Blue')).toBeInTheDocument(); expect(getByText('Dropdown content')).toBeInTheDocument(); - } finally { - vi.useRealTimers(); - } + }); }); }); diff --git a/semcore/add-filter/vitest.config.mts b/semcore/add-filter/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/add-filter/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/badge/vitest.config.mts b/semcore/badge/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/badge/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/base-components/__tests__/Animation.test.jsx b/semcore/base-components/__tests__/Animation.test.jsx index 1cbf8b42a0..dc8a58779c 100644 --- a/semcore/base-components/__tests__/Animation.test.jsx +++ b/semcore/base-components/__tests__/Animation.test.jsx @@ -1,32 +1,57 @@ import { afterEach, expect, test, describe, vi } from '@semcore/testing-utils/vitest'; -import { cleanup, render, screen } from '@testing-library/react'; import React from 'react'; +import { flushSync } from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { Animation } from '../src'; +let root = null; +let container = null; + +const renderSync = (element) => { + container = document.createElement('div'); + root = createRoot(container); + + document.body.appendChild(container); + + flushSync(() => { + root.render(element); + }); + + return container; +}; + describe('Animation', () => { afterEach(() => { - cleanup(); + if (root && container) { + flushSync(() => { + root.unmount(); + }); + container.remove(); + root = null; + container = null; + } + vi.restoreAllMocks(); vi.useRealTimers(); }); test('Verify not renders when visible is false and preserveNode is false', () => { - render( + const container = renderSync( Content , ); - expect(screen.queryByText('Content')).not.toBeInTheDocument(); + expect(container.textContent).not.toContain('Content'); }); test('Verify preserve node when preserveNode is true', () => { - render( + const container = renderSync( Content , ); - expect(screen.getByText('Content')).toBeInTheDocument(); + expect(container.textContent).toContain('Content'); }); test('Verify animationsDisabled fallback uses zero timeout', () => { @@ -34,7 +59,7 @@ describe('Animation', () => { const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout'); setTimeoutSpy.mockClear(); - render( + renderSync( Content , @@ -50,7 +75,7 @@ describe('Animation', () => { const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout'); setTimeoutSpy.mockClear(); - render( + renderSync( Content , diff --git a/semcore/base-components/__tests__/Grid.test.jsx b/semcore/base-components/__tests__/Grid.test.jsx deleted file mode 100644 index 01e3771f6f..0000000000 --- a/semcore/base-components/__tests__/Grid.test.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as sharedTests from '@semcore/testing-utils/shared-tests'; -import { cleanup } from '@semcore/testing-utils/testing-library'; -import { describe, beforeEach } from '@semcore/testing-utils/vitest'; -import React from 'react'; - -import { Col, Row } from '../src'; - -const { shouldSupportClassName, shouldSupportRef } = sharedTests; - -describe('Grid', () => { - beforeEach(cleanup); - shouldSupportClassName(Row); - shouldSupportRef(Row); - - shouldSupportClassName(Col, Row); - shouldSupportRef(Col, Row); -}); diff --git a/semcore/base-components/__tests__/flex-box.test.jsx b/semcore/base-components/__tests__/flex-box.test.jsx index 30f9b76007..5edf876943 100644 --- a/semcore/base-components/__tests__/flex-box.test.jsx +++ b/semcore/base-components/__tests__/flex-box.test.jsx @@ -1,18 +1,16 @@ -import * as sharedTests from '@semcore/testing-utils/shared-tests'; import { cleanup, render } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach } from '@semcore/testing-utils/vitest'; import React from 'react'; import { Box, Flex } from '../src'; -const { shouldSupportClassName, shouldSupportRef } = sharedTests; +const SpanTag = function SpanTag(props) { + return ; +}; describe('Flex', () => { beforeEach(cleanup); - shouldSupportClassName(Flex); - shouldSupportRef(Flex); - test.concurrent('Verify supports css property', async () => { const MAP_CSS = { reverse: { @@ -80,28 +78,16 @@ describe('Flex', () => { describe('Box', () => { beforeEach(cleanup); - shouldSupportClassName(Box); - shouldSupportRef(Box); - - test('Verify \'tag\' prop', () => { + test('Verify supports custom tag', () => { const { getByTestId } = render( - - tag - , + <> + + + , ); - expect(getByTestId('box').tagName).toBe('SPAN'); - }); - test('Verify \'tag\' prop component', () => { - const Span = function (props) { - return ; - }; - const { getByTestId } = render( - - tag - , - ); - expect(getByTestId('box').tagName).toBe('SPAN'); + expect(getByTestId('button-box').tagName).toBe('BUTTON'); + expect(getByTestId('span-box').tagName).toBe('SPAN'); }); test('Verify clear non html props', () => { diff --git a/semcore/base-components/__tests__/hint.test.tsx b/semcore/base-components/__tests__/hint.test.tsx index db81eaea95..0f753b8127 100644 --- a/semcore/base-components/__tests__/hint.test.tsx +++ b/semcore/base-components/__tests__/hint.test.tsx @@ -1,5 +1,10 @@ +import { + cleanup, + render, + userEvent, + waitFor, +} from '@semcore/testing-utils/testing-library'; import { expect, test, describe, vi, afterEach } from '@semcore/testing-utils/vitest'; -import { render, fireEvent, waitFor, cleanup } from '@testing-library/react'; import React, { useRef } from 'react'; import { Hint, PortalProvider } from '../src'; @@ -32,12 +37,12 @@ describe('Hint', () => { expect(document.body.querySelector('[data-testid="hint"]')).toBeNull(); - fireEvent.click(getByTestId('toggle')); + await userEvent.click(getByTestId('toggle')); await waitFor(() => { expect(document.body.querySelector('[data-testid="hint"]')).not.toBeNull(); }); - fireEvent.click(getByTestId('toggle')); + await userEvent.click(getByTestId('toggle')); await waitFor(() => { expect(document.body.querySelector('[data-testid="hint"]')).toBeNull(); }); @@ -60,7 +65,8 @@ describe('Hint', () => { const { getByTestId } = render(); - fireEvent.mouseEnter(getByTestId('trigger')); + const trigger = getByTestId('trigger'); + await userEvent.hover(trigger); await waitFor(() => { expect(handleChange).toHaveBeenCalledTimes(1); @@ -75,7 +81,7 @@ describe('Hint', () => { expect((hint as HTMLElement).style.cssText).toContain('--keyframesInitialize'); }); - fireEvent(getByTestId('trigger'), new MouseEvent('mouseleave', { bubbles: false })); + await userEvent.unhover(trigger); await waitFor(() => { expect(handleChange).toHaveBeenCalledTimes(2); @@ -101,8 +107,7 @@ describe('Hint', () => { expect(document.body.querySelector('[data-testid="hint"]')).not.toBeNull(); }); - test('Should not show hint when children is false', () => { - vi.useFakeTimers(); + test('Should not show hint when children is false', async () => { const handleChange = vi.fn(); const TestComponent = () => { @@ -119,14 +124,13 @@ describe('Hint', () => { const { getByTestId } = render(); - fireEvent.mouseEnter(getByTestId('trigger')); - vi.advanceTimersByTime(60); + await userEvent.hover(getByTestId('trigger')); + await new Promise((resolve) => setTimeout(resolve, 60)); expect(handleChange).not.toHaveBeenCalled(); }); - test('Should not show hint when children is empty string', () => { - vi.useFakeTimers(); + test('Should not show hint when children is empty string', async () => { const handleChange = vi.fn(); const emptyString = ''; @@ -144,8 +148,8 @@ describe('Hint', () => { const { getByTestId } = render(); - fireEvent.mouseEnter(getByTestId('trigger')); - vi.advanceTimersByTime(60); + await userEvent.hover(getByTestId('trigger')); + await new Promise((resolve) => setTimeout(resolve, 60)); expect(handleChange).not.toHaveBeenCalled(); }); diff --git a/semcore/base-components/__tests__/outside-click.test.tsx b/semcore/base-components/__tests__/outside-click.test.tsx index 72d5e5217b..35924a327d 100644 --- a/semcore/base-components/__tests__/outside-click.test.tsx +++ b/semcore/base-components/__tests__/outside-click.test.tsx @@ -1,4 +1,4 @@ -import { cleanup, fireEvent, render } from '@semcore/testing-utils/testing-library'; +import { cleanup, render, userEvent } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach, vi } from '@semcore/testing-utils/vitest'; import React from 'react'; @@ -7,16 +7,23 @@ import { OutsideClick } from '../src'; describe('OutsideClick', () => { beforeEach(cleanup); - test.concurrent('Verify call onOutsideClick if event outside', () => { + const clickPointer = async (target: Element) => { + await userEvent.pointer([ + { keys: '[MouseLeft>]', target }, + { keys: '[/MouseLeft]', target }, + ]); + }; + + test.sequential('Verify call onOutsideClick if event outside', async () => { const onOutsideClick = vi.fn(); render(); - fireEvent.mouseUp(document.body); + await clickPointer(document.body); expect(onOutsideClick).toBeCalled(); }); - test.concurrent('Verify excludeRefs with single and multiple elements', () => { + test.sequential('Verify excludeRefs with single and multiple elements', async () => { const onOutsideClick = vi.fn(); const outsideRef1 = React.createRef(); const outsideRef2 = React.createRef(); @@ -38,14 +45,14 @@ describe('OutsideClick', () => { , ); - fireEvent.mouseUp(getByTestId('outside1').childNodes[0]); - fireEvent.mouseUp(getByTestId('outside2').childNodes[0]); - fireEvent.mouseUp(document.body.childNodes[0]); + await clickPointer(getByTestId('outside1')); + await clickPointer(getByTestId('outside2')); + await clickPointer(document.body); expect(onOutsideClick).not.toBeCalled(); }); - test.concurrent('Verify excludeRefs', () => { + test.sequential('Verify excludeRefs', async () => { const onOutsideClick = vi.fn(); const outsideRef = React.createRef(); const { getByTestId } = render( @@ -57,12 +64,12 @@ describe('OutsideClick', () => { , ); - fireEvent.mouseUp(getByTestId('outside').childNodes[0]); + await clickPointer(getByTestId('outside')); expect(onOutsideClick).not.toBeCalled(); }); - test.concurrent('Verify excludeRefs node', () => { + test.sequential('Verify excludeRefs node', async () => { const onOutsideClick = vi.fn(); render( <> @@ -70,12 +77,12 @@ describe('OutsideClick', () => { , ); - fireEvent.mouseUp(document.body.childNodes[0]); + await clickPointer(document.body); expect(onOutsideClick).not.toBeCalled(); }); - test.concurrent('Verify calls onOutsideClick by click outside with excludeRefs', () => { + test.sequential('Verify calls onOutsideClick by click outside with excludeRefs', async () => { const onOutsideClick = vi.fn(); const outsideRef = React.createRef(); render( @@ -89,14 +96,14 @@ describe('OutsideClick', () => { , ); - fireEvent.mouseUp(document.body); + await clickPointer(document.body); expect(onOutsideClick).toBeCalled(); }); - test.concurrent( + test.sequential( 'Verify does not call onOutsideClick if mousedown inside and mouseup outside', - () => { + async () => { const onOutsideClick = vi.fn(); const { getByTestId } = render( @@ -104,16 +111,18 @@ describe('OutsideClick', () => { , ); - fireEvent.mouseDown(getByTestId('child')); - fireEvent.mouseUp(document.body); + await userEvent.pointer([ + { keys: '[MouseLeft>]', target: getByTestId('child') }, + { keys: '[/MouseLeft]', target: document.body }, + ]); expect(onOutsideClick).not.toBeCalled(); }, ); - test.concurrent( + test.sequential( 'Verify does not call onOutsideClick if mousedown outside and mouseup inside', - () => { + async () => { const onOutsideClick = vi.fn(); const { getByTestId } = render( @@ -121,8 +130,10 @@ describe('OutsideClick', () => { , ); - fireEvent.mouseDown(document.body); - fireEvent.mouseUp(getByTestId('child2')); + await userEvent.pointer([ + { keys: '[MouseLeft>]', target: document.body }, + { keys: '[/MouseLeft]', target: getByTestId('child2') }, + ]); expect(onOutsideClick).not.toBeCalled(); }, diff --git a/semcore/base-components/__tests__/popper.test.tsx b/semcore/base-components/__tests__/popper.test.tsx index 72347fc6e5..a7329bfffc 100644 --- a/semcore/base-components/__tests__/popper.test.tsx +++ b/semcore/base-components/__tests__/popper.test.tsx @@ -1,9 +1,4 @@ -import { - cleanup, - render, - fireEvent, - act, -} from '@semcore/testing-utils/testing-library'; +import { cleanup, render, userEvent } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach, vi } from '@semcore/testing-utils/vitest'; import React from 'react'; @@ -58,8 +53,7 @@ describe('Popper', () => { }); describe('onVisibleChange callback', () => { - test('Verify onVisibleChange is called on trigger click', () => { - vi.useFakeTimers(); + test('Verify onVisibleChange is called on trigger click', async () => { const onVisibleChange = vi.fn(); const { getByText } = render( @@ -69,13 +63,9 @@ describe('Popper', () => { , ); - fireEvent.click(getByText('Trigger')); - act(() => { - vi.advanceTimersByTime(0); - }); + await userEvent.click(getByText('Trigger')); expect(onVisibleChange).toHaveBeenCalledWith(true, expect.anything()); - vi.useRealTimers(); }); }); }); diff --git a/semcore/base-components/__tests__/scroll-area.test.tsx b/semcore/base-components/__tests__/scroll-area.test.tsx index a828080131..7782e2b482 100644 --- a/semcore/base-components/__tests__/scroll-area.test.tsx +++ b/semcore/base-components/__tests__/scroll-area.test.tsx @@ -1,16 +1,13 @@ -import * as sharedTests from '@semcore/testing-utils/shared-tests'; -import { cleanup, render, fireEvent, waitFor } from '@semcore/testing-utils/testing-library'; +import { act, cleanup, render, waitFor } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach, vi } from '@semcore/testing-utils/vitest'; import React from 'react'; import { ScrollArea, eventCalculate } from '../src'; -const { shouldSupportClassName, shouldSupportRef } = sharedTests; - describe('ScrollArea', () => { beforeEach(cleanup); - test.concurrent('Verify support render function for children', () => { + test.sequential('Verify support render function for children', () => { const component = ( {() => { @@ -25,10 +22,7 @@ describe('ScrollArea', () => { ).toBe(1); }); - shouldSupportClassName(ScrollArea); - shouldSupportRef(ScrollArea); - - test.concurrent('Verify trigger calculate event on container', () => { + test.sequential('Verify trigger calculate event on container', () => { const { getByTestId } = render( @@ -39,12 +33,12 @@ describe('ScrollArea', () => { const eventListener = vi.fn(); container.addEventListener('calculate', eventListener); - fireEvent(container, eventCalculate!); + container.dispatchEvent(eventCalculate!); expect(eventListener).toHaveBeenCalled(); }); - test.concurrent('Verify correctly set shadows based on scroll position', () => { + test.sequential('Verify correctly set shadows based on scroll position', async () => { const { getByTestId } = render( @@ -57,14 +51,20 @@ describe('ScrollArea', () => { Object.defineProperty(container, 'scrollWidth', { value: 300, writable: true }); Object.defineProperty(container, 'clientWidth', { value: 200, writable: true }); - fireEvent.scroll(container); + await act(async () => { + container.dispatchEvent(new Event('scroll')); + }); + + await waitFor(() => { + const scrollArea = container.closest('[data-ui-name="ScrollArea"]'); + const horizontalShadow = scrollArea?.querySelector('[class*="SShadowHorizontal"]'); - waitFor(() => { - expect(container).toHaveAttribute('data-shadow-horizontal', 'median'); + expect(horizontalShadow).toBeInstanceOf(HTMLElement); + expect((horizontalShadow as HTMLElement).className).toContain('_position_median_'); }); }); - test.concurrent('Verify keep focused element visible', () => { + test.sequential('Verify keep focused element visible', () => { const { getByTestId } = render( @@ -80,10 +80,3 @@ describe('ScrollArea', () => { expect(input.getBoundingClientRect().top).toBeGreaterThanOrEqual(0); }); }); - -describe('ScrollArea.Container', () => { - beforeEach(cleanup); - - shouldSupportClassName(ScrollArea.Container, ScrollArea); - shouldSupportRef(ScrollArea.Container, ScrollArea); -}); diff --git a/semcore/base-components/vitest.config.mts b/semcore/base-components/vitest.config.mts new file mode 100644 index 0000000000..c5ee41df6b --- /dev/null +++ b/semcore/base-components/vitest.config.mts @@ -0,0 +1,18 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + '__tests__/**/*.test.jsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/base-trigger/__tests__/index.test.tsx b/semcore/base-trigger/__tests__/index.test.tsx index a2dc1caa50..f9fac0758c 100644 --- a/semcore/base-trigger/__tests__/index.test.tsx +++ b/semcore/base-trigger/__tests__/index.test.tsx @@ -1,30 +1,17 @@ -import * as sharedTests from '@semcore/testing-utils/shared-tests'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; import { cleanup, render, userEvent } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach } from '@semcore/testing-utils/vitest'; import React from 'react'; -import BaseTrigger, { ButtonTrigger, FilterTrigger, LinkTrigger } from '../src'; +import { ButtonTrigger } from '../src'; describe('BaseTrigger Dependency imports', () => { runDependencyCheckTests('base-trigger'); }); -const { shouldSupportClassName, shouldSupportRef } = sharedTests; - -describe('BaseTrigger', () => { - beforeEach(cleanup); - - shouldSupportClassName(BaseTrigger); - shouldSupportRef(BaseTrigger); -}); - describe('ButtonTrigger', () => { beforeEach(cleanup); - shouldSupportClassName(ButtonTrigger); - shouldSupportRef(ButtonTrigger); - test.concurrent('Should work as button with labels', async () => { const component = ( <> @@ -42,17 +29,3 @@ describe('ButtonTrigger', () => { expect(getByTestId('buttonTrigger')).toHaveFocus(); }); }); - -describe('FilterTrigger', () => { - beforeEach(cleanup); - - shouldSupportClassName(FilterTrigger); - shouldSupportRef(FilterTrigger); -}); - -describe('LinkTrigger', () => { - beforeEach(cleanup); - - shouldSupportClassName(LinkTrigger); - shouldSupportRef(LinkTrigger); -}); diff --git a/semcore/base-trigger/vitest.config.mts b/semcore/base-trigger/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/base-trigger/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/breadcrumbs/__tests__/index.test.tsx b/semcore/breadcrumbs/__tests__/index.test.tsx index e81b682eec..fab8427ac6 100644 --- a/semcore/breadcrumbs/__tests__/index.test.tsx +++ b/semcore/breadcrumbs/__tests__/index.test.tsx @@ -1,6 +1,5 @@ import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; import { describe } from '@semcore/testing-utils/vitest'; -import React from 'react'; describe('breadcrumbs Dependency imports', () => { runDependencyCheckTests('breadcrumbs'); diff --git a/semcore/breadcrumbs/vitest.config.mts b/semcore/breadcrumbs/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/breadcrumbs/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/bulk-textarea/vitest.config.mts b/semcore/bulk-textarea/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/bulk-textarea/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/button/__tests__/index.test.tsx b/semcore/button/__tests__/index.test.tsx index 6df32c40f8..b1e25288cf 100644 --- a/semcore/button/__tests__/index.test.tsx +++ b/semcore/button/__tests__/index.test.tsx @@ -1,15 +1,56 @@ +import Link from '@semcore/link'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; -import { render } from '@semcore/testing-utils/testing-library'; -import { expect, test, describe } from '@semcore/testing-utils/vitest'; +import { cleanup, render, userEvent } from '@semcore/testing-utils/testing-library'; +import { beforeEach, expect, test, describe, vi } from '@semcore/testing-utils/vitest'; import React from 'react'; -import Button from '../src'; +import Button, { } from '../src'; describe('Button Dependency imports', () => { runDependencyCheckTests('button'); }); describe('Button', () => { + beforeEach(cleanup); + + test('Verify supports user click handler', async () => { + const spy = vi.fn(); + const { getByTestId } = render( + , + ); + + await userEvent.click(getByTestId('button')); + expect(spy).toHaveBeenCalledTimes(1); + }); + + test('Verify supports disabled state', () => { + const { getByTestId } = render( + , + ); + + expect((getByTestId('button') as HTMLButtonElement).disabled).toBe(true); + }); + + test('Verify supports custom tag', () => { + const { getByTestId } = render( + <> + + + , + ); + + expect(getByTestId('link-button').tagName).toBe('A'); + expect(getByTestId('semcore-link-button').tagName).toBe('A'); + }); + test('Verify loading attributes', () => { const { queryByTestId } = render( + `; + + expect(getAccessibleName(document.querySelector('button'))).toBe( + 'Labelled name', + ); + }); + + test('Verify returns aria-label', () => { + const button = document.createElement('button'); + button.setAttribute('aria-label', 'Aria name'); + + expect(getAccessibleName(button)).toBe('Aria name'); + }); + + test('Verify returns associated label text', () => { + document.body.innerHTML = ` + + + `; + + expect(getAccessibleName(document.querySelector('input'))).toBe('Input name'); + }); + + test('Verify falls back to title', () => { + const button = document.createElement('button'); + button.title = 'Title name'; + + expect(getAccessibleName(button)).toBe('Title name'); + }); +}); + describe('assignHandlers', () => { test('Verify merge event handlers from source and props', () => { const mockFn1 = vi.fn(); diff --git a/semcore/core/src/utils/logger.ts b/semcore/core/src/utils/logger.ts index fbf95392c5..ebb0609b4b 100644 --- a/semcore/core/src/utils/logger.ts +++ b/semcore/core/src/utils/logger.ts @@ -1,4 +1,4 @@ -const DEV = process.env.NODE_ENV !== 'production'; +const DEV = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test'; class Logger { private logger: any; diff --git a/semcore/core/vitest.config.mts b/semcore/core/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/core/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/counter/__tests__/index.test.tsx b/semcore/counter/__tests__/index.test.tsx index a0b3231db0..af56cb1bb8 100644 --- a/semcore/counter/__tests__/index.test.tsx +++ b/semcore/counter/__tests__/index.test.tsx @@ -1,6 +1,5 @@ import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; import { describe } from '@semcore/testing-utils/vitest'; -import React from 'react'; describe('Counter Dependency imports', () => { runDependencyCheckTests('counter'); diff --git a/semcore/counter/vitest.config.mts b/semcore/counter/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/counter/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/d3-chart/__tests__/index.test.tsx b/semcore/d3-chart/__tests__/index.test.tsx index 863bacfffc..d766dee664 100644 --- a/semcore/d3-chart/__tests__/index.test.tsx +++ b/semcore/d3-chart/__tests__/index.test.tsx @@ -1,5 +1,4 @@ import Icon from '@semcore/icon/Video/m'; -import * as sharedTests from '@semcore/testing-utils/shared-tests'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; import { render, fireEvent, cleanup, queryAllByAttribute, queryByAttribute, userEvent } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach, vi, afterEach } from '@semcore/testing-utils/vitest'; @@ -18,8 +17,6 @@ import { import { PlotA11yView } from '../src/a11y/PlotA11yView'; import { getIndexFromData } from '../src/utils'; -const { shouldSupportClassName, shouldSupportRef } = sharedTests; - const width = 500; const height = 500; const date = new Date(); @@ -107,8 +104,6 @@ describe('d3-chart Dependency imports', () => { describe('Plot', () => { beforeEach(cleanup); - shouldSupportClassName(PlotTest); - shouldSupportRef(PlotTest); test.concurrent('Should support render null', () => { const { queryByText } = render(Test); @@ -119,9 +114,6 @@ describe('Plot', () => { describe('YAxis', () => { beforeEach(cleanup); - shouldSupportClassName(YAxis, PlotTest); - shouldSupportRef(YAxis, PlotTest); - test( 'Should support call children function for Grid how many ticks are passed', () => { @@ -190,9 +182,6 @@ describe('YAxis', () => { describe('XAxis', () => { beforeEach(cleanup); - shouldSupportClassName(XAxis, PlotTest); - shouldSupportRef(XAxis, PlotTest); - test.concurrent('should support hover for custom XAxis.Ticks', () => { vi.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => (cb as any)()); // const bisect = bisector((d) => d.x).center; @@ -228,6 +217,7 @@ describe('XAxis', () => { , ); + // Keep fireEvent: this test asserts one exact mouseMove emission. fireEvent.mouseMove(getAllByTestId('tick')[9]); expect(eventEmitter.emit).toHaveBeenCalledTimes(2); // onMouseMoveRoot, onMouseLeaveChart (window.requestAnimationFrame as any).mockRestore(); @@ -390,6 +380,7 @@ describe('Chart.Area', () => { const firstCallIndex = 0; const secondCallIndex = dots.length - 1; + // Keep fireEvent: chart index resolution depends on explicit SVG coordinates. [firstCallIndex, secondCallIndex].forEach((index) => { fireEvent.click(dots[index], { clientX: dotsCoords[index].x, @@ -402,7 +393,7 @@ describe('Chart.Area', () => { expect(onClickHandler.mock.calls[1][0]).toBe(secondCallIndex); }); - test.concurrent('should not throw if onClickArea is not provided', () => { + test.concurrent('should not throw if onClickArea is not provided', async () => { const { container } = render( { const dots = queryAllByAttribute('data-ui-name', container, 'Area.Dots'); expect(dots.length).toBeGreaterThan(0); - expect(() => fireEvent.click(dots[0])).not.toThrow(); + await userEvent.click(dots[0]); }); }); @@ -441,6 +432,7 @@ describe('Chart.Bubble', () => { const firstCallIndex = 0; const secondCallIndex = bubbles.length - 1; + // Keep fireEvent: chart index resolution depends on explicit SVG coordinates. [firstCallIndex, secondCallIndex].forEach((index) => { fireEvent.click(bubbles[index], { clientX: bubblesCords[index].x, @@ -461,6 +453,7 @@ describe('Chart.Bubble', () => { const bubbles = queryAllByAttribute('data-ui-name', container, 'Bubble.Circle'); expect(bubbles.length).toBeGreaterThan(0); + // Keep fireEvent: userEvent triggers OutsideClick internals for this SVG target in jsdom. expect(() => fireEvent.click(bubbles[0])).not.toThrow(); }); }); @@ -468,7 +461,7 @@ describe('Chart.Bubble', () => { describe('Chart.Donut', () => { beforeEach(cleanup); - test.concurrent('should call onClickPie and return correct data key', () => { + test.concurrent('should call onClickPie and return correct data key', async () => { const onClickHandler = vi.fn(); const { container } = render( @@ -479,15 +472,15 @@ describe('Chart.Donut', () => { expect(pies.length).toBe(Object.keys(ChartOptions.donut.data).length); - fireEvent.click(pies[0]); - fireEvent.click(pies[pies.length - 1]); + await userEvent.click(pies[0]); + await userEvent.click(pies[pies.length - 1]); expect(onClickHandler).toBeCalledTimes(2); expect(onClickHandler.mock.calls[0][0]).toBe('a'); expect(onClickHandler.mock.calls[1][0]).toBe('c'); }); - test.concurrent('should not throw if onClickPie is not provided', () => { + test.concurrent('should not throw if onClickPie is not provided', async () => { const { container } = render( , ); @@ -495,7 +488,7 @@ describe('Chart.Donut', () => { const pies = queryAllByAttribute('data-ui-name', container, 'Donut.Pie'); expect(pies.length).toBeGreaterThan(0); - expect(() => fireEvent.click(pies[0])).not.toThrow(); + await userEvent.click(pies[0]); }); }); @@ -529,6 +522,7 @@ describe('Chart.Line', () => { const firstCallIndex = 0; const secondCallIndex = dots.length - 1; + // Keep fireEvent: chart index resolution depends on explicit SVG coordinates. [firstCallIndex, secondCallIndex].forEach((index) => { fireEvent.click(dots[index], { clientX: dotsCoords[index].x, @@ -541,7 +535,7 @@ describe('Chart.Line', () => { expect(onClickHandler.mock.calls[1][0]).toBe(secondCallIndex); }); - test.concurrent('should not throw if onClickLine is not provided', () => { + test.concurrent('should not throw if onClickLine is not provided', async () => { const { container } = render( { const dots = queryAllByAttribute('data-ui-name', container, 'Line.Dots'); expect(dots.length).toBeGreaterThan(0); - expect(() => fireEvent.click(dots[0])).not.toThrow(); + await userEvent.click(dots[0]); }); }); @@ -581,7 +575,7 @@ describe('Chart.Radar', () => { const radar = queryByAttribute('data-ui-name', container, 'Radar'); expect(radar).toBeTruthy(); - // idk, just simulated the way the first and second segments are clicked. + // Keep fireEvent: radar segment selection depends on explicit SVG coordinates. fireEvent.click(radar!, { clientX: 250, clientY: 125 }); fireEvent.click(radar!, { clientX: 375, clientY: 200 }); @@ -604,6 +598,7 @@ describe('Chart.Radar', () => { const radar = queryByAttribute('data-ui-name', container, 'Radar'); expect(radar).toBeTruthy(); + // Keep fireEvent: radar segment selection depends on explicit SVG coordinates. expect(() => fireEvent.click(radar!, { clientX: 250, clientY: 125 })).not.toThrow(); }); }); @@ -636,6 +631,7 @@ describe('Chart.ScatterPlot', () => { const firstCallIndex = 0; const secondCallIndex = scatterItems.length - 1; + // Keep fireEvent: chart index resolution depends on explicit SVG coordinates. [firstCallIndex, secondCallIndex].forEach((index) => { fireEvent.click(scatterItems[index], { clientX: scatterItemsCoords[index].x, @@ -662,6 +658,7 @@ describe('Chart.ScatterPlot', () => { const scatterItems = queryAllByAttribute('data-ui-name', container, 'ScatterPlot'); expect(scatterItems.length).toBe(ChartOptions.scatterPlot.data.length); + // Keep fireEvent: userEvent triggers OutsideClick internals for this SVG target in jsdom. expect(() => fireEvent.click(scatterItems[0])).not.toThrow(); }); }); @@ -696,6 +693,7 @@ describe('Chart.Venn', () => { const firstCallIndex = 0; const secondCallIndex = circles.length - 1; + // Keep fireEvent: chart key resolution depends on explicit SVG coordinates. [firstCallIndex, secondCallIndex].forEach((index) => { fireEvent.click(circles[index], { clientX: circlesCoords[index].x, @@ -708,7 +706,7 @@ describe('Chart.Venn', () => { expect(onClickHandler.mock.calls[1][0]).toBe('U'); }); - test.concurrent('should not throw if onClickVennItem is not provided', () => { + test.concurrent('should not throw if onClickVennItem is not provided', async () => { const { container } = render( { const circles = queryAllByAttribute('data-ui-name', container, 'Venn.Circle'); expect(circles.length).toBe(Object.keys(ChartOptions.venn.legendMap).length); - expect(() => fireEvent.click(circles[0])).not.toThrow(); + await userEvent.click(circles[0]); }); }); @@ -753,6 +751,7 @@ describe('Chart.Cigarette', () => { ); const svg = getByLabelText('Chart'); + // Keep fireEvent: tooltip percent calculation depends on a precise SVG mouse position. fireEvent.mouseMove(svg, { clientX: 200, clientY: 14, diff --git a/semcore/d3-chart/vitest.config.mts b/semcore/d3-chart/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/d3-chart/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/data-table/__tests__/__snapshots__/index.test.tsx.snap b/semcore/data-table/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..08633d77d8 --- /dev/null +++ b/semcore/data-table/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,163 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Data-table data-ui-name tests > test snap 1`] = ` +{ + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": undefined, + "uiName": "Head.Group", + }, + { + "children": undefined, + "uiName": "Head.Column", + }, + { + "children": undefined, + "uiName": "Head.Column", + }, + ], + "uiName": "Box", + }, + ], + "uiName": "DataTable.Head", + }, + { + "children": undefined, + "uiName": "Box", + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": undefined, + "uiName": "Row.Cell", + }, + ], + "uiName": "Box", + }, + { + "children": [ + { + "children": undefined, + "uiName": "Row.Cell", + }, + ], + "uiName": "Box", + }, + ], + "uiName": "Body.Row", + }, + ], + "uiName": "DataTable.Body", + }, + ], + "uiName": "DataTable", + }, + ], + }, + ], + "uiName": "ScrollArea.Container", + }, + ], + "uiName": "ScrollArea", + }, + ], +} +`; + +exports[`DataTable > Verify data-ui-name 1`] = ` +{ + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "uiName": "Head.Group", + }, + { + "children": [], + "uiName": "Head.Column", + }, + { + "children": [], + "uiName": "Head.Column", + }, + ], + "uiName": "Box", + }, + ], + "uiName": "DataTable.Head", + }, + { + "children": [], + "uiName": "Box", + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "uiName": "Row.Cell", + }, + ], + "uiName": "Box", + }, + { + "children": [ + { + "children": [], + "uiName": "Row.Cell", + }, + ], + "uiName": "Box", + }, + ], + "uiName": "Body.Row", + }, + ], + "uiName": "DataTable.Body", + }, + ], + "uiName": "DataTable", + }, + ], + "uiName": null, + }, + ], + "uiName": "ScrollArea.Container", + }, + ], + "uiName": "ScrollArea", + }, + ], + "uiName": null, +} +`; diff --git a/semcore/data-table/__tests__/index.test.tsx b/semcore/data-table/__tests__/index.test.tsx index 56abf58866..7e6d8e7697 100644 --- a/semcore/data-table/__tests__/index.test.tsx +++ b/semcore/data-table/__tests__/index.test.tsx @@ -1,7 +1,8 @@ import type { Intergalactic } from '@semcore/core'; +import { extractUIName } from '@semcore/testing-utils/shared/extractUINameTree.ts'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; import { render, cleanup } from '@semcore/testing-utils/testing-library'; -import { expect, test, describe, beforeEach, vi, assertType, afterEach } from '@semcore/testing-utils/vitest'; +import { expect, describe, beforeEach, vi, assertType, afterEach } from '@semcore/testing-utils/vitest'; import React from 'react'; import { DataTable } from '../src'; @@ -11,8 +12,32 @@ describe('data-table Dependency imports', () => { runDependencyCheckTests('data-table'); }); -describe('DataTable', () => { - describe('types', () => { +describe('DataTable', (test) => { + test('Verify data-ui-name', ({ expect }) => { + const dataTable = ( + + ); + + const { container } = render(dataTable); + const result = extractUIName(container); + + expect(result).toMatchSnapshot(); + }); + + describe('types', (test) => { const any: any = null; test('props nesting', () => { const Link: Intergalactic.Component<'a', { xProp1: 1 }> = any; @@ -122,7 +147,7 @@ describe('DataTable', () => { beforeEach(cleanup); }); -describe('DataTable.Cell', () => { +describe('DataTable.Cell', (test) => { beforeEach(() => { cleanup(); }); diff --git a/semcore/data-table/vitest.config.mts b/semcore/data-table/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/data-table/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/date-picker/__tests__/index.test.tsx b/semcore/date-picker/__tests__/index.test.tsx index 089f14fd83..61698c3eb0 100644 --- a/semcore/date-picker/__tests__/index.test.tsx +++ b/semcore/date-picker/__tests__/index.test.tsx @@ -1,6 +1,6 @@ import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; -import { cleanup, render, fireEvent, act, userEvent } from '@semcore/testing-utils/testing-library'; -import { expect, test, describe, beforeEach, vi } from '@semcore/testing-utils/vitest'; +import { cleanup, render, userEvent } from '@semcore/testing-utils/testing-library'; +import { expect, test, describe, beforeEach, afterEach, vi } from '@semcore/testing-utils/vitest'; import React from 'react'; import { mockDate, RealDate } from './utils'; @@ -18,14 +18,15 @@ describe('DatePicker', () => { global.Date = RealDate; cleanup(); }); + afterEach(cleanup); - test('Verify supports onChange with format time 00:00:00:000', () => { + test('Verify supports onChange with format time 00:00:00:000', async () => { const spy = vi.fn(); mockDate('2020-02-10T12:00:00.808Z'); const { getByText } = render(); - fireEvent.click(getByText('Today')); + await userEvent.click(getByText('Today')); expect(spy).toBeCalledWith(new Date(new Date().setHours(0, 0, 0, 0))); }); @@ -38,21 +39,15 @@ describe('DatePicker', () => { expect(aprilInstance.getByText('April 2020')).toBeTruthy(); }); - test('Verify supports set custom displayPeriod after changed value date', () => { - vi.useFakeTimers(); + test('Verify supports set custom displayPeriod after changed value date', async () => { const component = ( ); const { getByText, getByLabelText } = render(component); - fireEvent.click(getByLabelText('Previous month')); - // change visible - fireEvent.click(getByText('15')); - act(() => { - vi.runAllTimers(); - }); + await userEvent.click(getByLabelText('Previous month')); + await userEvent.click(getByText('15')); // change visible expect(getByText('Aug 15, 2021')).toBeTruthy(); - vi.useRealTimers(); }); }); @@ -61,6 +56,7 @@ describe('DateRangePicker', () => { global.Date = RealDate; cleanup(); }); + afterEach(cleanup); test('Verify pikcer support onChange with format time 00:00:00:000', async () => { const spy = vi.fn(); @@ -68,8 +64,8 @@ describe('DateRangePicker', () => { const { getByText } = render(); - fireEvent.click(getByText('Last 2 days')); - fireEvent.click(getByText('Apply')); + await userEvent.click(getByText('Last 2 days')); + await userEvent.click(getByText('Apply')); const today = new Date(new Date().setHours(0, 0, 0, 0)); expect(spy).toBeCalledWith([DateRangePicker.subtract(today, 1, 'day'), today]); }); @@ -84,17 +80,14 @@ describe('DateRangePicker', () => { expect(getByText('April 2020')).toBeTruthy(); }); - test('Verify supports set custom displayPeriod after changed value date', () => { - vi.useFakeTimers(); + test('Verify supports set custom displayPeriod after changed value date', async () => { const { getByText, getByLabelText } = render( , ); - fireEvent.click(getByLabelText('Previous month')); - // change visible - fireEvent.click(getByText('31')); - fireEvent.click(getByText('Apply')); + await userEvent.click(getByLabelText('Previous month')); + await userEvent.click(getByText('31')); + await userEvent.click(getByText('Apply')); expect(getByText('August 2021')).toBeTruthy(); - vi.useRealTimers(); }); test.sequential('Verify not select disabled date from the keyboard', async () => { @@ -107,7 +100,7 @@ describe('DateRangePicker', () => { onPreselectedValueChange={onPreselectedValueChange} > - + , ); @@ -230,7 +223,7 @@ describe('DateRangePicker', () => { ); const resetButton = getByText('Reset'); - fireEvent.click(resetButton); + await userEvent.click(resetButton); expect(onChange).toHaveBeenCalledWith([]); expect(onVisibleChange).toHaveBeenCalledWith(false); @@ -253,13 +246,12 @@ describe('DateRangePicker', () => { , ); - // Select date range using aria-labels to avoid duplicates - fireEvent.click(getByLabelText('Dec 20, 2023')); - fireEvent.click(getByLabelText('Dec 25, 2023')); + await userEvent.click(getByLabelText('Dec 20, 2023')); + await userEvent.click(getByLabelText('Dec 25, 2023')); // Apply selection const applyButton = getByText('Apply'); - fireEvent.click(applyButton); + await userEvent.click(applyButton); expect(onChange).toHaveBeenCalled(); expect(onVisibleChange).toHaveBeenCalledWith(false); @@ -280,12 +272,11 @@ describe('DateRangePicker', () => { , ); - // Select single date (start date = end date) - fireEvent.click(getByLabelText('Dec 20, 2023')); + await userEvent.click(getByLabelText('Dec 20, 2023')); // Apply selection const applyButton = getByText('Apply'); - fireEvent.click(applyButton); + await userEvent.click(applyButton); const callArgs = onChange.mock.calls[0][0]; expect(callArgs[0]).toEqual(callArgs[1]); // start date equals end date diff --git a/semcore/date-picker/vitest.config.mts b/semcore/date-picker/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/date-picker/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/divider/__tests__/index.test.tsx b/semcore/divider/__tests__/index.test.tsx index 6082e3a7bc..ba4e65e709 100644 --- a/semcore/divider/__tests__/index.test.tsx +++ b/semcore/divider/__tests__/index.test.tsx @@ -1,20 +1,6 @@ -import * as sharedTests from '@semcore/testing-utils/shared-tests'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; -import { cleanup } from '@semcore/testing-utils/testing-library'; -import { describe, beforeEach } from '@semcore/testing-utils/vitest'; -import React from 'react'; - -import Divider from '../src'; - -const { shouldSupportClassName, shouldSupportRef } = sharedTests; +import { describe } from '@semcore/testing-utils/vitest'; describe('Divider Dependency imports', () => { runDependencyCheckTests('divider'); }); - -describe('Divider', () => { - beforeEach(cleanup); - - shouldSupportRef(Divider); - shouldSupportClassName(Divider); -}); diff --git a/semcore/divider/vitest.config.mts b/semcore/divider/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/divider/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/dot/__tests__/index.test.tsx b/semcore/dot/__tests__/index.test.tsx index fca201ebff..fb87dda496 100644 --- a/semcore/dot/__tests__/index.test.tsx +++ b/semcore/dot/__tests__/index.test.tsx @@ -1,5 +1,4 @@ import Button from '@semcore/button'; -import * as sharedTests from '@semcore/testing-utils/shared-tests'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; import { cleanup, render } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach, vi, afterEach } from '@semcore/testing-utils/vitest'; @@ -7,8 +6,6 @@ import React from 'react'; import Dot from '../src'; -const { shouldSupportClassName, shouldSupportRef } = sharedTests; - describe('dot Dependency imports', () => { runDependencyCheckTests('dot'); }); @@ -24,9 +21,6 @@ describe('Dot', () => { vi.restoreAllMocks(); }); - shouldSupportClassName(Dot, undefined, { 'aria-label': 'test dot' }); - shouldSupportRef(Dot, undefined, { 'aria-label': 'test dot' }); - test('Verify no "alert" for screenreaders when hidden', async () => { const { queryByTestId } = render( diff --git a/semcore/pagination/vitest.config.mts b/semcore/pagination/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/pagination/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/pills/__tests__/index.test.tsx b/semcore/pills/__tests__/index.test.tsx index 775e15e29b..59414dcbae 100644 --- a/semcore/pills/__tests__/index.test.tsx +++ b/semcore/pills/__tests__/index.test.tsx @@ -1,6 +1,6 @@ import type { Intergalactic } from '@semcore/core'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; -import { render, fireEvent, cleanup, userEvent } from '@semcore/testing-utils/testing-library'; +import { render, cleanup, userEvent } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach, vi, assertType } from '@semcore/testing-utils/vitest'; import React from 'react'; @@ -29,7 +29,7 @@ describe('PillGroup', () => { beforeEach(cleanup); - test.concurrent('Verify supports onChange callback', () => { + test.concurrent('Verify supports onChange callback', async () => { const spy = vi.fn(); const { getByTestId } = render( @@ -42,11 +42,11 @@ describe('PillGroup', () => { , ); - fireEvent.click(getByTestId('tab-4')); + await userEvent.click(getByTestId('tab-4')); expect(spy).toHaveBeenCalledTimes(1); }); - test('Verify supports onClick on Pill', () => { + test('Verify supports onClick on Pill', async () => { const spy = vi.fn(); const { getByTestId } = render( @@ -59,11 +59,11 @@ describe('PillGroup', () => { , ); - fireEvent.click(getByTestId('tab-4')); + await userEvent.click(getByTestId('tab-4')); expect(spy).toHaveBeenCalledTimes(1); }); - test('Verify not calls PillGroup onChange after falsy onClick on Pill', () => { + test('Verify not calls PillGroup onChange after falsy onClick on Pill', async () => { const spy = vi.fn(); const spyClick = vi.fn(() => false); const { getByTestId } = render( @@ -77,12 +77,12 @@ describe('PillGroup', () => { , ); - fireEvent.click(getByTestId('tab-4')); + await userEvent.click(getByTestId('tab-4')); expect(spy).toHaveBeenCalledTimes(0); expect(spyClick).toHaveBeenCalledTimes(1); }); - test('Verify not supports clicks on disabled tab', () => { + test('Verify not supports clicks on disabled tab', async () => { const spy = vi.fn(); const { getByTestId } = render( @@ -96,7 +96,7 @@ describe('PillGroup', () => { , ); - fireEvent.click(getByTestId('tab-4')); + await expect(userEvent.click(getByTestId('tab-4'))).rejects.toThrow('pointer-events: none'); expect(spy).toHaveBeenCalledTimes(0); }); diff --git a/semcore/pills/vitest.config.mts b/semcore/pills/vitest.config.mts new file mode 100644 index 0000000000..36006d4f8e --- /dev/null +++ b/semcore/pills/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.tsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/product-head/vitest.config.mts b/semcore/product-head/vitest.config.mts new file mode 100644 index 0000000000..e02bbe44a5 --- /dev/null +++ b/semcore/product-head/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.jsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/progress-bar/__tests__/index.test.jsx b/semcore/progress-bar/__tests__/index.test.jsx index 187d856b3e..2f901c1d68 100644 --- a/semcore/progress-bar/__tests__/index.test.jsx +++ b/semcore/progress-bar/__tests__/index.test.jsx @@ -1,32 +1,6 @@ -import * as sharedTests from '@semcore/testing-utils/shared-tests'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; -import { cleanup, render } from '@semcore/testing-utils/testing-library'; -import { expect, test, describe, beforeEach } from '@semcore/testing-utils/vitest'; -import React from 'react'; - -import ProgressBar from '../src'; - -const { shouldSupportClassName, shouldSupportRef } = sharedTests; +import { describe } from '@semcore/testing-utils/vitest'; describe('progress-bar Dependency imports', () => { runDependencyCheckTests('progress-bar'); }); - -describe('ProgressBar', () => { - beforeEach(cleanup); - - shouldSupportClassName(ProgressBar); - shouldSupportRef(ProgressBar); - - test.concurrent('Verify supports children', () => { - const component = ( - -

Test

-

Test

-

Test

-
- ); - const { getByTestId } = render(component); - expect(getByTestId('parent').children.length).toEqual(3); - }); -}); diff --git a/semcore/progress-bar/vitest.config.mts b/semcore/progress-bar/vitest.config.mts new file mode 100644 index 0000000000..e02bbe44a5 --- /dev/null +++ b/semcore/progress-bar/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.jsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/radio/__tests__/index.test.jsx b/semcore/radio/__tests__/index.test.jsx index cf5f6b7e9a..9bbdfa01b1 100644 --- a/semcore/radio/__tests__/index.test.jsx +++ b/semcore/radio/__tests__/index.test.jsx @@ -1,13 +1,10 @@ -import * as sharedTests from '@semcore/testing-utils/shared-tests'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; -import { cleanup, fireEvent, render } from '@semcore/testing-utils/testing-library'; +import { cleanup, render, userEvent } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach, vi } from '@semcore/testing-utils/vitest'; import React from 'react'; import Radio, { RadioGroup, inputProps } from '../src/Radio'; -const { shouldSupportClassName, shouldSupportRef } = sharedTests; - describe('radio Dependency imports', () => { runDependencyCheckTests('radio'); }); @@ -15,13 +12,6 @@ describe('radio Dependency imports', () => { describe('Radio', () => { beforeEach(cleanup); - shouldSupportClassName(Radio); - shouldSupportRef(Radio); - shouldSupportClassName(Radio.Value, Radio); - shouldSupportRef(Radio.Value, Radio); - shouldSupportClassName(Radio.Text, Radio); - shouldSupportRef(Radio.Text, Radio); - test.concurrent('Verify supports custom attributes on the input', () => { const { getByTestId } = render( @@ -44,7 +34,7 @@ describe('Radio', () => { , ); - fireEvent.click(getByTestId('label')); + await userEvent.click(getByTestId('label')); expect(spy).toHaveBeenCalled(); }); }); @@ -64,7 +54,7 @@ describe('RadioGroup', () => { expect(getByTestId('radio').name).toContain('test'); }); - test('Verify supports onChange', () => { + test('Verify supports onChange', async () => { const onChange = vi.fn(); const onChangeRadio = vi.fn(); const value = 'test'; @@ -81,12 +71,12 @@ describe('RadioGroup', () => { , ); - fireEvent.click(getByTestId('radio')); + await userEvent.click(getByTestId('radio')); expect(onChangeRadio).toHaveBeenCalledWith(expect.anything()); expect(onChange).toHaveBeenCalledWith(value, expect.anything()); }); - test('Verify supports cancel chain of onChanges', () => { + test('Verify supports cancel chain of onChanges', async () => { const onChange = vi.fn(); const onChangeRadio = vi.fn(() => false); const value = 'test'; @@ -103,7 +93,7 @@ describe('RadioGroup', () => { , ); - fireEvent.click(getByTestId('radio')); + await userEvent.click(getByTestId('radio')); expect(onChangeRadio).toHaveBeenCalledWith(expect.anything()); expect(onChange).not.toHaveBeenCalled(); }); @@ -236,7 +226,7 @@ describe('RadioGroup', () => { expect(getByTestId('r2').checked).toBe(true); - fireEvent.click(getByTestId('r1')); + await userEvent.click(getByTestId('r1')); expect(onChange).toHaveBeenCalledWith('1', expect.anything()); expect(getByTestId('r1').checked).toBe(true); }); diff --git a/semcore/radio/vitest.config.mts b/semcore/radio/vitest.config.mts new file mode 100644 index 0000000000..e02bbe44a5 --- /dev/null +++ b/semcore/radio/vitest.config.mts @@ -0,0 +1,17 @@ +import { resolve as resolvePath } from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +import { vitestPlugins, vitestResolve } from '../../vitest.config.mts'; + +export default defineConfig({ + plugins: vitestPlugins, + resolve: vitestResolve, + test: { + include: [ + '__tests__/**/*.test.jsx', + ], + environment: 'jsdom', + setupFiles: [resolvePath(__dirname, '../../tools/testing-utils/setupTests')], + }, +}); diff --git a/semcore/select/__tests__/index.test.tsx b/semcore/select/__tests__/index.test.tsx index c170d7240b..d14f2770c4 100644 --- a/semcore/select/__tests__/index.test.tsx +++ b/semcore/select/__tests__/index.test.tsx @@ -1,13 +1,10 @@ -import * as sharedTests from '@semcore/testing-utils/shared-tests'; import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; -import { cleanup, fireEvent, render, act, userEvent } from '@semcore/testing-utils/testing-library'; +import { cleanup, render, userEvent, waitFor } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach, vi } from '@semcore/testing-utils/vitest'; import React from 'react'; import Select, { InputSearch } from '../src'; -const { shouldSupportClassName, shouldSupportRef } = sharedTests; - describe('select Dependency imports', () => { runDependencyCheckTests('select'); }); @@ -47,7 +44,7 @@ describe('Select Trigger', () => { }, ); - test.concurrent('Verify onVisibleChange calls for click in Option when value selected', () => { + test.concurrent('Verify onVisibleChange calls for click in Option when value selected', async () => { const spy = vi.fn(); const { getByTestId } = render( , ); - fireEvent.click(getByTestId('option')); - expect(spy).toHaveBeenCalledTimes(1); - fireEvent.click(getByTestId('option')); - expect(spy).toHaveBeenCalledTimes(2); + await userEvent.click(getByTestId('option')); + expect(spy).toHaveBeenCalled(); + const callsAfterFirstClick = spy.mock.calls.length; + await userEvent.click(getByTestId('option')); + expect(spy.mock.calls.length).toBeGreaterThan(callsAfterFirstClick); }); test('Verify highlights selected item', async () => { @@ -137,8 +135,7 @@ describe('Select Trigger', () => { expect(spy).toBeCalledTimes(1); }); - test.concurrent('Verify focus position preserve with mouse navigation', async () => { - vi.useFakeTimers(); + test.sequential('Verify focus position preserve with mouse navigation', async () => { const { getByTestId } = render( , ); - fireEvent.click(getByTestId('trigger')); - act(() => { - vi.runAllTimers(); - }); - act(() => getByTestId('option-2').focus()); - fireEvent.click(getByTestId('option-2')); - act(() => { - vi.runAllTimers(); - }); - act(() => { - vi.runAllTimers(); - }); - expect(getByTestId('trigger')).toHaveFocus(); - vi.useRealTimers(); + await userEvent.click(getByTestId('trigger')); + await new Promise((resolve) => setTimeout(resolve, 50)); + await userEvent.keyboard('[Enter]'); + + await waitFor(() => { + expect(getByTestId('trigger')).toHaveFocus(); + }); }); test.sequential( 'Verify focus position preserve with mouse navigation and interaction=focus', async () => { - vi.useFakeTimers(); const { getByTestId } = render( , ); - act(() => getByTestId('trigger').focus()); - act(() => { - vi.runAllTimers(); - }); - act(() => getByTestId('option-2').focus()); - fireEvent.click(getByTestId('option-2')); - act(() => { - vi.runAllTimers(); - }); - expect(getByTestId('trigger')).toHaveFocus(); - vi.useRealTimers(); + await userEvent.click(getByTestId('trigger')); + await new Promise((resolve) => setTimeout(resolve, 50)); + await userEvent.keyboard('[Enter]'); + + await waitFor(() => { + expect(getByTestId('trigger')).toHaveFocus(); + }); }, ); @@ -230,9 +220,6 @@ describe('Select Trigger', () => { describe('Option.Checkbox', () => { beforeEach(cleanup); - shouldSupportClassName(Select.Option.Checkbox, Select); - shouldSupportRef(Select.Option.Checkbox, Select); - test('Verify not focused by Tab between Select.Option.Checkbox(deprecated methids regression)', async () => { const { getByTestId } = render(