diff --git a/unit-test/.gitignore b/unit-test/.gitignore new file mode 100644 index 00000000000..4d29575de80 --- /dev/null +++ b/unit-test/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/unit-test/README.md b/unit-test/README.md new file mode 100644 index 00000000000..5717b5254bd --- /dev/null +++ b/unit-test/README.md @@ -0,0 +1,21 @@ +# Rsbuild / Create React App Example + +This example demos a basic host application loading remote component. + +- `host` is the host application (unit_test-based). +- `remote` standalone application (unit_test-based) which exposes `Button` component. + +# Running Demo + +Run `pnpm run start`. This will build and serve both `host` and `remote` on ports 3001 and 3002 respectively. + +- [localhost:3001](http://localhost:3000/) (HOST) +- [localhost:3002](http://localhost:3002/) (STANDALONE REMOTE) + +# Running Cypress E2E Tests + +To run tests in interactive mode, run `npm run cypress:debug` from the root directory of the project. It will open Cypress Test Runner and allow to run tests in interactive mode. [More info about "How to run tests"](../../cypress/README.md#how-to-run-tests) + +To build app and run test in headless mode, run `yarn e2e:ci`. It will build app and run tests for this workspace in headless mode. If tets failed cypress will create `cypress` directory in sample root folder with screenshots and videos. + +["Best Practices, Rules amd more interesting information here](../../cypress/README.md) diff --git a/unit-test/cypress.env.json b/unit-test/cypress.env.json new file mode 100644 index 00000000000..c65e6cd032e --- /dev/null +++ b/unit-test/cypress.env.json @@ -0,0 +1,4 @@ +{ + "allure": true, + "allureResultsPath": "../cypress-e2e/results/allure-results" +} diff --git a/unit-test/e2e/checkCraApps.cy.ts b/unit-test/e2e/checkCraApps.cy.ts new file mode 100644 index 00000000000..f27caaa1375 --- /dev/null +++ b/unit-test/e2e/checkCraApps.cy.ts @@ -0,0 +1,45 @@ +import { baseSelectors } from './../../cypress-e2e/common/selectors'; +import { BaseMethods } from '../../cypress-e2e/common/base'; +import { Constants } from '../../cypress-e2e/fixtures/constants'; + +const basePage: BaseMethods = new BaseMethods(); + +const appsData = [ + { + appNameText: Constants.commonConstantsData.basicComponents.host, + host: 3000, + }, + { + appNameText: Constants.commonConstantsData.basicComponents.remote, + host: 3002, + }, +]; + +appsData.forEach((property: { appNameText: string; host: number }) => { + const appName = property.host === 3000 ? appsData[0].appNameText : appsData[1].appNameText; + + describe('CRA', () => { + context(`Check ${appName}`, () => { + beforeEach(() => { + basePage.openLocalhost({ + number: property.host, + }); + }); + + it(`Check ${appName} elements exist on the page`, () => { + basePage.checkElementWithTextPresence({ + selector: baseSelectors.tags.headers.h1, + text: Constants.commonConstantsData.basicComponents.basicHostRemote, + }); + basePage.checkElementWithTextPresence({ + selector: baseSelectors.tags.headers.h2, + text: property.appNameText, + }); + basePage.checkElementWithTextPresence({ + selector: baseSelectors.tags.coreElements.button, + text: Constants.elementsText.craApp.buttonText, + }); + }); + }); + }); +}); diff --git a/unit-test/host/.babelrc b/unit-test/host/.babelrc new file mode 100644 index 00000000000..76957d6a99e --- /dev/null +++ b/unit-test/host/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env", "@babel/preset-react"] + } \ No newline at end of file diff --git a/unit-test/host/.gitignore b/unit-test/host/.gitignore new file mode 100644 index 00000000000..4d29575de80 --- /dev/null +++ b/unit-test/host/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/unit-test/host/__tests__/app.test.js b/unit-test/host/__tests__/app.test.js new file mode 100644 index 00000000000..4e0579d3be1 --- /dev/null +++ b/unit-test/host/__tests__/app.test.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import App from '@src/App.js'; + +describe('App Component', () => { + beforeAll(async ()=>{ + await require('federation-test') + }); + test('renders the main heading', () => { + render(); + const mainHeading = screen.getByTestId('main-heading'); + expect(mainHeading).toBeInTheDocument(); + }); + + test('renders the subheading', () => { + render(); + const subHeading = screen.getByTestId('sub-heading'); + expect(subHeading).toBeInTheDocument(); + }); + + test('renders the RemoteButton with fallback', async () => { + render(); + const remoteButton = await screen.findByTestId('remote-button'); + expect(remoteButton).toBeInTheDocument(); + }); +}); diff --git a/unit-test/host/jest.config.js b/unit-test/host/jest.config.js new file mode 100644 index 00000000000..cf0087d2477 --- /dev/null +++ b/unit-test/host/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + moduleNameMapper: { + '^@src/(.*)$': '/src/$1' + }, + testEnvironment: 'jsdom', + transform: { + '^.+\\.jsx?$': 'babel-jest' + }, + setupFiles: ['/jest.setup.js'] + }; diff --git a/unit-test/host/jest.setup.js b/unit-test/host/jest.setup.js new file mode 100644 index 00000000000..dfbfb73bc06 --- /dev/null +++ b/unit-test/host/jest.setup.js @@ -0,0 +1,5 @@ +const { setupFederationTest } = require('../mf-test'); + +module.exports = async () => { + await setupFederationTest(require('./modulefederation.config')); +}; diff --git a/unit-test/host/modulefederation.config.js b/unit-test/host/modulefederation.config.js new file mode 100644 index 00000000000..ffe4a2b8fd0 --- /dev/null +++ b/unit-test/host/modulefederation.config.js @@ -0,0 +1,21 @@ +const { dependencies } = require('./package.json'); + +module.exports = { + name: 'host', + library: {type: 'commonjs-module', name: 'host'}, + remoteType: 'script', + remotes: { + remote: 'remote@http://localhost:3002/remoteEntry.js', + }, + shared: { + ...dependencies, + react: { + singleton: true, + requiredVersion: dependencies['react'], + }, + 'react-dom': { + singleton: true, + requiredVersion: dependencies['react-dom'], + }, + }, +}; diff --git a/unit-test/host/package.json b/unit-test/host/package.json new file mode 100644 index 00000000000..a073f3353e2 --- /dev/null +++ b/unit-test/host/package.json @@ -0,0 +1,44 @@ +{ + "name": "unit_test_host", + "version": "0.0.0", + "dependencies": { + "deasync": "^0.1.29", + "fibers": "^5.0.3", + "future": "^2.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "sync-promise": "^1.1.0" + }, + "scripts": { + "start": "rsbuild dev", + "build": "rsbuild build", + "preview": "rsbuild preview", + "test": "jest" + }, + "eslintConfig": {}, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@babel/preset-env": "^7.24.5", + "@babel/preset-react": "^7.24.1", + "@module-federation/enhanced": "^0.1.13", + "@module-federation/runtime": "^0.1.13", + "@rsbuild/core": "0.6.15", + "@rsbuild/plugin-react": "0.6.15", + "@rspack/core": "0.6.5", + "babel-jest": "^29.7.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "node-fetch": "2.6.9" + } +} diff --git a/unit-test/host/public/favicon.ico b/unit-test/host/public/favicon.ico new file mode 100644 index 00000000000..a11777cc471 Binary files /dev/null and b/unit-test/host/public/favicon.ico differ diff --git a/unit-test/host/public/index.html b/unit-test/host/public/index.html new file mode 100644 index 00000000000..17336bb272b --- /dev/null +++ b/unit-test/host/public/index.html @@ -0,0 +1,13 @@ + + + + + + + Host + + + +
+ + diff --git a/unit-test/host/rsbuild.config.ts b/unit-test/host/rsbuild.config.ts new file mode 100644 index 00000000000..0e858dade9e --- /dev/null +++ b/unit-test/host/rsbuild.config.ts @@ -0,0 +1,30 @@ +import { defineConfig } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; +import {ModuleFederationPlugin} from '@module-federation/enhanced/rspack' +//@ts-ignore +import mfConfig from './modulefederation.config'; +import rspack from '@rspack/core'; + +const rsbuildPlugin = () => ({ + name: 'example', + setup(api) { + api.onAfterBuild(() => console.log('done')); + }, +}); + +export default defineConfig({ + server: { + port: 3000, + }, + output: { + targets: ['node'], + }, + plugins: [pluginReact()], + tools: { + rspack: (config, { appendPlugins }) => { + appendPlugins([ + new ModuleFederationPlugin(mfConfig), + ]); + }, + }, +}); diff --git a/unit-test/host/scripts/build.js b/unit-test/host/scripts/build.js new file mode 100644 index 00000000000..60a17bd95d2 --- /dev/null +++ b/unit-test/host/scripts/build.js @@ -0,0 +1,3 @@ +process.env.NODE_ENV = 'production'; +require('./overrides/webpack-config'); +require('react-scripts/scripts/build'); diff --git a/unit-test/host/scripts/overrides/webpack-config.js b/unit-test/host/scripts/overrides/webpack-config.js new file mode 100644 index 00000000000..c7f55b49aca --- /dev/null +++ b/unit-test/host/scripts/overrides/webpack-config.js @@ -0,0 +1,16 @@ +const { ModuleFederationPlugin } = require('webpack').container; + +const webpackConfigPath = 'react-scripts/config/webpack.config'; +const webpackConfig = require(webpackConfigPath); + +const override = config => { + config.plugins.push(new ModuleFederationPlugin(require('../../modulefederation.config.js'))); + + config.output.publicPath = 'auto'; + + return config; +}; + +require.cache[require.resolve(webpackConfigPath)].exports = env => override(webpackConfig(env)); + +module.exports = require(webpackConfigPath); diff --git a/unit-test/host/scripts/start.js b/unit-test/host/scripts/start.js new file mode 100644 index 00000000000..17742ef30a2 --- /dev/null +++ b/unit-test/host/scripts/start.js @@ -0,0 +1,3 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; +require('./overrides/webpack-config'); +require('react-scripts/scripts/start'); diff --git a/unit-test/host/src/App.js b/unit-test/host/src/App.js new file mode 100644 index 00000000000..210d89f59a6 --- /dev/null +++ b/unit-test/host/src/App.js @@ -0,0 +1,15 @@ +import React from 'react'; +import RemoteButton from 'remote/Button' +// const RemoteButton = React.lazy(() => import('remote/Button')); + +const App = () => ( +
+

Basic Host-Remote

+

Host

+ Loading Button}> + + +
+); + +export default App; diff --git a/unit-test/host/src/bootstrap.js b/unit-test/host/src/bootstrap.js new file mode 100644 index 00000000000..f36b3686266 --- /dev/null +++ b/unit-test/host/src/bootstrap.js @@ -0,0 +1,11 @@ +// import React from 'react'; +// import ReactDOM from 'react-dom'; +// +// import App from './App'; +// +// ReactDOM.render( +// +// +// , +// // document.getElementById('root'), +// ); diff --git a/unit-test/host/src/index.js b/unit-test/host/src/index.js new file mode 100644 index 00000000000..b93c7a0268a --- /dev/null +++ b/unit-test/host/src/index.js @@ -0,0 +1 @@ +import('./bootstrap'); diff --git a/unit-test/host/thing.js b/unit-test/host/thing.js new file mode 100644 index 00000000000..5a21fb825d8 --- /dev/null +++ b/unit-test/host/thing.js @@ -0,0 +1 @@ +require('./jest.setup')() diff --git a/unit-test/mf-test/index.js b/unit-test/mf-test/index.js new file mode 100644 index 00000000000..4b009371c4b --- /dev/null +++ b/unit-test/mf-test/index.js @@ -0,0 +1,142 @@ +const fs = require('fs'); +const path = require('path'); +const fetch = require('node-fetch'); +const { init, loadRemote } = require('@module-federation/runtime'); + +const generateSharedConfig = (mfConfig) => { + const sharedConfig = {}; + for (const [packageName, packageConfig] of Object.entries(mfConfig.shared)) { + let version = false; + try { + version = require(path.join(packageName, 'package.json')).version; + } catch (e) { + // Handle error if needed + } + if (typeof packageConfig === 'string') { + sharedConfig[packageName] = { + version, + lib: () => require(packageName), + }; + } else { + sharedConfig[packageName] = { + version, + ...packageConfig, + lib: () => require(packageName), + }; + } + } + return sharedConfig; +}; + +const setupFederationTest = async (mfConfig) => { + const sharedConfig = generateSharedConfig(mfConfig); + let remotes = []; + + const harnessPath = path.resolve(__dirname, 'node_modules', 'federation-test'); + let harnessData = []; + + for (const [remote, entry] of Object.entries(mfConfig.remotes)) { + const [name, url] = entry.split('@'); + const manifest = url.replace('remoteEntry.js', 'mf-manifest.json'); + const response = await fetch(manifest); + const data = await response.json(); + + const parsedPath = new URL(url).origin; + const subPath = data.metaData.remoteEntry.path; + + const buildUrl = (parsedPath, subPath, file) => { + return subPath ? `${parsedPath}/${subPath}/${file}` : `${parsedPath}/${file}`; + }; + + remotes.push(buildUrl(parsedPath, subPath, data.metaData.remoteEntry.name)); + + const jsFiles = [ + ...data.shared.flatMap(shared => [...shared.assets.js.sync, ...shared.assets.js.async].map(file => buildUrl(parsedPath, subPath, file))), + ...data.exposes.flatMap(expose => [...expose.assets.js.sync, ...expose.assets.js.async].map(file => buildUrl(parsedPath, subPath, file))) + ]; + + const cssFiles = [ + ...data.shared.flatMap(shared => [...shared.assets.css.sync, ...shared.assets.css.async].map(file => buildUrl(parsedPath, subPath, file))), + ...data.exposes.flatMap(expose => [...expose.assets.css.sync, ...expose.assets.css.async].map(file => buildUrl(parsedPath, subPath, file))) + ]; + + remotes.push(...jsFiles, ...cssFiles); + + const fakePackagePath = path.resolve(__dirname, 'node_modules', data.id); + const fakePackageJsonPath = path.join(fakePackagePath, 'package.json'); + const fakePackageIndexPath = path.join(fakePackagePath, 'index.js'); + + if (!fs.existsSync(fakePackagePath)) { + fs.mkdirSync(fakePackagePath, { recursive: true }); + } + + const exportsContent = data.exposes.reduce((exportsObj, expose) => { + let exposeName = expose.name; + if (!exposeName.endsWith('.js')) { + exposeName += '.js'; + } + exportsObj[expose.path] = './virtual' + exposeName; + const resolvePath = path.join(fakePackagePath, './virtual' + exposeName); + + harnessData.push(resolvePath); + + fs.writeFileSync(resolvePath, ` + const container = require('./remoteEntry.js')[${JSON.stringify(data.id)}]; + const target = {}; + + let e; + const cx = container.get(${JSON.stringify(expose.path)}).then((m) => { + e = m(); + Object.assign(target, e); + }); + + + module.exports = new Proxy(target, { + get(target, prop) { + if(prop === 'setupTest') return cx; + if (!e) { + return cx; + } else if (prop in e) { + return e[prop]; + } else { + return e; + } + } + }); + `, 'utf-8'); + return exportsObj; + }, {}); + + const packageJsonContent = { + name: data.id, + version: '1.0.0', + exports: exportsContent + }; + const indexJsContent = ` + module.exports = () => 'Hello from fake package!'; + `; + + fs.writeFileSync(fakePackageJsonPath, JSON.stringify(packageJsonContent, null, 2)); + fs.writeFileSync(fakePackageIndexPath, indexJsContent); + + for (const fileUrl of remotes) { + const fileName = path.basename(fileUrl); + const filePath = path.join(fakePackagePath, fileName); + const fileResponse = await fetch(fileUrl); + const fileData = await fileResponse.buffer(); + fs.writeFileSync(filePath, fileData); + } + } + + if (!fs.existsSync(harnessPath)) { + fs.mkdirSync(harnessPath, { recursive: true }); + } + + fs.writeFileSync('node_modules/federation-test/index.js', `module.exports = Promise.all(${JSON.stringify(harnessData)}.map((p) => require(p).setupTest))`, 'utf-8'); + fs.writeFileSync('node_modules/federation-test/package.json', '{"name": "federation-test", "main": "./index.js"}', 'utf-8'); +}; + +module.exports = { + generateSharedConfig, + setupFederationTest +}; diff --git a/unit-test/package.json b/unit-test/package.json new file mode 100644 index 00000000000..66565dbf077 --- /dev/null +++ b/unit-test/package.json @@ -0,0 +1,22 @@ +{ + "name": "unit_test", + "version": "0.0.1", + "description": "Create React app Running with rsbuild", + "workspaces": [ + "./host", + "./remote" + ], + "scripts": { + "start": "pnpm --filter unit_test_* start", + "build": "pnpm --filter unit_test_* build", + "serve": "serve ./remote/dist/server -p 3001", + "preview": "pnpm run build && pnpm --filter unit_test_* preview", + "e2e:ci": "pnpm start & npx cypress run --config-file ../cypress-e2e/config/cypress.config.ts --config '{\"supportFile\": \"../cypress-e2e/support/e2e.ts\"}' --spec \"./e2e/*.cy.ts\" --browser=chrome && lsof -ti tcp:3000,3001,3002 | xargs kill" + }, + "devDependencies": { + "wait-on": "7.2.0", + "concurrently": "7.6.0", + "forever": "4.0.3", + "serve": "^14.2.3" + } +} diff --git a/unit-test/remote/.gitignore b/unit-test/remote/.gitignore new file mode 100644 index 00000000000..4d29575de80 --- /dev/null +++ b/unit-test/remote/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/unit-test/remote/modulefederation.config.js b/unit-test/remote/modulefederation.config.js new file mode 100644 index 00000000000..5962a6ab9ff --- /dev/null +++ b/unit-test/remote/modulefederation.config.js @@ -0,0 +1,21 @@ +const { dependencies } = require('./package.json'); + +module.exports = { + name: 'remote', + library: {type: 'commonjs-module', name: 'remote'}, + exposes: { + './Button': './src/Button', + }, + filename: 'remoteEntry.js', + shared: { + ...dependencies, + react: { + singleton: true, + requiredVersion: dependencies['react'], + }, + 'react-dom': { + singleton: true, + requiredVersion: dependencies['react-dom'], + }, + }, +}; diff --git a/unit-test/remote/package.json b/unit-test/remote/package.json new file mode 100644 index 00000000000..af7a11843e9 --- /dev/null +++ b/unit-test/remote/package.json @@ -0,0 +1,32 @@ +{ + "name": "unit_test_remote", + "version": "0.0.0", + "dependencies": { + "react": "17.0.2", + "react-dom": "17.0.2" + }, + "scripts": { + "start": "rsbuild dev", + "build": "rsbuild build", + "preview": "rsbuild preview" + }, + "eslintConfig": {}, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@module-federation/enhanced": "^0.1.13", + "@rsbuild/core": "0.6.15", + "@rsbuild/plugin-react": "0.6.15", + "@rspack/core": "0.6.5" + } +} diff --git a/unit-test/remote/public/favicon.ico b/unit-test/remote/public/favicon.ico new file mode 100644 index 00000000000..a11777cc471 Binary files /dev/null and b/unit-test/remote/public/favicon.ico differ diff --git a/unit-test/remote/public/index.html b/unit-test/remote/public/index.html new file mode 100644 index 00000000000..b73f992d90d --- /dev/null +++ b/unit-test/remote/public/index.html @@ -0,0 +1,13 @@ + + + + + + + Remote + + + +
+ + diff --git a/unit-test/remote/rsbuild.config.ts b/unit-test/remote/rsbuild.config.ts new file mode 100644 index 00000000000..af682121cf8 --- /dev/null +++ b/unit-test/remote/rsbuild.config.ts @@ -0,0 +1,30 @@ +import { defineConfig } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; +import {ModuleFederationPlugin} from '@module-federation/enhanced/rspack' +//@ts-ignore +import mfConfig from './modulefederation.config'; +import rspack from '@rspack/core'; + +const rsbuildPlugin = () => ({ + name: 'example', + setup(api) { + api.onAfterBuild(() => console.log('done')); + }, +}); + +export default defineConfig({ + server: { + port: 3002, + }, + output: { + targets: ['node'], + }, + plugins: [pluginReact()], + tools: { + rspack: (config, { appendPlugins }) => { + appendPlugins([ + new ModuleFederationPlugin(mfConfig), + ]); + }, + }, +}); diff --git a/unit-test/remote/scripts/build.js b/unit-test/remote/scripts/build.js new file mode 100644 index 00000000000..60a17bd95d2 --- /dev/null +++ b/unit-test/remote/scripts/build.js @@ -0,0 +1,3 @@ +process.env.NODE_ENV = 'production'; +require('./overrides/webpack-config'); +require('react-scripts/scripts/build'); diff --git a/unit-test/remote/scripts/overrides/webpack-config.js b/unit-test/remote/scripts/overrides/webpack-config.js new file mode 100644 index 00000000000..c7f55b49aca --- /dev/null +++ b/unit-test/remote/scripts/overrides/webpack-config.js @@ -0,0 +1,16 @@ +const { ModuleFederationPlugin } = require('webpack').container; + +const webpackConfigPath = 'react-scripts/config/webpack.config'; +const webpackConfig = require(webpackConfigPath); + +const override = config => { + config.plugins.push(new ModuleFederationPlugin(require('../../modulefederation.config.js'))); + + config.output.publicPath = 'auto'; + + return config; +}; + +require.cache[require.resolve(webpackConfigPath)].exports = env => override(webpackConfig(env)); + +module.exports = require(webpackConfigPath); diff --git a/unit-test/remote/scripts/start.js b/unit-test/remote/scripts/start.js new file mode 100644 index 00000000000..17742ef30a2 --- /dev/null +++ b/unit-test/remote/scripts/start.js @@ -0,0 +1,3 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; +require('./overrides/webpack-config'); +require('react-scripts/scripts/start'); diff --git a/unit-test/remote/src/App.js b/unit-test/remote/src/App.js new file mode 100644 index 00000000000..c2689dc3baa --- /dev/null +++ b/unit-test/remote/src/App.js @@ -0,0 +1,11 @@ +import LocalButton from './Button'; + +const App = () => ( +
+

Basic Host-Remote

+

Remote

+ +
+); + +export default App; diff --git a/unit-test/remote/src/Button.js b/unit-test/remote/src/Button.js new file mode 100644 index 00000000000..fd10bdecb58 --- /dev/null +++ b/unit-test/remote/src/Button.js @@ -0,0 +1,2 @@ +const Button = () => ; +export default Button; diff --git a/unit-test/remote/src/bootstrap.js b/unit-test/remote/src/bootstrap.js new file mode 100644 index 00000000000..c9ebadbf857 --- /dev/null +++ b/unit-test/remote/src/bootstrap.js @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root'), +); diff --git a/unit-test/remote/src/index.js b/unit-test/remote/src/index.js new file mode 100644 index 00000000000..b93c7a0268a --- /dev/null +++ b/unit-test/remote/src/index.js @@ -0,0 +1 @@ +import('./bootstrap');