diff --git a/.circleci/config.yml b/.circleci/config.yml index adb1bf6c916b..e9311749504a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,8 +5,8 @@ parameters: description: Whether to force browserstack usage. We have limited resources on browserstack so the pipeline might decide to skip browserstack if this parameter isn't set to true. type: boolean default: false - react-dist-tag: - description: The dist-tag of react to be used + react-version: + description: The version of react to be used type: string default: stable workflow: @@ -20,10 +20,10 @@ parameters: default-job: &default-job parameters: - react-dist-tag: - description: The dist-tag of react to be used + react-version: + description: The version of react to be used type: string - default: << pipeline.parameters.react-dist-tag >> + default: << pipeline.parameters.react-version >> e2e-base-url: description: The base url for running end-to-end test type: string @@ -33,7 +33,7 @@ default-job: &default-job PLAYWRIGHT_BROWSERS_PATH: /tmp/pw-browsers # expose it globally otherwise we have to thread it from each job to the install command BROWSERSTACK_FORCE: << pipeline.parameters.browserstack-force >> - REACT_DIST_TAG: << parameters.react-dist-tag >> + REACT_VERSION: << parameters.react-version >> working_directory: /tmp/mui docker: - image: cimg/node:18.20 @@ -59,6 +59,13 @@ commands: description: 'Set to true if you intend to any browser (for example with playwright).' steps: + - run: + name: Resolve React version + command: | + node scripts/useReactVersion.mjs + # log a patch for maintainers who want to check out this change + git --no-pager diff HEAD + - when: condition: << parameters.browsers >> steps: @@ -90,7 +97,17 @@ commands: pnpm --version - run: name: Install js dependencies - command: pnpm install + command: | + echo "React version $REACT_VERSION" + if [ $REACT_VERSION == "stable" ]; + then + echo "pnpm install" + pnpm install + else + echo "pnpm install --no-frozen-lockfile" + pnpm install --no-frozen-lockfile + fi + - when: condition: << parameters.browsers >> steps: @@ -146,7 +163,7 @@ jobs: command: | curl -Os https://uploader.codecov.io/latest/linux/codecov chmod +x codecov - ./codecov -t ${CODECOV_TOKEN} -Z -F "$REACT_DIST_TAG-jsdom" + ./codecov -t ${CODECOV_TOKEN} -Z -F "$REACT_VERSION-jsdom" test_lint: <<: *default-job steps: @@ -330,3 +347,31 @@ workflows: - test_e2e_website: requires: - checkout + + react-next: + when: + equal: [react-next, << pipeline.parameters.workflow >>] + # triggers: + # - schedule: + # cron: '0 0 * * *' + # filters: + # branches: + # only: + # - master + jobs: + - test_unit: + <<: *default-context + react-version: next + name: test_unit-react@next + - test_browser: + <<: *default-context + react-version: next + name: test_browser-react@next + - test_regressions: + <<: *default-context + react-version: next + name: test_regressions-react@next + - test_e2e: + <<: *default-context + react-version: next + name: test_e2e-react@next diff --git a/package.json b/package.json index 8c37f4d776b6..4da47547fef5 100644 --- a/package.json +++ b/package.json @@ -95,8 +95,8 @@ "@octokit/plugin-retry": "^7.1.1", "@octokit/rest": "^21.0.0", "@playwright/test": "^1.44.1", - "@types/babel__traverse": "^7.20.6", "@types/babel__core": "^7.20.5", + "@types/babel__traverse": "^7.20.6", "@types/chai": "^4.3.16", "@types/chai-dom": "^1.11.3", "@types/fs-extra": "^11.0.4", diff --git a/scripts/useReactVersion.mjs b/scripts/useReactVersion.mjs new file mode 100644 index 000000000000..1a36599fce68 --- /dev/null +++ b/scripts/useReactVersion.mjs @@ -0,0 +1,109 @@ +/* eslint-disable no-console */ +/** + * Given the dist tag fetch the corresponding + * version and make sure this version is used throughout the repository. + * + * If you work on this file: + * WARNING: This script can only use built-in modules since it has to run before + * `pnpm install` + */ +import childProcess from 'child_process'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { promisify } from 'util'; +import { getWorkspaceRoot } from './utils.mjs'; + +// TODO: reuse the `useReactVersion.mjs` from the monorepo + +const exec = promisify(childProcess.exec); + +// packages published from the react monorepo using the same version +const reactPackageNames = ['react', 'react-dom', 'react-is', 'react-test-renderer', 'scheduler']; +const devDependenciesPackageNames = ['@testing-library/react']; + +// if we need to support more versions we will need to add new mapping here +const additionalVersionsMappings = { + 17: { + '@testing-library/react': '^12.1.0', + }, + 19: {}, +}; + +async function main(version) { + if (typeof version !== 'string') { + throw new TypeError(`expected version: string but got '${version}'`); + } + + if (version === 'stable') { + console.log('Nothing to do with stable'); + return; + } + + const packageJsonPath = path.resolve(getWorkspaceRoot(), 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, { encoding: 'utf8' })); + + // the version is something in format: "17.0.0" + let majorVersion = null; + + if (version.startsWith('^') || version.startsWith('~') || !Number.isNaN(version.charAt(0))) { + majorVersion = version.replace('^', '').replace('~', '').split('.')[0]; + } + + await Promise.all( + reactPackageNames.map(async (reactPackageName) => { + const { stdout: versions } = await exec(`npm dist-tag ls ${reactPackageName} ${version}`); + const tagMapping = versions.split('\n').find((mapping) => { + return mapping.startsWith(`${version}: `); + }); + + let packageVersion = null; + + if (tagMapping === undefined) { + // Some specific version is being requested + if (majorVersion) { + packageVersion = version; + if (reactPackageName === 'scheduler') { + // get the scheduler version from the react-dom's dependencies entry + const { stdout: reactDOMDependenciesString } = await exec( + `npm view --json react-dom@${version} dependencies`, + ); + packageVersion = JSON.parse(reactDOMDependenciesString).scheduler; + } + } else { + throw new Error(`Could not find '${version}' in "${versions}"`); + } + } else { + packageVersion = tagMapping.replace(`${version}: `, ''); + } + + packageJson.resolutions[reactPackageName] = packageVersion; + }), + ); + + // At this moment all dist tags reference React 18 version, so we don't need + // to update these dependencies unless an older version is used, or when the + // next/experimental dist tag reference to a future version of React + // packageJson.devDependencies['@testing-library/react'] = 'alpha'; + + if (majorVersion && additionalVersionsMappings[majorVersion]) { + devDependenciesPackageNames.forEach((packageName) => { + if (!additionalVersionsMappings[majorVersion][packageName]) { + throw new Error( + `Version ${majorVersion} does not have version defined for the ${packageName}`, + ); + } + packageJson.devDependencies[packageName] = + additionalVersionsMappings[majorVersion][packageName]; + }); + } + + // add newline for clean diff + fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}${os.EOL}`); +} + +const [version = process.env.REACT_VERSION] = process.argv.slice(2); +main(version).catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000000..e2bb080720fb --- /dev/null +++ b/test/README.md @@ -0,0 +1,46 @@ +# Testing + +## Testing multiple versions of React + +You can check integration of different versions of React (for example different [release channels](https://react.dev/community/versioning-policy) or PRs to React) by running the following commands: + +1. `node scripts/useReactVersion.mjs `. + + Possible values for `version`: + + - default: `stable` (minimum supported React version) + - a tag on npm, for example `next`, `experimental` or `latest` + - an older version, for example `^17.0.0` + +2. `pnpm install` + +### CI + +#### `next` version + +For `react@next` specifically, there's a `react-next` workflow in our CircleCI pipeline that you can trigger in CircleCI on the PR you want to test: + +1. Go to https://app.circleci.com/pipelines/github/mui/mui-x?branch=pull/PR_NUMBER and replace `PR_NUMBER` with the PR number you want to test. +2. Click `Trigger Pipeline` button. +3. Expand `Add parameters (optional)` and add the following parameter: + + | Parameter type | Name | Value | + | :------------- | :--------- | :----------- | + | `string` | `workflow` | `react-next` | + +4. Click `Trigger Pipeline` button. + +#### Other versions + +You can pass the same `version` to our CircleCI pipeline as well: + +With the following API request we're triggering a run of the default workflow in +PR #24289 for `react@next` + +```bash +curl --request POST \ + --url https://circleci.com/api/v2/project/gh/mui/mui-x/pipeline \ + --header 'content-type: application/json' \ + --header 'Circle-Token: $CIRCLE_TOKEN' \ + --data-raw '{"branch":"pull/24289/head","parameters":{"react-version":"next"}}' +```