From 796fd1999dbd518eb67fa1c062e550b8d46ff306 Mon Sep 17 00:00:00 2001 From: "@compulim" <@compulim> Date: Fri, 2 Jan 2026 00:59:13 +0000 Subject: [PATCH 1/6] Bump scaffold --- .eslintrc.production.yml | 20 +-- .eslintrc.react.yml | 6 +- .eslintrc.test.yml | 1 - .eslintrc.yml | 2 - .github/workflows/bump-scaffold.yml | 6 - .github/workflows/pull-request-validation.yml | 1 - package.json | 20 ++- .../integration-test/.eslintrc.custom.yml | 2 - packages/integration-test/.eslintrc.yml | 6 +- packages/integration-test/.gitignore | 3 +- packages/integration-test/docker-compose.yml | 6 +- packages/integration-test/package.json | 72 +++++++++- packages/integration-test/tsconfig.json | 1 + packages/pages/package.json | 11 +- packages/pages/src/tsconfig.json | 6 +- packages/react-say/.eslintrc.custom.yml | 5 - packages/react-say/package.json | 53 ++++++- packages/react-say/src/tsconfig.custom.json | 9 +- .../src/tsconfig.precommit.production.json | 27 +++- .../src/tsconfig.precommit.test.json | 27 +++- packages/react-say/tsup.config.override.ts | 7 + packages/react-say/tsup.config.ts | 33 +++-- packages/test-harness/.eslintrc.custom.yml | 0 packages/test-harness/.eslintrc.yml | 23 +++ packages/test-harness/.gitignore | 1 + packages/test-harness/package.json | 131 ++++++++++++++++++ packages/test-harness/src/act.d.mts | 3 + packages/test-harness/src/act.d.ts | 5 + packages/test-harness/src/act.js | 11 ++ packages/test-harness/src/act.mjs | 11 ++ packages/test-harness/src/describeEach.d.mts | 50 +++++++ packages/test-harness/src/describeEach.d.ts | 50 +++++++ packages/test-harness/src/describeEach.js | 26 ++++ packages/test-harness/src/describeEach.mjs | 26 ++++ .../test-harness/src/happy-dom.import.mjs | 21 +++ packages/test-harness/src/jsx.import.mjs | 34 +++++ packages/test-harness/src/render.d.mts | 1 + packages/test-harness/src/render.d.ts | 1 + packages/test-harness/src/render.js | 1 + packages/test-harness/src/render.mjs | 1 + packages/test-harness/src/renderHook.d.mts | 6 + packages/test-harness/src/renderHook.d.ts | 8 ++ packages/test-harness/src/renderHook.js | 11 ++ packages/test-harness/src/renderHook.mjs | 11 ++ .../test-harness/src/test-types.import.mjs | 31 +++++ packages/test-harness/src/test-types.stub.mts | 96 +++++++++++++ packages/test-harness/src/tsconfig.json | 10 ++ .../test-harness/src/tsconfig.precommit.json | 9 ++ 48 files changed, 825 insertions(+), 77 deletions(-) create mode 100644 packages/react-say/tsup.config.override.ts create mode 100644 packages/test-harness/.eslintrc.custom.yml create mode 100644 packages/test-harness/.eslintrc.yml create mode 100644 packages/test-harness/.gitignore create mode 100644 packages/test-harness/package.json create mode 100644 packages/test-harness/src/act.d.mts create mode 100644 packages/test-harness/src/act.d.ts create mode 100644 packages/test-harness/src/act.js create mode 100644 packages/test-harness/src/act.mjs create mode 100644 packages/test-harness/src/describeEach.d.mts create mode 100644 packages/test-harness/src/describeEach.d.ts create mode 100644 packages/test-harness/src/describeEach.js create mode 100644 packages/test-harness/src/describeEach.mjs create mode 100644 packages/test-harness/src/happy-dom.import.mjs create mode 100644 packages/test-harness/src/jsx.import.mjs create mode 100644 packages/test-harness/src/render.d.mts create mode 100644 packages/test-harness/src/render.d.ts create mode 100644 packages/test-harness/src/render.js create mode 100644 packages/test-harness/src/render.mjs create mode 100644 packages/test-harness/src/renderHook.d.mts create mode 100644 packages/test-harness/src/renderHook.d.ts create mode 100644 packages/test-harness/src/renderHook.js create mode 100644 packages/test-harness/src/renderHook.mjs create mode 100644 packages/test-harness/src/test-types.import.mjs create mode 100644 packages/test-harness/src/test-types.stub.mts create mode 100644 packages/test-harness/src/tsconfig.json create mode 100644 packages/test-harness/src/tsconfig.precommit.json diff --git a/.eslintrc.production.yml b/.eslintrc.production.yml index b63635d..5e19a32 100644 --- a/.eslintrc.production.yml +++ b/.eslintrc.production.yml @@ -36,15 +36,15 @@ rules: - '**/*.css' - dotenv/config settings: - import/extensions: - - .cjs - - .mjs - - .js - - .jsx - - .cts - - .mts - - .ts - - .tsx import/resolver: - node: true + node: + extensions: + - .cjs + - .mjs + - .js + - .jsx + - .cts + - .mts + - .ts + - .tsx typescript: true diff --git a/.eslintrc.react.yml b/.eslintrc.react.yml index e6f295c..e8998b7 100644 --- a/.eslintrc.react.yml +++ b/.eslintrc.react.yml @@ -4,6 +4,6 @@ extends: plugins: - react - react-hooks -settings: - react: - version: 18.3.1 +rules: + react-hooks/refs: off # We use refs intensively + react-hooks/preserve-manual-memoization: off # Does not know `valibot.parseProps` diff --git a/.eslintrc.test.yml b/.eslintrc.test.yml index 0255384..0c765a3 100644 --- a/.eslintrc.test.yml +++ b/.eslintrc.test.yml @@ -2,7 +2,6 @@ env: commonjs: true es2021: true es2022: true - jest: true rules: # Disable for convenience react/display-name: off diff --git a/.eslintrc.yml b/.eslintrc.yml index caea226..ec49405 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -61,8 +61,6 @@ overrides: - '**/*.ts' - '**/*.tsx' parserOptions: - ecmaFeatures: - jsx: true ecmaVersion: latest sourceType: module plugins: diff --git a/.github/workflows/bump-scaffold.yml b/.github/workflows/bump-scaffold.yml index 0ef4973..b6d476e 100644 --- a/.github/workflows/bump-scaffold.yml +++ b/.github/workflows/bump-scaffold.yml @@ -13,11 +13,6 @@ on: description: Use React required: true type: boolean - skip-integration-test: - default: false - description: Skip integration test - required: true - type: boolean jobs: call-workflow: @@ -30,5 +25,4 @@ jobs: uses: compulim/workflows/.github/workflows/bump-scaffold.yml@main with: package-name: ${{ inputs.package-name }} - skip-integration-test: ${{ inputs.skip-integration-test }} use-react: ${{ inputs.use-react }} diff --git a/.github/workflows/pull-request-validation.yml b/.github/workflows/pull-request-validation.yml index 17b3c13..f1a523f 100644 --- a/.github/workflows/pull-request-validation.yml +++ b/.github/workflows/pull-request-validation.yml @@ -15,5 +15,4 @@ jobs: uses: compulim/workflows/.github/workflows/pull-request-validation.yml@main with: package-name: 'react-say' - skip-integration-test: false switch: ${{ matrix.switch }} diff --git a/package.json b/package.json index 0011e1c..485eec3 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { - "name": "react-say-root", + "name": "@compulim/root", "version": "2.2.2-0", "private": true, "workspaces": [ + "packages/test-harness", "packages/react-say", - "packages/pages", - "packages/integration-test" + "packages/integration-test", + "packages/pages" ], "scripts": { "build": "npm run build --if-present --workspaces", @@ -14,7 +15,9 @@ "bump:eslintrc": "if [ -f node_modules/react/package.json ]; then docker run -e VERSION=$(cat node_modules/react/package.json | jq -r '.version') -i --rm mikefarah/yq '.settings.react.version = strenv(VERSION)' < ./.eslintrc.react.yml | tee /tmp/output.tmp && mv /tmp/output.tmp ./.eslintrc.react.yml; fi", "bump:packages": "npm run bump --if-present --workspaces", "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", - "postscaffold": "npm run postscaffold:eslint:react && npm run postscaffold --if-present --workspaces", + "init": "npm run init:dev --if-present && npm run init:prod --if-present", + "init:dev": "npm install --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-import-resolver-node eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks prettier", + "postscaffold": "npm run postscaffold:eslint:react --if-present && npm run postscaffold --if-present --workspaces", "postscaffold:eslint:react": "npm run bump:eslintrc", "precommit": "npm run precommit --if-present --workspaces", "switch:_": "npm run --if-present --workspaces switch && npm install --legacy-peer-deps --prefer-dedupe", @@ -34,5 +37,12 @@ "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", "prettier": "^3.7.4" - } + }, + "author": "William Wong (https://github.com/compulim)", + "license": "MIT", + "dependencies": {}, + "exports": {}, + "localPeerDependencies": {}, + "pinDependencies": {}, + "peerDependencies": {} } diff --git a/packages/integration-test/.eslintrc.custom.yml b/packages/integration-test/.eslintrc.custom.yml index 2e7b97f..48acf78 100644 --- a/packages/integration-test/.eslintrc.custom.yml +++ b/packages/integration-test/.eslintrc.custom.yml @@ -1,7 +1,5 @@ env: browser: true - es2021: true - es2022: true extends: - ../../.eslintrc.react.yml rules: diff --git a/packages/integration-test/.eslintrc.yml b/packages/integration-test/.eslintrc.yml index 1023f84..030c13a 100644 --- a/packages/integration-test/.eslintrc.yml +++ b/packages/integration-test/.eslintrc.yml @@ -1,7 +1,7 @@ extends: - ./.eslintrc.custom.yml ignorePatterns: - - test/webDriver/static/** + - webDriver/** overrides: - env: commonjs: true @@ -15,3 +15,7 @@ overrides: parserOptions: ecmaVersion: latest sourceType: module + - files: + - '**/*.test-types.*' + rules: + react-hooks/rules-of-hooks: off diff --git a/packages/integration-test/.gitignore b/packages/integration-test/.gitignore index a39d331..795d799 100644 --- a/packages/integration-test/.gitignore +++ b/packages/integration-test/.gitignore @@ -1,3 +1,2 @@ /node_modules/ -/package-lock.json -/test/webDriver/static/ +/webDriver/static/ diff --git a/packages/integration-test/docker-compose.yml b/packages/integration-test/docker-compose.yml index 1169f1f..187f096 100644 --- a/packages/integration-test/docker-compose.yml +++ b/packages/integration-test/docker-compose.yml @@ -8,7 +8,7 @@ services: SE_EVENT_BUS_PUBLISH_PORT: 4442 SE_EVENT_BUS_SUBSCRIBE_PORT: 4443 SE_NODE_SESSION_TIMEOUT: 15 - image: selenium/node-chromium:130.0 + image: selenium/node-chromium:140.0 shm_size: 2gb stop_grace_period: 0s volumes: @@ -24,7 +24,7 @@ services: start_period: 30s test: ["CMD-SHELL", "curl -s -f http://localhost:4444/wd/hub/status 2>/dev/null | jq '.value.nodes | length > 0' | grep -q true"] timeout: 10s - image: selenium/hub:4 + image: selenium/hub:4.38 ports: - "4444:4444" stop_grace_period: 0s @@ -35,5 +35,5 @@ services: - "8080:80" stop_grace_period: 0s volumes: - - ./test/webDriver:/usr/share/nginx/html:ro + - ./webDriver:/usr/share/nginx/html:ro - ./nginx.conf:/etc/nginx/nginx.conf:ro diff --git a/packages/integration-test/package.json b/packages/integration-test/package.json index ca150f6..31564ff 100644 --- a/packages/integration-test/package.json +++ b/packages/integration-test/package.json @@ -4,25 +4,47 @@ "description": "", "private": true, "scripts": { - "build": "if test \"$CI\" = \"true\"; then mkdir -p ./test/webDriver/static/js/; cp `node --eval=\"console.log(require('path').resolve(require('resolve-cwd')('@testduet/wait-for'), '../../dist/**'))\"` ./test/webDriver/static/js/; else mkdir -p ./test/webDriver/static/; ln --relative --symbolic `node --eval=\"console.log(require('path').resolve(require('resolve-cwd')('@testduet/wait-for'), '../../dist'))\"` ./test/webDriver/static/js; fi", + "build": "npm run build:copy && npm run build:custom --if-present", + "build:copy": "mkdir -p ./webDriver/static/; SRC=$(node --eval=\"console.log(require('path').resolve(require('resolve-cwd')('react-say'), '../../dist'))\"); DEST=$(realpath ./webDriver/static); rm -rf $DEST; mkdir -p $DEST/js/; cp $SRC/** $DEST/js/", "bump": "npm run bump:prod && npm run bump:dev", "bump:dev": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.devDependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", "docker": "docker compose up --quiet-pull --scale chromium=4", + "init": "npm run init:dev --if-present && npm run init:prod --if-present", + "init:dev": "npm install --save-dev @tsconfig/strictest @types/react@18 @types/react-dom@18 @types/react-test-renderer@18 expect resolve-cwd typescript", + "init:prod": "npm install --save @compulim/test-harness@0.0.0-0 react@18 react-dom@18 react-test-renderer@18 react-say@0.0.0-0", "posttest": "if test \"$CI\" = \"true\"; then docker compose logs; docker compose down; fi", "precommit": "ESLINT_USE_FLAT_CONFIG=false eslint --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts ./", "pretest": "if test \"$CI\" = \"true\"; then npm run build; npm run docker -- --detach --wait; fi", "switch": "cat package.json | jq --arg SWITCH_NAME $SWITCH_NAME -r '(.[\"switch:\" + $SWITCH_NAME] // {}) as $TEMPLATE | .devDependencies += ($TEMPLATE.devDependencies // {}) | .dependencies += ($TEMPLATE.dependencies // {})' | tee ./package.json.tmp && mv ./package.json.tmp ./package.json", - "test": "node --import ./happy-dom-env.ts --test ${CI:---watch} **/*.{spec,test}.{[jt]s,[jt]sx,c[jt]s,m[jt]s}" + "test": "npm run test:unit && npm run test:types", + "test:types": "node --experimental-strip-types --import @compulim/test-harness/test-types.import --test **/*.test-types.ts", + "test:unit": "node --experimental-strip-types --experimental-test-coverage --experimental-test-isolation=none --import @compulim/test-harness/happy-dom.import --import @compulim/test-harness/jsx.import --test --test-force-exit **/*.test.{cjs,cts,js,jsx,mjs,mts,ts,tsx}" }, "author": "William Wong (https://github.com/compulim)", "license": "MIT", "localPeerDependencies": { - "react-say": "^0.0.0-0" + "@compulim/test-harness": "0.0.0-0", + "react-say": "0.0.0-0" }, "pinDependencies": { "react": [ "18" + ], + "@types/react": [ + "18" + ], + "@types/react-dom": [ + "18" + ], + "@types/react-test-renderer": [ + "18" + ], + "react-dom": [ + "18" + ], + "react-test-renderer": [ + "18" ] }, "devDependencies": { @@ -41,5 +63,49 @@ }, "dependencies": { "react-say": "^0.0.0-0" + }, + "files": [ + "**/*.cjs", + "**/*.js", + "**/*.mjs", + "webDriver/**/*" + ], + "exports": {}, + "peerDependencies": {}, + "switch:react-16": { + "dependencies": { + "react": "16.8.0", + "react-dom": "16.8.0", + "react-test-renderer": "16.8.0" + }, + "devDependencies": { + "@types/react": "^16", + "@types/react-dom": "^16", + "@types/react-test-renderer": "^16" + } + }, + "switch:react-17": { + "dependencies": { + "react": "17.0.0", + "react-dom": "17.0.0", + "react-test-renderer": "17.0.0" + }, + "devDependencies": { + "@types/react": "^17", + "@types/react-dom": "^17", + "@types/react-test-renderer": "^17" + } + }, + "switch:react-18": { + "dependencies": { + "react": "18.0.0", + "react-dom": "18.0.0", + "react-test-renderer": "18.0.0" + }, + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/react-test-renderer": "^18" + } } } diff --git a/packages/integration-test/tsconfig.json b/packages/integration-test/tsconfig.json index a5f3edc..4542b6f 100644 --- a/packages/integration-test/tsconfig.json +++ b/packages/integration-test/tsconfig.json @@ -7,5 +7,6 @@ "noEmit": true, "target": "esnext" }, + "exclude": ["webDriver/**/*"], "extends": "./tsconfig.custom.json" } diff --git a/packages/pages/package.json b/packages/pages/package.json index e12f561..6b34cef 100644 --- a/packages/pages/package.json +++ b/packages/pages/package.json @@ -4,10 +4,13 @@ "description": "", "private": true, "scripts": { - "build": "esbuild --bundle --entry-names=[name]/[ext]/main --jsx=transform --minify --outdir=./public/static/ --sourcemap app=./src/app/index.tsx", + "build": "esbuild --bundle --entry-names=[name]/[ext]/main --jsx=transform --minify --outdir=./public/static/ --sourcemap $(find ./src -mindepth 1 -maxdepth 1 -type d -printf '%f=./src/%f/index.tsx ')", "bump": "npm run bump:prod && npm run bump:dev", "bump:dev": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.devDependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", + "init": "npm run init:dev --if-present && npm run init:prod --if-present", + "init:dev": "npm install --save-dev @tsconfig/strictest @types/react@18 @types/react-dom@18 esbuild typescript", + "init:prod": "npm install --save react@18 react-dom@18 react-say@0.0.0-0", "precommit": "npm run precommit:eslint && npm run precommit:typescript", "precommit:eslint": "ESLINT_USE_FLAT_CONFIG=false eslint --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts ./src/", "precommit:typescript": "tsc --project ./src/tsconfig.json", @@ -18,7 +21,7 @@ "author": "William Wong (https://github.com/compulim)", "license": "MIT", "localPeerDependencies": { - "react-say": "^0.0.0-0" + "react-say": "0.0.0-0" }, "switch:react-16": { "devDependencies": { @@ -80,5 +83,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-say": "^0.0.0-0" - } + }, + "exports": {}, + "peerDependencies": {} } diff --git a/packages/pages/src/tsconfig.json b/packages/pages/src/tsconfig.json index 4e1f4fa..f167a59 100644 --- a/packages/pages/src/tsconfig.json +++ b/packages/pages/src/tsconfig.json @@ -3,11 +3,7 @@ "allowImportingTsExtensions": true, "esModuleInterop": true, "jsx": "react", - "lib": [ - "DOM", - "ESNext", - "WebWorker" - ], + "lib": ["DOM", "ESNext", "WebWorker"], "module": "esnext", "moduleResolution": "bundler", "noEmit": true, diff --git a/packages/react-say/.eslintrc.custom.yml b/packages/react-say/.eslintrc.custom.yml index 019ca6a..e69de29 100644 --- a/packages/react-say/.eslintrc.custom.yml +++ b/packages/react-say/.eslintrc.custom.yml @@ -1,5 +0,0 @@ -env: - browser: true - es2020: true -rules: - 'import/no-cycle': off # TODO: Fix this. diff --git a/packages/react-say/package.json b/packages/react-say/package.json index d5a9e93..713fc12 100644 --- a/packages/react-say/package.json +++ b/packages/react-say/package.json @@ -25,6 +25,8 @@ "bump": "npm run bump:prod && npm run bump:dev", "bump:dev": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.devDependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", + "init": "npm run init:dev --if-present && npm run init:prod --if-present", + "init:dev": "npm install --save--dev @compulim/test-harness@0.0.0-0 @tsconfig/recommended @tsconfig/strictest @types/node @types/react@18 @types/react-dom@18 escape-string-regexp expect publint react@18 react-dom@18 react-test-renderer@18 tsup typescript", "precommit": "npm run precommit:eslint && npm run precommit:publint && npm run precommit:typescript:production && npm run precommit:typescript:test", "precommit:eslint": "ESLINT_USE_FLAT_CONFIG=false eslint ./src/", "precommit:publint": "publint", @@ -33,7 +35,9 @@ "prepack": "cp ../../CHANGELOG.md . && cp ../../LICENSE . && cp ../../README.md .", "start": "npm run build -- --onSuccess \"touch ../pages/package.json\" --watch", "switch": "cat package.json | jq --arg SWITCH_NAME $SWITCH_NAME -r '(.[\"switch:\" + $SWITCH_NAME] // {}) as $TEMPLATE | .devDependencies += ($TEMPLATE.devDependencies // {}) | .dependencies += ($TEMPLATE.dependencies // {})' | tee ./package.json.tmp && mv ./package.json.tmp ./package.json", - "test": "node --import ./happy-dom-env.ts --test ${CI:---watch} **/*.{spec,test}.{[jt]s,[jt]sx,c[jt]s,m[jt]s}" + "test": "npm run test:unit && npm run test:types", + "test:types": "node --experimental-strip-types --import @compulim/test-harness/test-types.import --test **/*.test-types.ts", + "test:unit": "node --experimental-strip-types --experimental-test-coverage --experimental-test-isolation=none --import @compulim/test-harness/happy-dom.import --import @compulim/test-harness/jsx.import --test --test-force-exit **/*.test.{cjs,cts,js,jsx,mjs,mts,ts,tsx}" }, "repository": { "type": "git", @@ -62,10 +66,22 @@ ], "react": [ "18" + ], + "@types/react-dom": [ + "18" + ], + "@types/react-test-renderer": [ + "18" + ], + "react-dom": [ + "18" + ], + "react-test-renderer": [ + "18" ] }, "peerDependencies": { - "react": ">= 16.8.6" + "react": ">=16.8.0" }, "devDependencies": { "@happy-dom/global-registrator": "^20.0.11", @@ -87,5 +103,38 @@ }, "dependencies": { "prop-types": "^15.8.1" + }, + "localPeerDependencies": { + "@compulim/test-harness": "0.0.0-0" + }, + "switch:react-16": { + "devDependencies": { + "@types/react": "^16", + "@types/react-dom": "^16", + "@types/react-test-renderer": "^16", + "react": "16.8.0", + "react-dom": "16.8.0", + "react-test-renderer": "16.8.0" + } + }, + "switch:react-17": { + "devDependencies": { + "@types/react": "^17", + "@types/react-dom": "^17", + "@types/react-test-renderer": "^17", + "react": "17.0.0", + "react-dom": "17.0.0", + "react-test-renderer": "17.0.0" + } + }, + "switch:react-18": { + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/react-test-renderer": "^18", + "react": "18.0.0", + "react-dom": "18.0.0", + "react-test-renderer": "18.0.0" + } } } diff --git a/packages/react-say/src/tsconfig.custom.json b/packages/react-say/src/tsconfig.custom.json index a994aa5..7a3b017 100644 --- a/packages/react-say/src/tsconfig.custom.json +++ b/packages/react-say/src/tsconfig.custom.json @@ -1,10 +1,3 @@ { - "compilerOptions": { - "allowJs": true, - "types": ["node"] - }, - // "extends": "@tsconfig/strictest/tsconfig.json" - - // Commented out @tsconfig/strictest until ported to TypeScript. - "extends": "@tsconfig/recommended/tsconfig.json" + "extends": "@tsconfig/strictest/tsconfig.json" } diff --git a/packages/react-say/src/tsconfig.precommit.production.json b/packages/react-say/src/tsconfig.precommit.production.json index 177a039..99282dc 100644 --- a/packages/react-say/src/tsconfig.precommit.production.json +++ b/packages/react-say/src/tsconfig.precommit.production.json @@ -10,6 +10,31 @@ "target": "ESNext", "types": [] }, - "exclude": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.test.ts", "**/*.test.tsx", "__tests__/**/*"], + "exclude": [ + "__tests__/**/*.cjs", + "__tests__/**/*.cts", + "__tests__/**/*.js", + "__tests__/**/*.jsx", + "__tests__/**/*.mjs", + "__tests__/**/*.mts", + "__tests__/**/*.ts", + "__tests__/**/*.tsx", + "**/*.spec.cjs", + "**/*.spec.cts", + "**/*.spec.js", + "**/*.spec.jsx", + "**/*.spec.mjs", + "**/*.spec.mts", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.cjs", + "**/*.test.cts", + "**/*.test.js", + "**/*.test.jsx", + "**/*.test.mjs", + "**/*.test.mts", + "**/*.test.ts", + "**/*.test.tsx" + ], "extends": "./tsconfig.custom.json" } diff --git a/packages/react-say/src/tsconfig.precommit.test.json b/packages/react-say/src/tsconfig.precommit.test.json index 85397d5..cc38d0c 100644 --- a/packages/react-say/src/tsconfig.precommit.test.json +++ b/packages/react-say/src/tsconfig.precommit.test.json @@ -11,5 +11,30 @@ "types": ["node"] }, "extends": "@tsconfig/recommended/tsconfig.json", - "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.test.ts", "**/*.test.tsx", "__tests__/**/*"] + "include": [ + "__tests__/**/*.cjs", + "__tests__/**/*.cts", + "__tests__/**/*.js", + "__tests__/**/*.jsx", + "__tests__/**/*.mjs", + "__tests__/**/*.mts", + "__tests__/**/*.ts", + "__tests__/**/*.tsx", + "**/*.spec.cjs", + "**/*.spec.cts", + "**/*.spec.js", + "**/*.spec.jsx", + "**/*.spec.mjs", + "**/*.spec.mts", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.cjs", + "**/*.test.cts", + "**/*.test.js", + "**/*.test.jsx", + "**/*.test.mjs", + "**/*.test.mts", + "**/*.test.ts", + "**/*.test.tsx" + ] } diff --git a/packages/react-say/tsup.config.override.ts b/packages/react-say/tsup.config.override.ts new file mode 100644 index 0000000..e258365 --- /dev/null +++ b/packages/react-say/tsup.config.override.ts @@ -0,0 +1,7 @@ +import { type Options } from 'tsup'; + +export default function override(options: Options): Options { + return { + ...options + }; +} diff --git a/packages/react-say/tsup.config.ts b/packages/react-say/tsup.config.ts index 4aad709..ad1a1f7 100644 --- a/packages/react-say/tsup.config.ts +++ b/packages/react-say/tsup.config.ts @@ -1,16 +1,23 @@ -import { defineConfig } from 'tsup'; +import { defineConfig, type Options } from 'tsup'; +import overrideConfig from './tsup.config.override.ts'; -export default defineConfig([ - { - dts: true, - entry: { - 'react-say': './src/index.mjs' - }, - format: ['cjs', 'esm'], - sourcemap: true, - target: 'esnext', +const baseConfig: Options = { + dts: true, + entry: { + 'react-say': './src/index.ts' + }, + sourcemap: true +}; - // Remove the followings after we ported to TypeScript. - loader: { '.js': 'jsx' } - } +export default defineConfig([ + overrideConfig({ + ...baseConfig, + format: ['esm'], + target: 'esnext' + }), + overrideConfig({ + ...baseConfig, + format: ['cjs'], + target: 'es2019' // For compatibility with Webpack 4. + }) ]); diff --git a/packages/test-harness/.eslintrc.custom.yml b/packages/test-harness/.eslintrc.custom.yml new file mode 100644 index 0000000..e69de29 diff --git a/packages/test-harness/.eslintrc.yml b/packages/test-harness/.eslintrc.yml new file mode 100644 index 0000000..d9a0f88 --- /dev/null +++ b/packages/test-harness/.eslintrc.yml @@ -0,0 +1,23 @@ +extends: + - ./.eslintrc.custom.yml +ignorePatterns: + - '**/*.d.mts' + - '**/*.d.ts' +overrides: + - env: + commonjs: true + files: + - '**/*.js' + - '**/*.ts' + parserOptions: + ecmaVersion: latest + rules: + import/no-commonjs: off + - files: + - '**/*.mjs' + - '**/*.mts' + parserOptions: + ecmaVersion: latest + sourceType: module +rules: + import/no-mutable-exports: off diff --git a/packages/test-harness/.gitignore b/packages/test-harness/.gitignore new file mode 100644 index 0000000..2ccbe46 --- /dev/null +++ b/packages/test-harness/.gitignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/packages/test-harness/package.json b/packages/test-harness/package.json new file mode 100644 index 0000000..aaffdc5 --- /dev/null +++ b/packages/test-harness/package.json @@ -0,0 +1,131 @@ +{ + "name": "@compulim/test-harness", + "version": "0.0.0-0", + "description": "", + "private": true, + "files": [ + "./src/**/*" + ], + "exports": { + "./describeEach": { + "import": { + "types": "./src/describeEach.d.mts", + "default": "./src/describeEach.mjs" + }, + "require": { + "types": "./src/describeEach.d.ts", + "default": "./src/describeEach.js" + } + }, + "./happy-dom.import": "./src/happy-dom.import.mjs", + "./test-types.import": "./src/test-types.import.mjs", + "./act": { + "import": { + "default": "./src/act.mjs", + "types": "./src/act.d.mts" + }, + "require": { + "default": "./src/act.js", + "types": "./src/act.d.ts" + } + }, + "./jsx.import": "./src/jsx.import.mjs", + "./render": { + "import": { + "default": "./src/render.mjs", + "types": "./src/render.d.mts" + }, + "require": { + "default": "./src/render.js", + "types": "./src/render.d.ts" + } + }, + "./renderHook": { + "import": { + "default": "./src/renderHook.mjs", + "types": "./src/renderHook.d.mts" + }, + "require": { + "default": "./src/renderHook.js", + "types": "./src/renderHook.d.ts" + } + } + }, + "scripts": { + "bump": "npm run bump:prod && npm run bump:dev", + "bump:dev": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.devDependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", + "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", + "init": "npm run init:dev --if-present && npm run init:prod --if-present", + "init:dev": "npm install --save-dev @tsconfig/strictest @types/react@18 @types/react-dom@18 @types/react-test-renderer@18 react@18 react-dom@18 react-test-renderer@18 resolve-cwd", + "init:prod": "npm install --save @happy-dom/global-registrator @testing-library/dom @testing-library/react esbuild escape-string-regexp expect typescript", + "precommit": "npm run precommit:eslint && npm run precommit:typescript", + "precommit:eslint": "ESLINT_USE_FLAT_CONFIG=false eslint --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts ./", + "precommit:typescript": "tsc --noEmit --project ./src/tsconfig.precommit.json", + "switch": "cat package.json | jq --arg SWITCH_NAME $SWITCH_NAME -r '(.[\"switch:\" + $SWITCH_NAME] // {}) as $TEMPLATE | .devDependencies += ($TEMPLATE.devDependencies // {}) | .dependencies += ($TEMPLATE.dependencies // {})' | tee ./package.json.tmp && mv ./package.json.tmp ./package.json" + }, + "peerDependencies": { + "typescript": ">=5", + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "react-test-renderer": ">=16.8.0" + }, + "switch:react-16": { + "dependencies": { + "@testing-library/react": "^12", + "@testing-library/react-hooks": "latest" + }, + "devDependencies": { + "@types/react": "^16", + "@types/react-dom": "^16", + "@types/react-test-renderer": "^16", + "react": "16.8.0", + "react-dom": "16.8.0", + "react-test-renderer": "16.8.0" + } + }, + "switch:react-17": { + "dependencies": { + "@testing-library/react": "^12", + "@testing-library/react-hooks": "latest" + }, + "devDependencies": { + "@types/react": "^17", + "@types/react-dom": "^17", + "@types/react-test-renderer": "^17", + "react": "17.0.0", + "react-dom": "17.0.0", + "react-test-renderer": "17.0.0" + } + }, + "switch:react-18": { + "dependencies": {}, + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/react-test-renderer": "^18", + "react": "18.0.0", + "react-dom": "18.0.0", + "react-test-renderer": "18.0.0" + } + }, + "pinDependencies": { + "@types/react": [ + "18" + ], + "@types/react-dom": [ + "18" + ], + "@types/react-test-renderer": [ + "18" + ], + "react": [ + "18" + ], + "react-dom": [ + "18" + ], + "react-test-renderer": [ + "18" + ] + } +} diff --git a/packages/test-harness/src/act.d.mts b/packages/test-harness/src/act.d.mts new file mode 100644 index 0000000..01aaa5e --- /dev/null +++ b/packages/test-harness/src/act.d.mts @@ -0,0 +1,3 @@ +declare const act: (fn: () => void) => void; + +export { act }; diff --git a/packages/test-harness/src/act.d.ts b/packages/test-harness/src/act.d.ts new file mode 100644 index 0000000..45ece16 --- /dev/null +++ b/packages/test-harness/src/act.d.ts @@ -0,0 +1,5 @@ +declare const act: (fn: () => void) => void; + +declare const export_: { act: typeof act }; + +export = export_; diff --git a/packages/test-harness/src/act.js b/packages/test-harness/src/act.js new file mode 100644 index 0000000..aa420c0 --- /dev/null +++ b/packages/test-harness/src/act.js @@ -0,0 +1,11 @@ +/** @type {(fn: () => void) => void} */ +let act; + +try { + // eslint-disable-next-line import/no-unresolved + ({ act } = require('@testing-library/react-hooks')); +} catch { + ({ act } = require('@testing-library/react')); +} + +module.exports = { act }; diff --git a/packages/test-harness/src/act.mjs b/packages/test-harness/src/act.mjs new file mode 100644 index 0000000..4f3cd95 --- /dev/null +++ b/packages/test-harness/src/act.mjs @@ -0,0 +1,11 @@ +/** @type {(fn: () => void) => void} */ +let act; + +try { + // eslint-disable-next-line import/no-unresolved + ({ act } = await import('@testing-library/react-hooks')); +} catch { + ({ act } = await import('@testing-library/react')); +} + +export { act }; diff --git a/packages/test-harness/src/describeEach.d.mts b/packages/test-harness/src/describeEach.d.mts new file mode 100644 index 0000000..da2cac3 --- /dev/null +++ b/packages/test-harness/src/describeEach.d.mts @@ -0,0 +1,50 @@ +declare interface DescribeEach { + (rows: readonly T[]): (message: string, fn: (...args: T) => void) => void; + only(rows: readonly T[]): (message: string, fn: (...args: T) => void) => void; +} + +declare const describeEach: DescribeEach; + +export { describeEach }; + +// export declare function describeEach( +// rows: readonly (readonly [T])[] +// ): (message: string, fn: (arg0: T) => void) => void; + +// export declare function describeEach( +// rows: readonly (readonly [T0, T1])[] +// ): (message: string, fn: (arg0: T0, arg1: T1) => void) => void; + +// export declare function describeEach( +// rows: readonly (readonly [T0, T1, T2])[] +// ): (message: string, fn: (arg0: T0, arg1: T1, arg3: T2) => void) => void; + +// export declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3])[] +// ): (message: string, fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3) => void) => void; + +// export declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3, T4])[] +// ): (message: string, fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4) => void) => void; + +// export declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3, T4, T5])[] +// ): (message: string, fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => void) => void; + +// export declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3, T4, T5, T6])[] +// ): (message: string, fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6) => void) => void; + +// export declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3, T4, T5, T6, T7])[] +// ): ( +// message: string, +// fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7) => void +// ) => void; + +// export declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3, T4, T5, T6, T7, T8])[] +// ): ( +// message: string, +// fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8) => void +// ) => void; diff --git a/packages/test-harness/src/describeEach.d.ts b/packages/test-harness/src/describeEach.d.ts new file mode 100644 index 0000000..f50f320 --- /dev/null +++ b/packages/test-harness/src/describeEach.d.ts @@ -0,0 +1,50 @@ +declare interface DescribeEach { + (rows: readonly T[]): (message: string, fn: (...args: T) => void) => void; + only(rows: readonly T[]): (message: string, fn: (...args: T) => void) => void; +} + +exports = { describeEach: DescribeEach }; + +// declare function describeEach( +// rows: readonly (readonly [T])[] +// ): (message: string, fn: (arg0: T) => void) => void; + +// declare function describeEach( +// rows: readonly (readonly [T0, T1])[] +// ): (message: string, fn: (arg0: T0, arg1: T1) => void) => void; + +// declare function describeEach( +// rows: readonly (readonly [T0, T1, T2])[] +// ): (message: string, fn: (arg0: T0, arg1: T1, arg3: T2) => void) => void; + +// declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3])[] +// ): (message: string, fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3) => void) => void; + +// declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3, T4])[] +// ): (message: string, fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4) => void) => void; + +// declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3, T4, T5])[] +// ): (message: string, fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => void) => void; + +// declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3, T4, T5, T6])[] +// ): (message: string, fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6) => void) => void; + +// declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3, T4, T5, T6, T7])[] +// ): ( +// message: string, +// fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7) => void +// ) => void; + +// declare function describeEach( +// rows: readonly (readonly [T0, T1, T2, T3, T4, T5, T6, T7, T8])[] +// ): ( +// message: string, +// fn: (arg0: T0, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8) => void +// ) => void; + +// exports = { describeEach }; diff --git a/packages/test-harness/src/describeEach.js b/packages/test-harness/src/describeEach.js new file mode 100644 index 0000000..d84b6d4 --- /dev/null +++ b/packages/test-harness/src/describeEach.js @@ -0,0 +1,26 @@ +import { describe } from 'node:test'; +import { format } from 'util'; + +function describe_(describe, rows) { + return ( + /** @type {string} */ + message, + fn + ) => { + for (const row of rows) { + const countFormatting = message.replaceAll('%%', '').split('%').length - 1; + + describe(format(message, ...row.slice(0, countFormatting)), () => fn(...row)); + } + }; +} + +function describeEach(rows) { + return describe_(describe, rows); +} + +describeEach.only = function only(rows) { + return describe_(describe.only, rows); +}; + +exports = { describeEach }; diff --git a/packages/test-harness/src/describeEach.mjs b/packages/test-harness/src/describeEach.mjs new file mode 100644 index 0000000..ae37c18 --- /dev/null +++ b/packages/test-harness/src/describeEach.mjs @@ -0,0 +1,26 @@ +import { describe } from 'node:test'; +import { format } from 'util'; + +function describe_(describe, rows) { + return ( + /** @type {string} */ + message, + fn + ) => { + for (const row of rows) { + const countFormatting = message.replaceAll('%%', '').split('%').length - 1; + + describe(format(message, ...row.slice(0, countFormatting)), () => fn(...row)); + } + }; +} + +function describeEach(rows) { + return describe_(describe, rows); +} + +describeEach.only = function only(rows) { + return describe_(describe.only, rows); +}; + +export { describeEach }; diff --git a/packages/test-harness/src/happy-dom.import.mjs b/packages/test-harness/src/happy-dom.import.mjs new file mode 100644 index 0000000..d99e686 --- /dev/null +++ b/packages/test-harness/src/happy-dom.import.mjs @@ -0,0 +1,21 @@ +import { GlobalRegistrator } from '@happy-dom/global-registrator'; +import { after, before } from 'node:test'; + +// `node --import` does passthrough things set on `globalThis` to the test. +// However, `node --test-global-setup` will not passthrough. The test will run without `globalThis.document`. +// Not sure if this is intentional or not. We must load this module using `node --import`. + +before(() => { + GlobalRegistrator.register({ + height: 1080, + url: 'http://localhost:3000', + width: 1920 + }); +}); + +after(() => { + // Must unregister, otherwise, there will be promises lingering. + GlobalRegistrator.unregister(); +}); + +export {}; diff --git a/packages/test-harness/src/jsx.import.mjs b/packages/test-harness/src/jsx.import.mjs new file mode 100644 index 0000000..2bbf5d9 --- /dev/null +++ b/packages/test-harness/src/jsx.import.mjs @@ -0,0 +1,34 @@ +import { buildSync } from 'esbuild'; +import { registerHooks } from 'node:module'; +import { resolve } from 'node:path'; +import { fileURLToPath, URL } from 'node:url'; + +registerHooks({ + load( + /** @type {string} */ + url, + context, + nextLoad + ) { + // Could be file: or node:. + if (new URL(url).protocol === 'file:') { + const filename = resolve(fileURLToPath(url)); + + // Could be loading /node_modules/. + if (filename.endsWith('.jsx') || filename.endsWith('.tsx')) { + return { + format: 'module-typescript', + shortCircuit: true, + source: buildSync({ + entryPoints: [filename], + platform: 'node', + target: 'esnext', + write: false + }).outputFiles[0].text + }; + } + } + + return nextLoad(url, context); + } +}); diff --git a/packages/test-harness/src/render.d.mts b/packages/test-harness/src/render.d.mts new file mode 100644 index 0000000..922d530 --- /dev/null +++ b/packages/test-harness/src/render.d.mts @@ -0,0 +1 @@ +export { render, type RenderResult } from '@testing-library/react'; diff --git a/packages/test-harness/src/render.d.ts b/packages/test-harness/src/render.d.ts new file mode 100644 index 0000000..922d530 --- /dev/null +++ b/packages/test-harness/src/render.d.ts @@ -0,0 +1 @@ +export { render, type RenderResult } from '@testing-library/react'; diff --git a/packages/test-harness/src/render.js b/packages/test-harness/src/render.js new file mode 100644 index 0000000..0ed435d --- /dev/null +++ b/packages/test-harness/src/render.js @@ -0,0 +1 @@ +module.exports = { render: require('@testing-library/react') }; diff --git a/packages/test-harness/src/render.mjs b/packages/test-harness/src/render.mjs new file mode 100644 index 0000000..3c1b4c7 --- /dev/null +++ b/packages/test-harness/src/render.mjs @@ -0,0 +1 @@ +export { render } from '@testing-library/react'; diff --git a/packages/test-harness/src/renderHook.d.mts b/packages/test-harness/src/renderHook.d.mts new file mode 100644 index 0000000..c20c4e4 --- /dev/null +++ b/packages/test-harness/src/renderHook.d.mts @@ -0,0 +1,6 @@ +declare const renderHook: ( + render: (props: P) => T, + options?: { initialProps: P } +) => { rerender: (props: P) => void; result: { current: T } }; + +export { renderHook }; diff --git a/packages/test-harness/src/renderHook.d.ts b/packages/test-harness/src/renderHook.d.ts new file mode 100644 index 0000000..3451197 --- /dev/null +++ b/packages/test-harness/src/renderHook.d.ts @@ -0,0 +1,8 @@ +declare const renderHook: ( + render: (props: P) => T, + options?: { initialProps: P } +) => { rerender: (props: P) => void; result: { current: T } }; + +declare const export_: { renderHook: typeof renderHook }; + +export = export_; diff --git a/packages/test-harness/src/renderHook.js b/packages/test-harness/src/renderHook.js new file mode 100644 index 0000000..a4e07d0 --- /dev/null +++ b/packages/test-harness/src/renderHook.js @@ -0,0 +1,11 @@ +/** @type {(render: (props: P) => T, options?: { initialProps: P }) => { rerender: (props: P) => void; result: { current: T } }} */ +let renderHook; + +try { + // eslint-disable-next-line import/no-unresolved + ({ renderHook } = require('@testing-library/react-hooks')); +} catch { + ({ renderHook } = require('@testing-library/react')); +} + +module.exports = { renderHook }; diff --git a/packages/test-harness/src/renderHook.mjs b/packages/test-harness/src/renderHook.mjs new file mode 100644 index 0000000..df501fa --- /dev/null +++ b/packages/test-harness/src/renderHook.mjs @@ -0,0 +1,11 @@ +/** @type {(render: (props: P) => T, options?: { initialProps: P }) => { rerender: (props: P) => void; result: { current: T } }} */ +let renderHook; + +try { + // eslint-disable-next-line import/no-unresolved + ({ renderHook } = await import('@testing-library/react-hooks')); +} catch { + ({ renderHook } = await import('@testing-library/react')); +} + +export { renderHook }; diff --git a/packages/test-harness/src/test-types.import.mjs b/packages/test-harness/src/test-types.import.mjs new file mode 100644 index 0000000..da6da83 --- /dev/null +++ b/packages/test-harness/src/test-types.import.mjs @@ -0,0 +1,31 @@ +import { readFile } from 'node:fs/promises'; +import { registerHooks } from 'node:module'; +import { resolve } from 'node:path'; +import { fileURLToPath, URL } from 'node:url'; + +const stub = (await readFile(resolve(fileURLToPath(import.meta.url), '../test-types.stub.mts'))).toString(); + +registerHooks({ + load( + /** @type {string} */ + url, + context, + nextLoad + ) { + // Could be file: or node:. + if (new URL(url).protocol === 'file:') { + const filename = resolve(fileURLToPath(url)); + + // Could be loading /node_modules/. + if (filename.endsWith('.test-types.ts')) { + return { + format: 'module-typescript', + shortCircuit: true, + source: `const FILENAME=${JSON.stringify(filename)};\n${stub}` + }; + } + } + + return nextLoad(url, context); + } +}); diff --git a/packages/test-harness/src/test-types.stub.mts b/packages/test-harness/src/test-types.stub.mts new file mode 100644 index 0000000..bdc43ac --- /dev/null +++ b/packages/test-harness/src/test-types.stub.mts @@ -0,0 +1,96 @@ +import escapeStringRegexp from 'escape-string-regexp'; +import { expect } from 'expect'; +import { readFileSync } from 'fs'; +import { describe, test } from 'node:test'; +import { + createCompilerHost, + createProgram, + flattenDiagnosticMessageText, + getPreEmitDiagnostics, + JsxEmit, + ModuleKind, + ModuleResolutionKind, + ScriptTarget, + type CompilerOptions +} from 'typescript'; + +// Passed when stubbing. +declare global { + const FILENAME: string; +} + +const TS_EXPECT_ERROR = /(\/\/\s+)(@ts-expect-error)[\s+(.*)]/gu; +const TSCONFIG: CompilerOptions = { + allowImportingTsExtensions: true, + allowSyntheticDefaultImports: true, + jsx: JsxEmit.React, + lib: ['lib.dom.d.ts', 'lib.esnext.d.ts'], + module: ModuleKind.ESNext, + moduleResolution: ModuleResolutionKind.Bundler, + noEmit: true, + skipLibCheck: true, + strict: true, + target: ScriptTarget.ESNext, + types: [] +}; + +async function compile(filename: string) { + const program = createProgram([filename], TSCONFIG); + + const emitResult = program.emit(); + const allDiagnostics = getPreEmitDiagnostics(program).concat(emitResult.diagnostics); + + allDiagnostics.forEach(({ file, messageText, start }) => { + if (file && start) { + const { line, character } = file.getLineAndCharacterOfPosition(start); + const message = flattenDiagnosticMessageText(messageText, '\n'); + + throw new Error(`Failed to compile ${file.fileName} (${line + 1},${character + 1}): ${message}`); + } else { + throw new Error(flattenDiagnosticMessageText(messageText, '\n')); + } + }); +} + +async function checkExpectError(filename: string) { + const host = createCompilerHost({}); + + host.readFile = path => readFileSync(path).toString().replace(TS_EXPECT_ERROR, '$1'); + + const program = createProgram({ host, options: TSCONFIG, rootNames: [filename] }); + + const emitResult = program.emit(); + const allDiagnostics = getPreEmitDiagnostics(program).concat(emitResult.diagnostics); + + allDiagnostics.forEach(({ file, messageText, start }) => { + if (file && start) { + const { line } = file.getLineAndCharacterOfPosition(start); + const message = flattenDiagnosticMessageText(messageText, '\n'); + + const expectedErrorLine = file.getFullText().split('\n')[line - 1]; + const expectedError = expectedErrorLine?.replace(/\s*\/\/\s+/u, '').trim(); + let expectedErrors = [expectedError]; + + try { + if (expectedError) { + const parsed = JSON.parse(expectedError); + + if (Array.isArray(expectedErrors) && expectedErrors.every(value => typeof value === 'string')) { + expectedErrors = parsed; + } + } + } catch {} + + expect(message).toEqual( + expect.stringMatching(new RegExp(expectedErrors.map(value => escapeStringRegexp(value ?? '')).join('|'))) + ); + } else { + throw new Error(flattenDiagnosticMessageText(messageText, '\n')); + } + }); +} + +describe(FILENAME, () => { + test('should succeed', () => compile(FILENAME)); + test('should have @ts-expect-error describing compile errors correctly', () => checkExpectError(FILENAME)); +}); diff --git a/packages/test-harness/src/tsconfig.json b/packages/test-harness/src/tsconfig.json new file mode 100644 index 0000000..b2ad3f4 --- /dev/null +++ b/packages/test-harness/src/tsconfig.json @@ -0,0 +1,10 @@ +// This configuration file is for VSCode only. +{ + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "target": "esnext", + "types": ["node"] + }, + "extends": "@tsconfig/strictest" +} diff --git a/packages/test-harness/src/tsconfig.precommit.json b/packages/test-harness/src/tsconfig.precommit.json new file mode 100644 index 0000000..76da2a4 --- /dev/null +++ b/packages/test-harness/src/tsconfig.precommit.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "target": "esnext", + "types": ["node"] + }, + "extends": "@tsconfig/strictest" +} From b9a3d7e89cd94c5f5b40a6a1e010b067262aba8f Mon Sep 17 00:00:00 2001 From: William Wong Date: Fri, 2 Jan 2026 01:49:33 +0000 Subject: [PATCH 2/6] Fix scaffold --- .eslintrc.react.yml | 3 + lerna.json | 7 - package-lock.json | 341 ++++++------------ package.json | 17 +- packages/integration-test/package.json | 90 +++-- packages/pages/package.json | 8 +- packages/react-say/.eslintrc.yml | 3 + .../__setup__/typingTestTransformer.js | 95 ----- packages/react-say/__tests__/types/.gitignore | 1 - packages/react-say/happy-dom-env.ts | 7 - packages/react-say/package.json | 71 ++-- packages/react-say/src/Composer.jsx | 14 +- packages/react-say/src/Say.jsx | 2 + packages/react-say/src/SayButton.jsx | 4 +- packages/react-say/src/SayUtterance.jsx | 3 + packages/react-say/src/tsconfig.custom.json | 9 +- packages/react-say/tsup.config.override.ts | 5 +- packages/test-harness/package.json | 33 +- 18 files changed, 262 insertions(+), 451 deletions(-) delete mode 100644 lerna.json delete mode 100644 packages/react-say/__tests__/__setup__/typingTestTransformer.js delete mode 100644 packages/react-say/__tests__/types/.gitignore delete mode 100644 packages/react-say/happy-dom-env.ts diff --git a/.eslintrc.react.yml b/.eslintrc.react.yml index e8998b7..a6d75d2 100644 --- a/.eslintrc.react.yml +++ b/.eslintrc.react.yml @@ -7,3 +7,6 @@ plugins: rules: react-hooks/refs: off # We use refs intensively react-hooks/preserve-manual-memoization: off # Does not know `valibot.parseProps` +settings: + react: + version: 18.3.1 diff --git a/lerna.json b/lerna.json deleted file mode 100644 index 9b535f1..0000000 --- a/lerna.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "lerna": "2.11.0", - "packages": [ - "packages/*" - ], - "version": "0.0.0" -} diff --git a/package-lock.json b/package-lock.json index 2c0a9fc..ad395fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,18 @@ { - "name": "react-say-root", + "name": "@compulim/root", "version": "2.2.2-0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "react-say-root", + "name": "@compulim/root", "version": "2.2.2-0", + "license": "MIT", "workspaces": [ "packages/react-say", + "packages/integration-test", "packages/pages", - "packages/integration-test" + "packages/test-harness" ], "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.50.0", @@ -55,7 +57,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -315,6 +316,18 @@ "node": ">=6.9.0" } }, + "node_modules/@compulim/integration-test": { + "resolved": "packages/integration-test", + "link": true + }, + "node_modules/@compulim/pages": { + "resolved": "packages/pages", + "link": true + }, + "node_modules/@compulim/test-harness": { + "resolved": "packages/test-harness", + "link": true + }, "node_modules/@emnapi/core": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", @@ -459,7 +472,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -476,7 +488,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -493,7 +504,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -510,7 +520,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -527,7 +536,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -544,7 +552,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -561,7 +568,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -578,7 +584,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -595,7 +600,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -612,7 +616,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -629,7 +632,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -646,7 +648,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -663,7 +664,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -680,7 +680,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -697,7 +696,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -714,7 +712,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -731,7 +728,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -748,7 +744,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -765,7 +760,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -782,7 +776,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -799,7 +792,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -816,7 +808,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -833,7 +824,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -850,7 +840,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -867,7 +856,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -884,7 +872,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1083,37 +1070,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@happy-dom/global-registrator": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/@happy-dom/global-registrator/-/global-registrator-18.0.1.tgz", - "integrity": "sha512-xCy/cpEP8xyJ6u0eokYgaQxeUmcKqHx/+aC3R0DLa7/S38efhZAVDQqLJ5zzTguLFS0gvAzZHP40NGaLwRyapQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.0.0", - "happy-dom": "^18.0.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@happy-dom/global-registrator/node_modules/@types/node": { - "version": "20.19.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", - "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@happy-dom/global-registrator/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1202,7 +1158,6 @@ "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", - "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1212,7 +1167,6 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", - "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0" @@ -1225,7 +1179,6 @@ "version": "30.1.0", "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1235,7 +1188,6 @@ "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -1249,7 +1201,6 @@ "version": "30.0.5", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -1262,7 +1213,6 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", - "dev": true, "license": "MIT", "dependencies": { "@jest/pattern": "30.0.1", @@ -1664,7 +1614,6 @@ "version": "0.34.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", - "dev": true, "license": "MIT" }, "node_modules/@testduet/given-when-then": { @@ -1677,30 +1626,19 @@ "@testduet/given-when-then": "^0.1.0" } }, - "node_modules/@testduet/wait-for": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@testduet/wait-for/-/wait-for-0.1.1.tgz", - "integrity": "sha512-R7sII18qvbdITpgCyJVtA/kO8UWluC4Fzz4fVOd+xS/HLuCbTjXO9uvmTtblM3R+dX9iCrl2wavo+Hw/2d0EGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@testduet/wait-for": "^0.1.1" - } - }, "node_modules/@testing-library/dom": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", - "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "dev": true, + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", - "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", + "picocolors": "1.1.1", "pretty-format": "^27.0.2" }, "engines": { @@ -1711,7 +1649,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1721,7 +1658,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -1734,7 +1670,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1", @@ -1749,14 +1684,12 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, "license": "MIT" }, "node_modules/@testing-library/react": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", - "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", - "dev": true, + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.1.tgz", + "integrity": "sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5" @@ -1809,7 +1742,6 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, "license": "MIT" }, "node_modules/@types/dom-speech-recognition": { @@ -1830,14 +1762,12 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -1847,7 +1777,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -1871,7 +1800,6 @@ "version": "25.0.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -1887,14 +1815,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.27", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1905,31 +1833,38 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" } }, + "node_modules/@types/react-test-renderer": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.3.1.tgz", + "integrity": "sha512-vAhnk0tG2eGa37lkU9+s5SoroCsRI08xnsWFiAXOuPH2jqzMbcXvKExXViPi1P5fIklDeCvXqyrdmipFaSkZrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "^18" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, "license": "MIT" }, "node_modules/@types/whatwg-mimetype": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", - "dev": true, "license": "MIT" }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -1939,7 +1874,6 @@ "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -1977,7 +1911,6 @@ "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", @@ -2451,7 +2384,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2503,7 +2435,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -2533,7 +2464,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, "license": "Apache-2.0", "dependencies": { "dequal": "^2.0.3" @@ -2771,7 +2701,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2800,7 +2729,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2925,7 +2853,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -2958,7 +2885,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", - "dev": true, "funding": [ { "type": "github", @@ -2980,7 +2906,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2993,7 +2918,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/commander": { @@ -3200,7 +3124,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3223,7 +3146,6 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, "license": "MIT" }, "node_modules/dunder-proto": { @@ -3452,10 +3374,8 @@ "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -3519,7 +3439,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3690,7 +3609,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -4057,7 +3975,6 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", - "dev": true, "license": "MIT", "dependencies": { "@jest/expect-utils": "30.2.0", @@ -4116,7 +4033,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4430,41 +4346,8 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, - "node_modules/happy-dom": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.1.tgz", - "integrity": "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.0.0", - "@types/whatwg-mimetype": "^3.0.2", - "whatwg-mimetype": "^3.0.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/happy-dom/node_modules/@types/node": { - "version": "20.19.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", - "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/happy-dom/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -4482,7 +4365,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4881,7 +4763,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5101,7 +4982,6 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", - "dev": true, "license": "MIT", "dependencies": { "@jest/diff-sequences": "30.0.1", @@ -5117,7 +4997,6 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", - "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", @@ -5133,7 +5012,6 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -5154,7 +5032,6 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", - "dev": true, "license": "MIT", "dependencies": { "@jest/types": "30.2.0", @@ -5169,7 +5046,6 @@ "version": "30.0.1", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5179,7 +5055,6 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", - "dev": true, "license": "MIT", "dependencies": { "@jest/types": "30.2.0", @@ -5389,7 +5264,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, "license": "MIT", "bin": { "lz-string": "bin/bin.js" @@ -5419,7 +5293,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5433,7 +5306,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -5852,7 +5724,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5952,7 +5823,6 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -5980,7 +5850,6 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "30.0.5", @@ -5995,7 +5864,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -6008,7 +5876,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, "license": "MIT" }, "node_modules/prop-types": { @@ -6059,7 +5926,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -6072,7 +5938,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -6091,13 +5956,38 @@ "resolved": "packages/react-say", "link": true }, - "node_modules/react-say-integration-test": { - "resolved": "packages/integration-test", - "link": true + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } }, - "node_modules/react-say-pages": { - "resolved": "packages/pages", - "link": true + "node_modules/react-test-renderer": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", + "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", + "license": "MIT", + "dependencies": { + "react-is": "^18.3.1", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/readdirp": { "version": "4.1.2", @@ -6514,7 +6404,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6544,7 +6433,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" @@ -6557,7 +6445,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6835,7 +6722,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -6941,7 +6827,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -7159,9 +7044,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7200,7 +7083,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -7210,7 +7092,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -7284,7 +7165,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7526,7 +7406,6 @@ "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -7545,29 +7424,29 @@ } }, "packages/integration-test": { - "name": "react-say-integration-test", + "name": "@compulim/integration-test", "version": "0.0.0-0", "license": "MIT", "dependencies": { - "react-say": "^0.0.0-0" + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-say": "0.0.0-0", + "react-test-renderer": "^18.3.1" }, "devDependencies": { - "@happy-dom/global-registrator": "^18.0.1", "@testduet/given-when-then": "^0.1.0-main.334801c", - "@testduet/wait-for": "^0.1.1", "@testing-library/react": "^16.3.0", "@tsconfig/strictest": "^2.0.5", + "@types/react": "^18.3.27", "@types/react-dom": "^18.3.7", + "@types/react-test-renderer": "^18.3.1", "expect": "^30.0.4", - "happy-dom": "^18.0.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", "resolve-cwd": "^3.0.0", "typescript": "^5.8.3" } }, "packages/pages": { - "name": "react-say-pages", + "name": "@compulim/pages", "version": "0.0.0-0", "license": "MIT", "dependencies": { @@ -7595,16 +7474,11 @@ "prop-types": "^15.8.1" }, "devDependencies": { - "@happy-dom/global-registrator": "^20.0.11", "@testduet/given-when-then": "^0.1.0", "@tsconfig/recommended": "^1.0.13", "@tsconfig/strictest": "^2.0.8", - "@types/node": "^25.0.3", "@types/react": "^18.3.27", - "esbuild": "^0.27.2", - "escape-string-regexp": "^5.0.0", "expect": "^30.2.0", - "happy-dom": "^20.0.11", "has-resolved": "^1.1.0", "prettier": "^3.7.4", "publint": "^0.3.16", @@ -7613,14 +7487,42 @@ "typescript": "^5.9.3" }, "peerDependencies": { - "react": ">= 16.8.6" + "react": ">=16.8.0" } }, - "packages/react-say/node_modules/@happy-dom/global-registrator": { + "packages/test-harness": { + "name": "@compulim/test-harness", + "version": "0.0.0-0", + "license": "MIT", + "dependencies": { + "@happy-dom/global-registrator": "^20.0.11", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.1", + "esbuild": "^0.27.2", + "escape-string-regexp": "^5.0.0", + "expect": "^30.2.0" + }, + "devDependencies": { + "@tsconfig/strictest": "^2.0.8", + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.7", + "@types/react-test-renderer": "^18.3.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-test-renderer": "^18.3.1", + "resolve-cwd": "^3.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "react-test-renderer": ">=16.8.0", + "typescript": "^5.9.3" + } + }, + "packages/test-harness/node_modules/@happy-dom/global-registrator": { "version": "20.0.11", "resolved": "https://registry.npmjs.org/@happy-dom/global-registrator/-/global-registrator-20.0.11.tgz", "integrity": "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "^20.0.0", @@ -7630,28 +7532,19 @@ "node": ">=20.0.0" } }, - "packages/react-say/node_modules/@happy-dom/global-registrator/node_modules/@types/node": { + "packages/test-harness/node_modules/@types/node": { "version": "20.19.27", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, - "packages/react-say/node_modules/@happy-dom/global-registrator/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "packages/react-say/node_modules/escape-string-regexp": { + "packages/test-harness/node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7660,11 +7553,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/react-say/node_modules/happy-dom": { + "packages/test-harness/node_modules/happy-dom": { "version": "20.0.11", "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.0.11.tgz", "integrity": "sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "^20.0.0", @@ -7675,21 +7567,10 @@ "node": ">=20.0.0" } }, - "packages/react-say/node_modules/happy-dom/node_modules/@types/node": { - "version": "20.19.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", - "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "packages/react-say/node_modules/happy-dom/node_modules/undici-types": { + "packages/test-harness/node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" } } diff --git a/package.json b/package.json index 485eec3..f45587c 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,10 @@ "version": "2.2.2-0", "private": true, "workspaces": [ - "packages/test-harness", "packages/react-say", "packages/integration-test", - "packages/pages" + "packages/pages", + "packages/test-harness" ], "scripts": { "build": "npm run build --if-present --workspaces", @@ -26,6 +26,11 @@ "switch:react-18": "SWITCH_NAME=react-18 npm run switch:_", "test": "npm run test --if-present --workspaces" }, + "author": "William Wong (https://github.com/compulim)", + "license": "MIT", + "pinDependencies": {}, + "localPeerDependencies": {}, + "peerDependencies": {}, "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.50.0", "@typescript-eslint/parser": "^8.50.0", @@ -38,11 +43,5 @@ "eslint-plugin-react-hooks": "^7.0.1", "prettier": "^3.7.4" }, - "author": "William Wong (https://github.com/compulim)", - "license": "MIT", - "dependencies": {}, - "exports": {}, - "localPeerDependencies": {}, - "pinDependencies": {}, - "peerDependencies": {} + "dependencies": {} } diff --git a/packages/integration-test/package.json b/packages/integration-test/package.json index 31564ff..b4bcf78 100644 --- a/packages/integration-test/package.json +++ b/packages/integration-test/package.json @@ -1,8 +1,14 @@ { - "name": "react-say-integration-test", + "name": "@compulim/integration-test", "version": "0.0.0-0", "description": "", "private": true, + "files": [ + "**/*.cjs", + "**/*.js", + "**/*.mjs", + "webDriver/**/*" + ], "scripts": { "build": "npm run build:copy && npm run build:custom --if-present", "build:copy": "mkdir -p ./webDriver/static/; SRC=$(node --eval=\"console.log(require('path').resolve(require('resolve-cwd')('react-say'), '../../dist'))\"); DEST=$(realpath ./webDriver/static); rm -rf $DEST; mkdir -p $DEST/js/; cp $SRC/** $DEST/js/", @@ -27,51 +33,6 @@ "@compulim/test-harness": "0.0.0-0", "react-say": "0.0.0-0" }, - "pinDependencies": { - "react": [ - "18" - ], - "@types/react": [ - "18" - ], - "@types/react-dom": [ - "18" - ], - "@types/react-test-renderer": [ - "18" - ], - "react-dom": [ - "18" - ], - "react-test-renderer": [ - "18" - ] - }, - "devDependencies": { - "@happy-dom/global-registrator": "^18.0.1", - "@testduet/given-when-then": "^0.1.0-main.334801c", - "@testduet/wait-for": "^0.1.1", - "@testing-library/react": "^16.3.0", - "@tsconfig/strictest": "^2.0.5", - "@types/react-dom": "^18.3.7", - "expect": "^30.0.4", - "happy-dom": "^18.0.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "resolve-cwd": "^3.0.0", - "typescript": "^5.8.3" - }, - "dependencies": { - "react-say": "^0.0.0-0" - }, - "files": [ - "**/*.cjs", - "**/*.js", - "**/*.mjs", - "webDriver/**/*" - ], - "exports": {}, - "peerDependencies": {}, "switch:react-16": { "dependencies": { "react": "16.8.0", @@ -107,5 +68,42 @@ "@types/react-dom": "^18", "@types/react-test-renderer": "^18" } + }, + "pinDependencies": { + "react": [ + "18" + ], + "@types/react": [ + "18" + ], + "@types/react-dom": [ + "18" + ], + "@types/react-test-renderer": [ + "18" + ], + "react-dom": [ + "18" + ], + "react-test-renderer": [ + "18" + ] + }, + "devDependencies": { + "@testduet/given-when-then": "^0.1.0-main.334801c", + "@testing-library/react": "^16.3.0", + "@tsconfig/strictest": "^2.0.5", + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.7", + "@types/react-test-renderer": "^18.3.1", + "expect": "^30.0.4", + "resolve-cwd": "^3.0.0", + "typescript": "^5.8.3" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-say": "0.0.0-0", + "react-test-renderer": "^18.3.1" } } diff --git a/packages/pages/package.json b/packages/pages/package.json index 6b34cef..e443b9b 100644 --- a/packages/pages/package.json +++ b/packages/pages/package.json @@ -1,8 +1,9 @@ { - "name": "react-say-pages", + "name": "@compulim/pages", "version": "0.0.0-0", "description": "", "private": true, + "exports": {}, "scripts": { "build": "esbuild --bundle --entry-names=[name]/[ext]/main --jsx=transform --minify --outdir=./public/static/ --sourcemap $(find ./src -mindepth 1 -maxdepth 1 -type d -printf '%f=./src/%f/index.tsx ')", "bump": "npm run bump:prod && npm run bump:dev", @@ -67,6 +68,7 @@ "18" ] }, + "peerDependencies": {}, "devDependencies": { "@tsconfig/recommended": "^1.0.13", "@tsconfig/strictest": "^2.0.8", @@ -83,7 +85,5 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-say": "^0.0.0-0" - }, - "exports": {}, - "peerDependencies": {} + } } diff --git a/packages/react-say/.eslintrc.yml b/packages/react-say/.eslintrc.yml index 54b2959..b681e20 100644 --- a/packages/react-say/.eslintrc.yml +++ b/packages/react-say/.eslintrc.yml @@ -1,2 +1,5 @@ +env: + browser: true + es2024: true extends: - ./.eslintrc.custom.yml diff --git a/packages/react-say/__tests__/__setup__/typingTestTransformer.js b/packages/react-say/__tests__/__setup__/typingTestTransformer.js deleted file mode 100644 index 5462b3e..0000000 --- a/packages/react-say/__tests__/__setup__/typingTestTransformer.js +++ /dev/null @@ -1,95 +0,0 @@ -// Notes: to test changes in this file, run "jest" with "--no-cache" argument. - -const run = ({ filename }) => { - const escapeStringRegexp = require('escape-string-regexp'); - const fs = require('fs/promises'); - const { extname } = require('path'); - const typeScript = require('typescript'); - - const TS_EXPECT_ERROR = /(\/\/\s+)(@ts-expect-error)[\s+(.*)]/gu; - /** @type {import('typescript').CompilerOptions} */ - const TSCONFIG = { - allowImportingTsExtensions: true, - allowSyntheticDefaultImports: true, - jsx: typeScript.JsxEmit.React, - lib: ['lib.dom.d.ts', 'lib.esnext.d.ts'], - module: typeScript.ModuleKind.ESNext, - moduleResolution: typeScript.ModuleResolutionKind.Bundler, - noEmit: true, - skipLibCheck: true, - strict: true, - target: typeScript.ScriptTarget.ESNext, - types: [] - }; - - async function compile(filename) { - const program = typeScript.createProgram([filename], TSCONFIG); - - const emitResult = program.emit(); - const allDiagnostics = typeScript.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); - - allDiagnostics.forEach(({ file, messageText, start }) => { - if (file && start) { - const { line, character } = file.getLineAndCharacterOfPosition(start); - const message = typeScript.flattenDiagnosticMessageText(messageText, '\n'); - - throw new Error(`Failed to compile ${file.fileName} (${line + 1},${character + 1}): ${message}`); - } else { - throw new Error(typeScript.flattenDiagnosticMessageText(messageText, '\n')); - } - }); - } - - async function checkExpectError(filename) { - const sourceText = await fs.readFile(filename, 'utf-8'); - const sourceTextWithoutExpectError = sourceText.replace(TS_EXPECT_ERROR, '$1'); - - const extension = extname(filename); - const tempFilename = filename.substring(0, filename.length - extension.length) + `.tmp${extension}`; - - await fs.writeFile(tempFilename, sourceTextWithoutExpectError); - - try { - const program = typeScript.createProgram([tempFilename], TSCONFIG); - - const emitResult = program.emit(); - const allDiagnostics = typeScript.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); - - allDiagnostics.forEach(({ file, messageText, start }) => { - if (file && start) { - const { line } = file.getLineAndCharacterOfPosition(start); - const message = typeScript.flattenDiagnosticMessageText(messageText, '\n'); - - const expectedErrorLine = file.getFullText().split('\n')[line - 1]; - const expectedError = expectedErrorLine?.replace(/\s*\/\/\s+/u, '').trim(); - let expectedErrors = [expectedError]; - - try { - const parsed = JSON.parse(expectedError); - - if (Array.isArray(expectedErrors) && expectedErrors.every(value => typeof value === 'string')) { - expectedErrors = parsed; - } - } catch {} - - expect(message).toEqual(expect.stringMatching(new RegExp(expectedErrors.map(escapeStringRegexp).join('|')))); - } else { - throw new Error(typeScript.flattenDiagnosticMessageText(messageText, '\n')); - } - }); - } finally { - fs.unlink(tempFilename); - } - } - - describe(filename, () => { - test('should succeed', () => compile(filename)); - test('should have @ts-expect-error describing compile errors correctly', () => checkExpectError(filename)); - }); -}; - -module.exports = { - process(_, filename) { - return { code: `(${run})(${JSON.stringify({ filename })})` }; - } -}; diff --git a/packages/react-say/__tests__/types/.gitignore b/packages/react-say/__tests__/types/.gitignore deleted file mode 100644 index 5ad8225..0000000 --- a/packages/react-say/__tests__/types/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.tmp.tsx diff --git a/packages/react-say/happy-dom-env.ts b/packages/react-say/happy-dom-env.ts deleted file mode 100644 index 53c8814..0000000 --- a/packages/react-say/happy-dom-env.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { GlobalRegistrator } from '@happy-dom/global-registrator'; - -GlobalRegistrator.register({ - height: 1080, - url: 'http://localhost:3000', - width: 1920 -}); diff --git a/packages/react-say/package.json b/packages/react-say/package.json index 713fc12..8051281 100644 --- a/packages/react-say/package.json +++ b/packages/react-say/package.json @@ -60,6 +60,36 @@ "url": "https://github.com/compulim/react-say/issues" }, "homepage": "https://github.com/compulim/react-say#readme", + "switch:react-16": { + "devDependencies": { + "@types/react": "^16", + "@types/react-dom": "^16", + "@types/react-test-renderer": "^16", + "react": "16.8.0", + "react-dom": "16.8.0", + "react-test-renderer": "16.8.0" + } + }, + "switch:react-17": { + "devDependencies": { + "@types/react": "^17", + "@types/react-dom": "^17", + "@types/react-test-renderer": "^17", + "react": "17.0.0", + "react-dom": "17.0.0", + "react-test-renderer": "17.0.0" + } + }, + "switch:react-18": { + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/react-test-renderer": "^18", + "react": "18.0.0", + "react-dom": "18.0.0", + "react-test-renderer": "18.0.0" + } + }, "pinDependencies": { "@types/react": [ "18" @@ -83,17 +113,15 @@ "peerDependencies": { "react": ">=16.8.0" }, + "localPeerDependencies": { + "@compulim/test-harness": "0.0.0-0" + }, "devDependencies": { - "@happy-dom/global-registrator": "^20.0.11", "@testduet/given-when-then": "^0.1.0", "@tsconfig/recommended": "^1.0.13", "@tsconfig/strictest": "^2.0.8", - "@types/node": "^25.0.3", "@types/react": "^18.3.27", - "esbuild": "^0.27.2", - "escape-string-regexp": "^5.0.0", "expect": "^30.2.0", - "happy-dom": "^20.0.11", "has-resolved": "^1.1.0", "prettier": "^3.7.4", "publint": "^0.3.16", @@ -103,38 +131,5 @@ }, "dependencies": { "prop-types": "^15.8.1" - }, - "localPeerDependencies": { - "@compulim/test-harness": "0.0.0-0" - }, - "switch:react-16": { - "devDependencies": { - "@types/react": "^16", - "@types/react-dom": "^16", - "@types/react-test-renderer": "^16", - "react": "16.8.0", - "react-dom": "16.8.0", - "react-test-renderer": "16.8.0" - } - }, - "switch:react-17": { - "devDependencies": { - "@types/react": "^17", - "@types/react-dom": "^17", - "@types/react-test-renderer": "^17", - "react": "17.0.0", - "react-dom": "17.0.0", - "react-test-renderer": "17.0.0" - } - }, - "switch:react-18": { - "devDependencies": { - "@types/react": "^18", - "@types/react-dom": "^18", - "@types/react-test-renderer": "^18", - "react": "18.0.0", - "react-dom": "18.0.0", - "react-test-renderer": "18.0.0" - } } } diff --git a/packages/react-say/src/Composer.jsx b/packages/react-say/src/Composer.jsx index 9fc4495..af0038e 100644 --- a/packages/react-say/src/Composer.jsx +++ b/packages/react-say/src/Composer.jsx @@ -12,11 +12,15 @@ const Composer = props => { // If we have the parent context, we will use that synthesize() function and its internal queue. const { ponyfill: parentPonyfill, synthesize: parentSynthesize } = useContext(Context) || {}; - const ponyfill = ponyfillFromProps || - parentPonyfill || { - speechSynthesis: window.speechSynthesis || window.webkitSpeechSynthesis, - SpeechSynthesisUtterance: window.SpeechSynthesisUtterance || window.webkitSpeechSynthesisUtterance - }; + const ponyfill = useMemo( + () => + ponyfillFromProps || + parentPonyfill || { + speechSynthesis: window.speechSynthesis || window.webkitSpeechSynthesis, + SpeechSynthesisUtterance: window.SpeechSynthesisUtterance || window.webkitSpeechSynthesisUtterance + }, + [parentPonyfill, ponyfillFromProps] + ); // If the parent context changed and no longer has a synthesize() function, we will create the queue. // This is very unlikely to happen. diff --git a/packages/react-say/src/Say.jsx b/packages/react-say/src/Say.jsx index 3fef614..417e30a 100644 --- a/packages/react-say/src/Say.jsx +++ b/packages/react-say/src/Say.jsx @@ -4,6 +4,8 @@ import React, { useContext, useMemo } from 'react'; import Composer from './Composer.jsx'; import Context from './Context.mjs'; import createNativeUtterance from './createNativeUtterance.mjs'; +// TODO: Fix this cyclic in `migrateDeprecatedProps.mjs`. +// eslint-disable-next-line import/no-cycle import migrateDeprecatedProps from './migrateDeprecatedProps.mjs'; import SayUtterance from './SayUtterance.jsx'; diff --git a/packages/react-say/src/SayButton.jsx b/packages/react-say/src/SayButton.jsx index 0818e48..08b21e1 100644 --- a/packages/react-say/src/SayButton.jsx +++ b/packages/react-say/src/SayButton.jsx @@ -1,6 +1,8 @@ import PropTypes from 'prop-types'; import React, { useCallback, useState } from 'react'; +// TODO: Fix this cyclic in `migrateDeprecatedProps.mjs`. +// eslint-disable-next-line import/no-cycle import migrateDeprecatedProps from './migrateDeprecatedProps.mjs'; import Say from './Say.jsx'; @@ -9,7 +11,7 @@ const SayButton = props => { migrateDeprecatedProps(props, SayButton); const [busy, setBusy] = useState(false); - const handleClick = useCallback(() => setBusy(true)); + const handleClick = useCallback(() => setBusy(true), [setBusy]); const sayProps = { lang, onBoundary, diff --git a/packages/react-say/src/SayUtterance.jsx b/packages/react-say/src/SayUtterance.jsx index 520439e..018e477 100644 --- a/packages/react-say/src/SayUtterance.jsx +++ b/packages/react-say/src/SayUtterance.jsx @@ -39,6 +39,9 @@ const SayUtterance = props => { cancelled = true; cancel(); }; + + // TODO: Fix this. + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return false; diff --git a/packages/react-say/src/tsconfig.custom.json b/packages/react-say/src/tsconfig.custom.json index 7a3b017..a994aa5 100644 --- a/packages/react-say/src/tsconfig.custom.json +++ b/packages/react-say/src/tsconfig.custom.json @@ -1,3 +1,10 @@ { - "extends": "@tsconfig/strictest/tsconfig.json" + "compilerOptions": { + "allowJs": true, + "types": ["node"] + }, + // "extends": "@tsconfig/strictest/tsconfig.json" + + // Commented out @tsconfig/strictest until ported to TypeScript. + "extends": "@tsconfig/recommended/tsconfig.json" } diff --git a/packages/react-say/tsup.config.override.ts b/packages/react-say/tsup.config.override.ts index e258365..2204478 100644 --- a/packages/react-say/tsup.config.override.ts +++ b/packages/react-say/tsup.config.override.ts @@ -2,6 +2,9 @@ import { type Options } from 'tsup'; export default function override(options: Options): Options { return { - ...options + ...options, + entry: { + 'react-say': './src/index.mjs' + } }; } diff --git a/packages/test-harness/package.json b/packages/test-harness/package.json index aaffdc5..9054aa8 100644 --- a/packages/test-harness/package.json +++ b/packages/test-harness/package.json @@ -63,12 +63,8 @@ "precommit:typescript": "tsc --noEmit --project ./src/tsconfig.precommit.json", "switch": "cat package.json | jq --arg SWITCH_NAME $SWITCH_NAME -r '(.[\"switch:\" + $SWITCH_NAME] // {}) as $TEMPLATE | .devDependencies += ($TEMPLATE.devDependencies // {}) | .dependencies += ($TEMPLATE.dependencies // {})' | tee ./package.json.tmp && mv ./package.json.tmp ./package.json" }, - "peerDependencies": { - "typescript": ">=5", - "react": ">=16.8.0", - "react-dom": ">=16.8.0", - "react-test-renderer": ">=16.8.0" - }, + "author": "William Wong (https://github.com/compulim)", + "license": "MIT", "switch:react-16": { "dependencies": { "@testing-library/react": "^12", @@ -127,5 +123,30 @@ "react-test-renderer": [ "18" ] + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "react-test-renderer": ">=16.8.0", + "typescript": "^5.9.3" + }, + "devDependencies": { + "@tsconfig/strictest": "^2.0.8", + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.7", + "@types/react-test-renderer": "^18.3.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-test-renderer": "^18.3.1", + "resolve-cwd": "^3.0.0" + }, + "main": "index.js", + "dependencies": { + "@happy-dom/global-registrator": "^20.0.11", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.1", + "esbuild": "^0.27.2", + "escape-string-regexp": "^5.0.0", + "expect": "^30.2.0" } } From 00d0e4a569c67affe0c25ef275eef5773fe65ad2 Mon Sep 17 00:00:00 2001 From: William Wong Date: Fri, 2 Jan 2026 01:50:16 +0000 Subject: [PATCH 3/6] Rename to *.test.* --- .../src/{QueuedUtterance.spec.mjs => QueuedUtterance.test.mjs} | 2 -- .../{createSynthesize.spec.mjs => createSynthesize.test.mjs} | 0 2 files changed, 2 deletions(-) rename packages/react-say/src/{QueuedUtterance.spec.mjs => QueuedUtterance.test.mjs} (99%) rename packages/react-say/src/{createSynthesize.spec.mjs => createSynthesize.test.mjs} (100%) diff --git a/packages/react-say/src/QueuedUtterance.spec.mjs b/packages/react-say/src/QueuedUtterance.test.mjs similarity index 99% rename from packages/react-say/src/QueuedUtterance.spec.mjs rename to packages/react-say/src/QueuedUtterance.test.mjs index a05ebce..d5a270a 100644 --- a/packages/react-say/src/QueuedUtterance.spec.mjs +++ b/packages/react-say/src/QueuedUtterance.test.mjs @@ -1,5 +1,3 @@ -/** @jest-environment jsdom */ - import { expect } from 'expect'; import { beforeEach, mock, test } from 'node:test'; import createErrorEvent from './createErrorEvent.mjs'; diff --git a/packages/react-say/src/createSynthesize.spec.mjs b/packages/react-say/src/createSynthesize.test.mjs similarity index 100% rename from packages/react-say/src/createSynthesize.spec.mjs rename to packages/react-say/src/createSynthesize.test.mjs From 94c09930771e875021a6856b7ce039365c61bf40 Mon Sep 17 00:00:00 2001 From: William Wong Date: Fri, 2 Jan 2026 01:51:57 +0000 Subject: [PATCH 4/6] Target ESNext --- .eslintrc.yml | 2 ++ packages/react-say/.eslintrc.yml | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.yml b/.eslintrc.yml index ec49405..ed5e3e7 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,3 +1,5 @@ +env: + es2024: true # Target ESNext extends: - eslint:recommended overrides: diff --git a/packages/react-say/.eslintrc.yml b/packages/react-say/.eslintrc.yml index b681e20..a8f533d 100644 --- a/packages/react-say/.eslintrc.yml +++ b/packages/react-say/.eslintrc.yml @@ -1,5 +1,4 @@ env: browser: true - es2024: true extends: - ./.eslintrc.custom.yml From 59917b3a81c01731cb4e0e08201aeb65c4bd29c7 Mon Sep 17 00:00:00 2001 From: William Wong Date: Fri, 2 Jan 2026 01:53:51 +0000 Subject: [PATCH 5/6] Clean up --- packages/integration-test/babel.config.json | 17 ----------------- packages/integration-test/happy-dom-env.ts | 7 ------- 2 files changed, 24 deletions(-) delete mode 100644 packages/integration-test/babel.config.json delete mode 100644 packages/integration-test/happy-dom-env.ts diff --git a/packages/integration-test/babel.config.json b/packages/integration-test/babel.config.json deleted file mode 100644 index 9043e47..0000000 --- a/packages/integration-test/babel.config.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-react", - { - "runtime": "classic" - } - ], - [ - "@babel/preset-env", - { - "modules": "commonjs", - "targets": "defaults" - } - ] - ] -} diff --git a/packages/integration-test/happy-dom-env.ts b/packages/integration-test/happy-dom-env.ts deleted file mode 100644 index 53c8814..0000000 --- a/packages/integration-test/happy-dom-env.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { GlobalRegistrator } from '@happy-dom/global-registrator'; - -GlobalRegistrator.register({ - height: 1080, - url: 'http://localhost:3000', - width: 1920 -}); From ecd93e5905078ae6b92a51557ec14643aea53c84 Mon Sep 17 00:00:00 2001 From: William Wong Date: Fri, 2 Jan 2026 02:17:05 +0000 Subject: [PATCH 6/6] Add entry --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a15075c..d5e6e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +| Icon | Description | +| ---- | ------------------------------------------------- | +| 💢 | Breaking changes | +| 👷🏻 | Development experience (non-production impacting) | + ## [Unreleased] +### Changed + +- 👷🏻 Test framework moved to Node.js test runner, by [@compulim](https://github.com/compulim) in PR [#62](https://github.com/compulim/react-say/pull/62) + ## [2.2.1] - 2025-12-22 ### Changed