diff --git a/.changeset/rude-pears-open.md b/.changeset/rude-pears-open.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/rude-pears-open.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.cspell/contributors.txt b/.cspell/contributors.txt index 282e1489af..ce429baf4d 100644 --- a/.cspell/contributors.txt +++ b/.cspell/contributors.txt @@ -8,6 +8,10 @@ Zhiyuan ZhiyuanHong HongZhiyuan +Yiming +YimingLi +LiYiming + # Webpack Contributors Koppers sokra diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af0e52d865..40444334cc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -148,11 +148,15 @@ jobs: npx --registry http://localhost:4873 create-rspeedy-canary@latest --template react --dir create-rspeedy-regression cd create-rspeedy-regression pnpm install --registry=http://localhost:4873 + pnpm tsc --noEmit pnpm run build + pnpm tsc --noEmit npx --registry http://localhost:4873 create-rspeedy-canary@latest --template react-vitest-rltl --dir create-rspeedy-regression-vitest-rltl cd create-rspeedy-regression-vitest-rltl pnpm install --registry=http://localhost:4873 + pnpm tsc --noEmit pnpm run build + pnpm tsc --noEmit pnpm run test test-tools: needs: build diff --git a/packages/react/package.json b/packages/react/package.json index dda6619f46..086b460b30 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -164,11 +164,8 @@ "preact": "npm:@hongzhiyuan/preact@10.24.0-319c684e" }, "devDependencies": { - "@lynx-js/test-environment": "workspace:*", "@lynx-js/types": "^3.2.1", "@microsoft/api-extractor": "catalog:", - "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.6.3", "@types/react": "^18.3.20" }, "peerDependencies": { diff --git a/packages/react/testing-library/api-extractor.json b/packages/react/testing-library/api-extractor.json index 2d2d66ca84..c275eb4cf6 100644 --- a/packages/react/testing-library/api-extractor.json +++ b/packages/react/testing-library/api-extractor.json @@ -4,7 +4,7 @@ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", "extends": "../../../api-extractor.json", - "mainEntryPointFilePath": "/types/index.d.ts", + "mainEntryPointFilePath": "/types/entry.d.ts", "compiler": { "overrideTsconfig": { "compilerOptions": { diff --git a/packages/react/testing-library/etc/react-lynx-testing-library.api.md b/packages/react/testing-library/etc/react-lynx-testing-library.api.md index 4b6e4c9866..73d2b96c32 100644 --- a/packages/react/testing-library/etc/react-lynx-testing-library.api.md +++ b/packages/react/testing-library/etc/react-lynx-testing-library.api.md @@ -7,13 +7,19 @@ import { BoundFunction } from '@testing-library/dom'; import { ComponentChild } from 'preact'; import { ComponentType } from 'preact'; +import { ElementTree } from '@lynx-js/test-environment'; import { LynxElement } from '@lynx-js/test-environment'; +import { LynxEnv } from '@lynx-js/test-environment'; import { Queries } from '@testing-library/dom'; import { queries } from '@testing-library/dom'; // @public export function cleanup(): void; +export { ElementTree } + +export { LynxEnv } + // @public export function render( ui: ComponentChild, diff --git a/packages/react/testing-library/package.json b/packages/react/testing-library/package.json index 5d68b93c95..f6053c1f4e 100644 --- a/packages/react/testing-library/package.json +++ b/packages/react/testing-library/package.json @@ -11,6 +11,9 @@ "test:ui": "vitest --ui" }, "devDependencies": { - "@lynx-js/react": "workspace:*" + "@lynx-js/react": "workspace:*", + "@lynx-js/test-environment": "workspace:*", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3" } } diff --git a/packages/react/testing-library/rslib.config.ts b/packages/react/testing-library/rslib.config.ts index 67225dc333..07636e9466 100644 --- a/packages/react/testing-library/rslib.config.ts +++ b/packages/react/testing-library/rslib.config.ts @@ -41,6 +41,17 @@ export default defineConfig({ ], }, }, + { + format: 'esm', + dts: { + bundle: true, + }, + source: { + entry: { + 'index': './src/entry.ts', + }, + }, + }, ], tools: { rspack(config) { diff --git a/packages/react/testing-library/src/entry.ts b/packages/react/testing-library/src/entry.ts new file mode 100644 index 0000000000..b9677f884c --- /dev/null +++ b/packages/react/testing-library/src/entry.ts @@ -0,0 +1,2 @@ +// @ts-ignore +export type * from '../../../types/entry.d.ts'; diff --git a/packages/react/testing-library/types/entry.d.ts b/packages/react/testing-library/types/entry.d.ts new file mode 100644 index 0000000000..c7560bf9b8 --- /dev/null +++ b/packages/react/testing-library/types/entry.d.ts @@ -0,0 +1,257 @@ +/** + * @packageDocumentation + * + * ReactLynx Testing Library is a simple and complete ReactLynx + * unit testing library that encourages good testing practices. + * + * Inspired by {@link https://testing-library.com/docs/react-testing-library/intro | React Testing Library} and {@link https://github.com/jsdom/jsdom | jsdom}. + */ + +import { queries, Queries, BoundFunction } from '@testing-library/dom'; +import { LynxElement, type ElementTree, type LynxEnv } from '@lynx-js/test-environment'; +import { ComponentChild, ComponentType } from 'preact'; +export * from '@testing-library/dom'; +export { ElementTree, LynxEnv }; + +/** + * The options for {@link render}. + * + * @public + */ +export interface RenderOptions { + /** + * Queries to bind. Overrides the default set from DOM Testing Library unless merged. + * + * @example + * + * ```ts + * // Example, a function to traverse table contents + * import * as tableQueries from 'my-table-query-library' + * import { queries } from '@lynx-js/react/testing-library' + * + * const { getByRowColumn, getByText } = render(, { + * queries: {...queries, ...tableQueries}, + * }) + * + * ``` + */ + queries?: Q; + /** + * Pass a React Component as the wrapper option to have it rendered around the inner element. This is most useful for creating + * reusable custom render functions for common data providers. See setup for examples. + * + * @example + * + * ```ts + * import { render } from '@lynx-js/react/testing-library' + * import { ThemeProvider } from 'my-ui-lib' + * import { TranslationProvider } from 'my-i18n-lib' + * import defaultStrings from 'i18n/en-x-default' + * + * const AllTheProviders = ({children}) => { + * return ( + * + * + * {children} + * + * + * ) + * } + * + * const customRender = (ui, options) => + * render(ui, { wrapper: AllTheProviders, ...options }) + * + * // re-export everything + * export * from '@lynx-js/react/testing-library' + * + * // override render method + * export { customRender as render } + * ``` + */ + wrapper?: ComponentChild; + /** + * Render your component in the main thread or not. + * + * It is recommended to use this option only when you need to test the {@link https://lynxjs.org/zh/guide/interaction/ifr.html | IFR} behavior. + * + * @defaultValue false + */ + enableMainThread?: boolean; + /** + * Render your component in the background thread or not. + * + * Note that all user code in the top level will be executed in the background thread by default. (eg. `__BACKGROUND__` is `true` in the top level) + * + * @defaultValue true + */ + enableBackgroundThread?: boolean; +} + +/** + * The result of {@link render} + * + * @public + */ +export type RenderResult = { + container: LynxElement; + rerender: (ui: ComponentChild) => void; + unmount: () => boolean; +} & { [P in keyof Q]: BoundFunction }; + +/** + * Render into the page. It should be used with cleanup. + * + * @example + * + * ```ts + * import { render} from '@lynx-js/react/testing-library' + * + * const WrapperComponent = ({ children }) => ( + * {children} + * ); + * const Comp = () => { + * return ; + * }; + * const { container, getByTestId } = render(, { + * wrapper: WrapperComponent, + * }); + * expect(getByTestId('wrapper')).toBeInTheDocument(); + * expect(container.firstChild).toMatchInlineSnapshot(` + * + * + * + * `); + * ``` + * + * @public + */ +export function render( + ui: ComponentChild, + options?: RenderOptions, +): RenderResult; +/** + * Cleanup elements rendered to the page and Preact trees that were mounted with render. + * + * @public + */ +export function cleanup(): void; + +/** + * The result of {@link renderHook} + * + * @public + */ + +export interface RenderHookResult { + /** + * Triggers a re-render. The props will be passed to your renderHook callback. + */ + rerender: (props?: Props) => void; + /** + * This is a stable reference to the latest value returned by your renderHook + * callback + */ + result: { + /** + * The value returned by your renderHook callback + */ + current: Result; + }; + /** + * Unmounts the test component. This is useful for when you need to test + * any cleanup your useEffects have. + */ + unmount: () => void; +} + +/** + * The options for {@link renderHook} + * + * @public + */ +export interface RenderHookOptions { + /** + * The argument passed to the renderHook callback. Can be useful if you plan + * to use the rerender utility to change the values passed to your hook. + */ + initialProps?: Props; + /** + * Pass a React Component as the wrapper option to have it rendered around the inner element. This is most useful for creating + * reusable custom render functions for common data providers. See setup for examples. + * + * @example + * + * ```ts + * import { renderHook } from '@lynx-js/react/testing-library' + * import { ThemeProvider } from 'my-ui-lib' + * import { TranslationProvider } from 'my-i18n-lib' + * import defaultStrings from 'i18n/en-x-default' + * + * const AllTheProviders = ({children}) => { + * return ( + * + * + * {children} + * + * + * ) + * } + * + * const customRenderHook = (ui, options) => + * renderHook(ui, { wrapper: AllTheProviders, ...options }) + * + * // re-export everything + * export * from '@lynx-js/react/testing-library' + * + * // override renderHook method + * export { customRender as renderHook } + * ``` + */ + wrapper?: ComponentType<{ children: LynxElement }>; +} + +/** + * Allows you to render a hook within a test React component without having to + * create that component yourself. + * + * @example + * + * ```ts + * import { renderHook } from '@lynx-js/react/testing-library' + * + * const Context = createContext('default'); + * function Wrapper({ children }) { + * return {children}; + * } + * const { result } = renderHook( + * () => { + * return useContext(Context); + * }, + * { + * wrapper: Wrapper, + * }, + * ); + * + * expect(result.current).toEqual('provided'); + * ``` + * + * @public + */ +export function renderHook( + render: (initialProps: Props) => Result, + options?: RenderHookOptions, +): RenderHookResult; + +/** + * Wait for the next event loop. + * + * It will be useful when you want to wait for the next event loop to finish. + * + * @public + */ +export function waitSchedule(): Promise; diff --git a/packages/react/testing-library/types/index.d.ts b/packages/react/testing-library/types/index.d.ts index 9e7ee2b7ac..6f21459aa8 100644 --- a/packages/react/testing-library/types/index.d.ts +++ b/packages/react/testing-library/types/index.d.ts @@ -1,256 +1,15 @@ -/** - * @packageDocumentation - * - * ReactLynx Testing Library is a simple and complete ReactLynx - * unit testing library that encourages good testing practices. - * - * Inspired by {@link https://testing-library.com/docs/react-testing-library/intro | React Testing Library} and {@link https://github.com/jsdom/jsdom | jsdom}. - */ - -import { queries, Queries, BoundFunction } from '@testing-library/dom'; -import { LynxElement } from '@lynx-js/test-environment'; -import { ComponentChild, ComponentType } from 'preact'; -export * from '@testing-library/dom'; - -/** - * The options for {@link render}. - * - * @public - */ -export interface RenderOptions { - /** - * Queries to bind. Overrides the default set from DOM Testing Library unless merged. - * - * @example - * - * ```ts - * // Example, a function to traverse table contents - * import * as tableQueries from 'my-table-query-library' - * import { queries } from '@lynx-js/react/testing-library' - * - * const { getByRowColumn, getByText } = render(, { - * queries: {...queries, ...tableQueries}, - * }) - * - * ``` - */ - queries?: Q; - /** - * Pass a React Component as the wrapper option to have it rendered around the inner element. This is most useful for creating - * reusable custom render functions for common data providers. See setup for examples. - * - * @example - * - * ```ts - * import { render } from '@lynx-js/react/testing-library' - * import { ThemeProvider } from 'my-ui-lib' - * import { TranslationProvider } from 'my-i18n-lib' - * import defaultStrings from 'i18n/en-x-default' - * - * const AllTheProviders = ({children}) => { - * return ( - * - * - * {children} - * - * - * ) - * } - * - * const customRender = (ui, options) => - * render(ui, { wrapper: AllTheProviders, ...options }) - * - * // re-export everything - * export * from '@lynx-js/react/testing-library' - * - * // override render method - * export { customRender as render } - * ``` - */ - wrapper?: ComponentChild; - /** - * Render your component in the main thread or not. - * - * It is recommended to use this option only when you need to test the {@link https://lynxjs.org/zh/guide/interaction/ifr.html | IFR} behavior. - * - * @defaultValue false - */ - enableMainThread?: boolean; - /** - * Render your component in the background thread or not. - * - * Note that all user code in the top level will be executed in the background thread by default. (eg. `__BACKGROUND__` is `true` in the top level) - * - * @defaultValue true - */ - enableBackgroundThread?: boolean; +// @ts-nocheck +export * from '../dist/index.d.ts'; +import { ElementTree, LynxEnv } from '../dist/index.d.ts'; + +declare global { + var lynxEnv: LynxEnv; + var elementTree: ElementTree; + + function onInjectBackgroundThreadGlobals(globals: any): void; + function onInjectMainThreadGlobals(globals: any): void; + function onSwitchedToBackgroundThread(): void; + function onSwitchedToMainThread(): void; + function onResetLynxEnv(): void; + function onInitWorkletRuntime(): void; } - -/** - * The result of {@link render} - * - * @public - */ -export type RenderResult = { - container: LynxElement; - rerender: (ui: ComponentChild) => void; - unmount: () => boolean; -} & { [P in keyof Q]: BoundFunction }; - -/** - * Render into the page. It should be used with cleanup. - * - * @example - * - * ```ts - * import { render} from '@lynx-js/react/testing-library' - * - * const WrapperComponent = ({ children }) => ( - * {children} - * ); - * const Comp = () => { - * return ; - * }; - * const { container, getByTestId } = render(, { - * wrapper: WrapperComponent, - * }); - * expect(getByTestId('wrapper')).toBeInTheDocument(); - * expect(container.firstChild).toMatchInlineSnapshot(` - * - * - * - * `); - * ``` - * - * @public - */ -export function render( - ui: ComponentChild, - options?: RenderOptions, -): RenderResult; -/** - * Cleanup elements rendered to the page and Preact trees that were mounted with render. - * - * @public - */ -export function cleanup(): void; - -/** - * The result of {@link renderHook} - * - * @public - */ - -export interface RenderHookResult { - /** - * Triggers a re-render. The props will be passed to your renderHook callback. - */ - rerender: (props?: Props) => void; - /** - * This is a stable reference to the latest value returned by your renderHook - * callback - */ - result: { - /** - * The value returned by your renderHook callback - */ - current: Result; - }; - /** - * Unmounts the test component. This is useful for when you need to test - * any cleanup your useEffects have. - */ - unmount: () => void; -} - -/** - * The options for {@link renderHook} - * - * @public - */ -export interface RenderHookOptions { - /** - * The argument passed to the renderHook callback. Can be useful if you plan - * to use the rerender utility to change the values passed to your hook. - */ - initialProps?: Props; - /** - * Pass a React Component as the wrapper option to have it rendered around the inner element. This is most useful for creating - * reusable custom render functions for common data providers. See setup for examples. - * - * @example - * - * ```ts - * import { renderHook } from '@lynx-js/react/testing-library' - * import { ThemeProvider } from 'my-ui-lib' - * import { TranslationProvider } from 'my-i18n-lib' - * import defaultStrings from 'i18n/en-x-default' - * - * const AllTheProviders = ({children}) => { - * return ( - * - * - * {children} - * - * - * ) - * } - * - * const customRenderHook = (ui, options) => - * renderHook(ui, { wrapper: AllTheProviders, ...options }) - * - * // re-export everything - * export * from '@lynx-js/react/testing-library' - * - * // override renderHook method - * export { customRender as renderHook } - * ``` - */ - wrapper?: ComponentType<{ children: LynxElement }>; -} - -/** - * Allows you to render a hook within a test React component without having to - * create that component yourself. - * - * @example - * - * ```ts - * import { renderHook } from '@lynx-js/react/testing-library' - * - * const Context = createContext('default'); - * function Wrapper({ children }) { - * return {children}; - * } - * const { result } = renderHook( - * () => { - * return useContext(Context); - * }, - * { - * wrapper: Wrapper, - * }, - * ); - * - * expect(result.current).toEqual('provided'); - * ``` - * - * @public - */ -export function renderHook( - render: (initialProps: Props) => Result, - options?: RenderHookOptions, -): RenderHookResult; - -/** - * Wait for the next event loop. - * - * It will be useful when you want to wait for the next event loop to finish. - * - * @public - */ -export function waitSchedule(): Promise; diff --git a/packages/testing-library/test-environment/package.json b/packages/testing-library/test-environment/package.json index 994b001a51..4461cb77ee 100644 --- a/packages/testing-library/test-environment/package.json +++ b/packages/testing-library/test-environment/package.json @@ -1,6 +1,22 @@ { "name": "@lynx-js/test-environment", "version": "0.0.0", + "description": "A subset of a Lynx environment to be useful for testing", + "keywords": [ + "Lynx", + "ReactLynx", + "testing" + ], + "repository": { + "type": "git", + "url": "https://github.com/lynx-family/lynx-stack.git", + "directory": "packages/testing-library/test-environment" + }, + "license": "Apache-2.0", + "author": { + "name": "Yiming Li", + "email": "yimingli.cs@gmail.com" + }, "exports": { ".": { "default": "./dist/index.mjs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a05f56405..b9a671ef33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,21 +183,12 @@ importers: specifier: npm:@hongzhiyuan/preact@10.24.0-319c684e version: '@hongzhiyuan/preact@10.24.0-319c684e' devDependencies: - '@lynx-js/test-environment': - specifier: workspace:* - version: link:../testing-library/test-environment '@lynx-js/types': specifier: ^3.2.1 version: 3.2.1 '@microsoft/api-extractor': specifier: 'catalog:' version: 7.52.5(@types/node@22.14.1) - '@testing-library/dom': - specifier: ^10.4.0 - version: 10.4.0 - '@testing-library/jest-dom': - specifier: ^6.6.3 - version: 6.6.3 '@types/react': specifier: ^18.3.20 version: 18.3.20 @@ -252,6 +243,15 @@ importers: '@lynx-js/react': specifier: workspace:* version: link:.. + '@lynx-js/test-environment': + specifier: workspace:* + version: link:../../testing-library/test-environment + '@testing-library/dom': + specifier: ^10.4.0 + version: 10.4.0 + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.6.3 packages/react/transform: devDependencies: