From 45f72e84d3a1f43fcab88f4e0e80cb7092f97ae0 Mon Sep 17 00:00:00 2001 From: MagdalenaJadach Date: Tue, 22 Oct 2024 17:51:02 +0200 Subject: [PATCH] Redundant editor code tidy up (#1103) closes https://github.com/RaspberryPiFoundation/editor-ui/issues/1088 --------- Co-authored-by: Scott --- .env.example | 8 +- .env.webcomponent.example | 7 - .github/workflows/ci-cd.yml | 2 +- .github/workflows/deploy.yml | 2 +- CHANGELOG.md | 1 + Dockerfile | 2 +- README.md | 18 +- config/env.js | 38 ++- config/paths.js | 4 +- docker-compose.yml | 2 +- docs/WebComponent.md | 2 +- package.json | 7 +- scripts/start.js | 163 ------------ scripts/test.js | 28 +-- src/App.jsx | 62 ----- src/App.test.js | 183 -------------- src/assets/stylesheets/BetaBanner.scss | 67 ----- src/assets/stylesheets/GlobalNav.scss | 72 ------ src/components/AppRoutes.jsx | 78 ------ src/components/BetaBanner/BetaBanner.jsx | 81 ------ src/components/BetaBanner/BetaBanner.test.js | 47 ---- .../EmbeddedViewer/EmbeddedViewer.jsx | 69 ------ .../EmbeddedViewer/EmbeddedViewer.test.js | 233 ------------------ .../__snapshots__/EmbeddedViewer.test.js.snap | 161 ------------ src/components/GlobalNav/GlobalNav.jsx | 46 ---- src/components/GlobalNav/GlobalNav.test.js | 60 ----- src/components/LandingPage/LandingPage.jsx | 106 -------- .../LandingPage/LandingPage.test.js | 108 -------- src/components/LocaleLayout/LocaleLayout.jsx | 23 -- .../LocaleLayout/LocaleLayout.test.js | 87 ------- src/components/Modals/BetaModal.jsx | 39 --- src/components/Modals/BetaModal.test.js | 48 ---- .../WebComponentProject.jsx | 3 +- src/containers/Callback.jsx | 37 --- src/containers/ProjectComponentLoader.jsx | 96 -------- src/containers/ProjectComponentLoader.test.js | 227 ----------------- src/index.html | 57 ----- src/index.jsx | 70 ------ src/utils/SilentRenew.js | 15 -- src/utils/apolloCache.js | 63 ----- src/utils/i18n.js | 37 --- ...k.component.config.js => webpack.config.js | 2 +- 42 files changed, 52 insertions(+), 2409 deletions(-) delete mode 100644 .env.webcomponent.example delete mode 100644 scripts/start.js delete mode 100644 src/App.jsx delete mode 100644 src/App.test.js delete mode 100644 src/assets/stylesheets/BetaBanner.scss delete mode 100644 src/assets/stylesheets/GlobalNav.scss delete mode 100644 src/components/AppRoutes.jsx delete mode 100644 src/components/BetaBanner/BetaBanner.jsx delete mode 100644 src/components/BetaBanner/BetaBanner.test.js delete mode 100644 src/components/EmbeddedViewer/EmbeddedViewer.jsx delete mode 100644 src/components/EmbeddedViewer/EmbeddedViewer.test.js delete mode 100644 src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap delete mode 100644 src/components/GlobalNav/GlobalNav.jsx delete mode 100644 src/components/GlobalNav/GlobalNav.test.js delete mode 100644 src/components/LandingPage/LandingPage.jsx delete mode 100644 src/components/LandingPage/LandingPage.test.js delete mode 100644 src/components/LocaleLayout/LocaleLayout.jsx delete mode 100644 src/components/LocaleLayout/LocaleLayout.test.js delete mode 100644 src/components/Modals/BetaModal.jsx delete mode 100644 src/components/Modals/BetaModal.test.js delete mode 100644 src/containers/Callback.jsx delete mode 100644 src/containers/ProjectComponentLoader.jsx delete mode 100644 src/containers/ProjectComponentLoader.test.js delete mode 100644 src/index.html delete mode 100644 src/index.jsx delete mode 100644 src/utils/SilentRenew.js delete mode 100644 src/utils/apolloCache.js rename webpack.component.config.js => webpack.config.js (98%) diff --git a/.env.example b/.env.example index 570bcff22..85d2e38ad 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,10 @@ REACT_APP_AUTHENTICATION_CLIENT_ID='editor-dev' REACT_APP_AUTHENTICATION_URL='http://localhost:9001' +REACT_APP_SENTRY_DSN='' +REACT_APP_SENTRY_ENV='local' +PUBLIC_URL='http://localhost:3011' +ASSETS_URL='http://localhost:3011' REACT_APP_API_ENDPOINT='http://localhost:3009' REACT_APP_GOOGLE_TAG_MANAGER_ID='' REACT_APP_PLAUSIBLE_DATA_DOMAIN='' REACT_APP_PLAUSIBLE_SOURCE='' -REACT_APP_SENTRY_DSN='' -REACT_APP_SENTRY_ENV='local' -PUBLIC_URL='http://localhost:3011' -ASSETS_URL='http://localhost:3010' diff --git a/.env.webcomponent.example b/.env.webcomponent.example deleted file mode 100644 index b3f0e38fc..000000000 --- a/.env.webcomponent.example +++ /dev/null @@ -1,7 +0,0 @@ -REACT_APP_AUTHENTICATION_URL='http://localhost:9001' -REACT_APP_SENTRY_DSN='' -REACT_APP_SENTRY_ENV='local' -PUBLIC_URL=http://localhost:3011 -REACT_APP_IDENTITY_URL='http://localhost:3011' -REACT_APP_API_ENDPOINT='http://localhost:3009' -ASSETS_URL='http://localhost:3011' diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 3bdf218d7..8f0a5f4b1 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -92,7 +92,7 @@ jobs: with: install: false start: | - yarn start:wc + yarn start wait-on: "http://localhost:3011" quiet: true env: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 96fc0be26..2f2199028 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -128,7 +128,7 @@ jobs: if [[ "${{ inputs.environment }}" != "production" ]]; then yarn build:dev fi - yarn build:wc + yarn build env: PUBLIC_URL: ${{ needs.setup-environment.outputs.public_url }} ASSETS_URL: ${{ needs.setup-environment.outputs.assets_url }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f5d357be..a7b48c20d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Enabling web component to find the `turtle` canvas (#1082) - Ability to stop code in the web component (#1083) +- Remove redundant code (#1103) ## [0.27.0] - 2024-09-26 diff --git a/Dockerfile b/Dockerfile index 6ac0feca8..c0281c054 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,6 @@ RUN corepack enable \ RUN yarn -EXPOSE 3010 +EXPOSE 3011 CMD ["yarn", "start"] diff --git a/README.md b/README.md index c067ec376..f9656bb8e 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,9 @@ Copy the example files into the correct place: ``` cp .env.example .env - -cp .env.webcomponent.example .env.webcomponent ``` -Variables for the web application need to go into the `.env` file. -Variables for the web component can be placed in `.env.webcomponent`. +Variables for the web component can be placed in `.env`. ## Private repo setup (.npmrc) @@ -32,7 +29,7 @@ In the project directory, you can run: ### `yarn start` Runs the app in the development mode.\ -Open [http://localhost:3010](http://localhost:3010) to view it in the browser. +Open [http://localhost:3011](http://localhost:3011) to view it in the browser. The page will reload if you make edits.\ You will also see any lint errors in the console. @@ -57,8 +54,9 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d Automated unit tests can be run via the `yarn test` command. These unit tests are written using the JavaScript testing framework `Jest` and make use of the tools provided by the [React Testing Library](https://testing-library.com/docs/). Automated accessibility testing for components is available via the `jest-axe` library. This can be achieved using the `haveNoViolations` matcher provided by `jest-axe`, although this does not guarantee that the tested components have no accessibility issues. Integration testing is carried out via `cypress` and can be run using: -* `yarn exec cypress run` to run in the CLI -* `yarn exec cypress open` to run in the GUI + +- `yarn exec cypress run` to run in the CLI +- `yarn exec cypress open` to run in the GUI Currently, there are basic `cypress` tests for the standalone editor site, the web component and Mission Zero-related functionality. These can be found in the `cypress/e2e` directory. Screenshots and videos related to the most recent `cypress` test run can be found in `cypress/screenshots` and `cypress/videos` respectively. @@ -80,12 +78,6 @@ The web component can be included in a page by using the `` HTML elem - `embedded`: Enable embedded mode which hides some functionality (defaults to `false`) - `output_split_view`: Start with split view in output panel (defaults to `false`, i.e. tabbed view) -### `yarn start:wc` - -Runs the web component in development mode. Open [http://localhost:3011](http://localhost:3011) to view it in the browser. - -**NB** You need to have the main `yarn start` process running too. - It is possible to add query strings to control how the web component is configured. Any HTML attribute can be set on the query string, including `class`, `style` etc. For example, to load the page with the Sense Hat always showing, add [`?sense_hat_always_enabled` to the URL](http://localhost:3011?sense_hat_always_enabled) diff --git a/config/env.js b/config/env.js index b26816770..17de945a2 100644 --- a/config/env.js +++ b/config/env.js @@ -1,16 +1,14 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const paths = require('./paths'); +const fs = require("fs"); +const path = require("path"); +const paths = require("./paths"); // Make sure that including paths.js after env.js will read .env variables. -delete require.cache[require.resolve('./paths')]; +delete require.cache[require.resolve("./paths")]; const NODE_ENV = process.env.NODE_ENV; if (!NODE_ENV) { throw new Error( - 'The NODE_ENV environment variable is required but was not specified.' + "The NODE_ENV environment variable is required but was not specified.", ); } @@ -20,7 +18,7 @@ const dotenvFiles = [ // Don't include `.env.local` for `test` environment // since normally you expect tests to produce the same // results for everyone - NODE_ENV !== 'test' && `${paths.dotenv}.local`, + NODE_ENV !== "test" && `${paths.dotenv}.local`, `${paths.dotenv}.${NODE_ENV}`, paths.dotenv, ].filter(Boolean); @@ -30,12 +28,12 @@ const dotenvFiles = [ // that have already been set. Variable expansion is supported in .env files. // https://github.com/motdotla/dotenv // https://github.com/motdotla/dotenv-expand -dotenvFiles.forEach(dotenvFile => { +dotenvFiles.forEach((dotenvFile) => { if (fs.existsSync(dotenvFile)) { - require('dotenv-expand')( - require('dotenv').config({ + require("dotenv-expand")( + require("dotenv").config({ path: dotenvFile, - }) + }), ); } }); @@ -50,10 +48,10 @@ dotenvFiles.forEach(dotenvFile => { // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 // We also resolve them to make sure all tools using them work consistently. const appDirectory = fs.realpathSync(process.cwd()); -process.env.NODE_PATH = (process.env.NODE_PATH || '') +process.env.NODE_PATH = (process.env.NODE_PATH || "") .split(path.delimiter) - .filter(folder => folder && !path.isAbsolute(folder)) - .map(folder => path.resolve(appDirectory, folder)) + .filter((folder) => folder && !path.isAbsolute(folder)) + .map((folder) => path.resolve(appDirectory, folder)) .join(path.delimiter); // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be @@ -62,7 +60,7 @@ const REACT_APP = /^REACT_APP_/i; function getClientEnvironment(publicUrl) { const raw = Object.keys(process.env) - .filter(key => REACT_APP.test(key)) + .filter((key) => REACT_APP.test(key)) .reduce( (env, key) => { env[key] = process.env[key]; @@ -71,7 +69,7 @@ function getClientEnvironment(publicUrl) { { // Useful for determining whether we’re running in production mode. // Most importantly, it switches React into the correct mode. - NODE_ENV: process.env.NODE_ENV || 'development', + NODE_ENV: process.env.NODE_ENV || "development", // Useful for resolving the correct path to static assets in `public`. // For example, . // This should only be used as an escape hatch. Normally you would put @@ -90,12 +88,12 @@ function getClientEnvironment(publicUrl) { // react-refresh is not 100% stable at this time, // which is why it's disabled by default. // It is defined here so it is available in the webpackHotDevClient. - FAST_REFRESH: process.env.FAST_REFRESH !== 'false', - } + FAST_REFRESH: process.env.FAST_REFRESH !== "false", + }, ); // Stringify all values so we can feed into webpack DefinePlugin const stringified = { - 'process.env': Object.keys(raw).reduce((env, key) => { + "process.env": Object.keys(raw).reduce((env, key) => { env[key] = JSON.stringify(raw[key]); return env; }, {}), diff --git a/config/paths.js b/config/paths.js index 28a32789a..28b0d3a0e 100644 --- a/config/paths.js +++ b/config/paths.js @@ -54,8 +54,8 @@ module.exports = { appPath: resolveApp("."), appBuild: resolveApp(buildPath), appPublic: resolveApp("public"), - appHtml: resolveApp("src/index.html"), - appIndexJs: resolveModule(resolveApp, "src/index"), + appHtml: resolveApp("src/web-component.html"), + appIndexJs: resolveModule(resolveApp, "src/web-component"), appPackageJson: resolveApp("package.json"), appSrc: resolveApp("src"), appTsConfig: resolveApp("tsconfig.json"), diff --git a/docker-compose.yml b/docker-compose.yml index 31aefc168..0068917f3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ x-app: &x-app services: react-ui-wc: <<: *x-app - command: yarn start:wc + command: yarn start ports: - "3011:3011" container_name: react-ui-wc diff --git a/docs/WebComponent.md b/docs/WebComponent.md index 84eb290f2..a865d50eb 100644 --- a/docs/WebComponent.md +++ b/docs/WebComponent.md @@ -2,7 +2,7 @@ To have the web component be able to use the same React components as the site application there needed to be a separate start script for the component. This required being able to customise how build was done and meant the `create-react-app` needed to be ejected. This copies configuration files into the project instead of running it all in create-react-app scripts. -There is a custom webpack config file for the component `webpack.component.config.js` and a script in the `package.json`: `start:wc` which will start serving the web component. +There is a custom webpack config file for the component `webpack.config.js` and a script in the `package.json`: `start` which will start serving the web component. In `public/web-component/index.html` the JavaScript output is added and the web-component mounted. Then viewing `http://localhost:3011` will load the page with the web component mounted. diff --git a/package.json b/package.json index 2ab314887..99a25e806 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,8 @@ "web-vitals": "^1.0.1" }, "scripts": { - "start": "node scripts/start.js", + "start": "NODE_ENV=development BABEL_ENV=development webpack serve -c ./webpack.config.js", + "build": "NODE_ENV=production BABEL_ENV=production webpack build -c ./webpack.config.js", "build:dev": "yarn install --check-cache && yarn run build-storybook", "build-storybook": "cd ./storybook && yarn install && yarn run build-storybook -- -o ../public/storybook --loglevel warn", "lint": "eslint \"src/**/*.{js,jsx,json}\"", @@ -89,9 +90,7 @@ "test": "node scripts/test.js --transformIgnorePatterns 'node_modules/(?!three)/'", "storybook": "cd storybook && rm -rf ./node_modules/.cache/storybook && yarn run storybook", "watch-css": "sass --load-path=./ -q --watch src:src", - "start:wc": "NODE_ENV=development BABEL_ENV=development webpack serve -c ./webpack.component.config.js", - "build:wc": "NODE_ENV=production BABEL_ENV=production webpack build -c ./webpack.component.config.js", - "heroku-postbuild": "export PUBLIC_URL='' && yarn build && yarn build:wc" + "heroku-postbuild": "export PUBLIC_URL='' && yarn build" }, "browserslist": { "production": [ diff --git a/scripts/start.js b/scripts/start.js deleted file mode 100644 index b97c7fcfc..000000000 --- a/scripts/start.js +++ /dev/null @@ -1,163 +0,0 @@ -// Do this as the first thing so that any code reading it knows the right env. -process.env.BABEL_ENV = "development"; -process.env.NODE_ENV = "development"; - -// Makes the script crash on unhandled rejections instead of silently -// ignoring them. In the future, promise rejections that are not handled will -// terminate the Node.js process with a non-zero exit code. -process.on("unhandledRejection", (err) => { - throw err; -}); - -// Ensure environment variables are read. -require("../config/env"); - -const fs = require("fs"); -const chalk = require("react-dev-utils/chalk"); -const webpack = require("webpack"); -const WebpackDevServer = require("webpack-dev-server"); -const clearConsole = require("react-dev-utils/clearConsole"); -const checkRequiredFiles = require("react-dev-utils/checkRequiredFiles"); -const { - choosePort, - createCompiler, - prepareProxy, - prepareUrls, -} = require("react-dev-utils/WebpackDevServerUtils"); -const openBrowser = require("react-dev-utils/openBrowser"); -const semver = require("semver"); -const paths = require("../config/paths"); -const configFactory = require("../webpack.component.config"); -const createDevServerConfig = require("../config/webpackDevServer.config"); -const getClientEnvironment = require("../config/env"); -const react = require(require.resolve("react", { paths: [paths.appPath] })); - -const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); -const useYarn = fs.existsSync(paths.yarnLockFile); -const isInteractive = process.stdout.isTTY; - -// Warn and crash if required files are missing -if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { - process.exit(1); -} - -// Tools like Cloud9 rely on this. -const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3010; -const HOST = process.env.HOST || "0.0.0.0"; - -if (process.env.HOST) { - console.log( - chalk.cyan( - `Attempting to bind to HOST environment variable: ${chalk.yellow( - chalk.bold(process.env.HOST), - )}`, - ), - ); - console.log( - `If this was unintentional, check that you haven't mistakenly set it in your shell.`, - ); - console.log( - `Learn more here: ${chalk.yellow("https://cra.link/advanced-config")}`, - ); - console.log(); -} - -// We require that you explicitly set browsers and do not fall back to -// browserslist defaults. -const { checkBrowsers } = require("react-dev-utils/browsersHelper"); -checkBrowsers(paths.appPath, isInteractive) - .then(() => { - // We attempt to use the default port but if it is busy, we offer the user to - // run on a different port. `choosePort()` Promise resolves to the next free port. - return choosePort(HOST, DEFAULT_PORT); - }) - .then((port) => { - if (port == null) { - // We have not found a port. - return; - } - - const config = configFactory("development"); - const protocol = process.env.HTTPS === "true" ? "https" : "http"; - const appName = require(paths.appPackageJson).name; - - const useTypeScript = fs.existsSync(paths.appTsConfig); - const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === "true"; - const urls = prepareUrls( - protocol, - HOST, - port, - paths.publicUrlOrPath.slice(0, -1), - ); - const devSocket = { - warnings: (warnings) => - devServer.sockWrite(devServer.sockets, "warnings", warnings), - errors: (errors) => - devServer.sockWrite(devServer.sockets, "errors", errors), - }; - // Create a webpack compiler that is configured with custom messages. - const compiler = createCompiler({ - appName, - config, - devSocket, - urls, - useYarn, - useTypeScript, - tscCompileOnError, - webpack, - }); - // Load proxy config - const proxySetting = require(paths.appPackageJson).proxy; - const proxyConfig = prepareProxy( - proxySetting, - paths.appPublic, - paths.publicUrlOrPath, - ); - // Serve webpack assets generated by the compiler over a web server. - const serverConfig = createDevServerConfig( - proxyConfig, - urls.lanUrlForConfig, - ); - Object.assign(serverConfig, { port }); - const devServer = new WebpackDevServer(serverConfig, compiler); - // Launch WebpackDevServer. - (async () => { - await devServer.start(); - - if (isInteractive) { - clearConsole(); - } - - if (env.raw.FAST_REFRESH && semver.lt(react.version, "16.10.0")) { - console.log( - chalk.yellow( - `Fast Refresh requires React 16.10 or higher. You are using React ${react.version}.`, - ), - ); - } - - console.log(chalk.cyan("Starting the development server...\n")); - openBrowser(urls.localUrlForBrowser); - })(); - - ["SIGINT", "SIGTERM"].forEach(function (sig) { - process.on(sig, function () { - devServer.close(); - process.exit(); - }); - }); - - if (process.env.CI !== "true") { - // Gracefully exit when stdin ends - process.stdin.on("end", function () { - devServer.close(); - process.exit(); - }); - } - }) - .catch((err) => { - if (err && err.message) { - console.log(err.message); - } - process.exit(1); - }); diff --git a/scripts/test.js b/scripts/test.js index b57cb3834..8b03376f3 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -1,28 +1,25 @@ -'use strict'; - // Do this as the first thing so that any code reading it knows the right env. -process.env.BABEL_ENV = 'test'; -process.env.NODE_ENV = 'test'; -process.env.PUBLIC_URL = ''; +process.env.BABEL_ENV = "test"; +process.env.NODE_ENV = "test"; +process.env.PUBLIC_URL = ""; // Makes the script crash on unhandled rejections instead of silently // ignoring them. In the future, promise rejections that are not handled will // terminate the Node.js process with a non-zero exit code. -process.on('unhandledRejection', err => { +process.on("unhandledRejection", (err) => { throw err; }); // Ensure environment variables are read. -require('../config/env'); - +require("../config/env"); -const jest = require('jest'); -const execSync = require('child_process').execSync; +const jest = require("jest"); +const execSync = require("child_process").execSync; let argv = process.argv.slice(2); function isInGitRepository() { try { - execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); + execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" }); return true; } catch (e) { return false; @@ -31,7 +28,7 @@ function isInGitRepository() { function isInMercurialRepository() { try { - execSync('hg --cwd . root', { stdio: 'ignore' }); + execSync("hg --cwd . root", { stdio: "ignore" }); return true; } catch (e) { return false; @@ -41,13 +38,12 @@ function isInMercurialRepository() { // Watch unless on CI or explicitly running all tests if ( !process.env.CI && - argv.indexOf('--watchAll') === -1 && - argv.indexOf('--watchAll=false') === -1 + argv.indexOf("--watchAll") === -1 && + argv.indexOf("--watchAll=false") === -1 ) { // https://github.com/facebook/create-react-app/issues/5210 const hasSourceControl = isInGitRepository() || isInMercurialRepository(); - argv.push(hasSourceControl ? '--watch' : '--watchAll'); + argv.push(hasSourceControl ? "--watch" : "--watchAll"); } - jest.run(argv); diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index f7a912d15..000000000 --- a/src/App.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import "./assets/stylesheets/App.scss"; -import "./assets/stylesheets/rpf_design_system/typography.scss"; -import "./assets/stylesheets/Notifications.scss"; - -import { useCookies } from "react-cookie"; -import { BrowserRouter } from "react-router-dom"; -import { useSelector } from "react-redux"; -import { ToastContainer } from "react-toastify"; - -import { SettingsContext } from "./utils/settings"; -import AppRoutes from "./components/AppRoutes"; -import GlobalNav from "./components/GlobalNav/GlobalNav"; -import BetaBanner from "./components/BetaBanner/BetaBanner"; -import BetaModal from "./components/Modals/BetaModal"; -import LoginToSaveModal from "./components/Modals/LoginToSaveModal"; -import ToastCloseButton from "./utils/ToastCloseButton"; - -function App() { - const isEmbedded = useSelector((state) => state.editor.isEmbedded); - const [cookies] = useCookies(["theme", "fontSize"]); - const themeDefault = window.matchMedia("(prefers-color-scheme:dark)").matches - ? "dark" - : "light"; - - return ( -
- - - - {isEmbedded ? null : ( - <> - - - - )} - - - - - - -
- ); -} - -export default App; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 43f6686f5..000000000 --- a/src/App.test.js +++ /dev/null @@ -1,183 +0,0 @@ -import App from "./App"; -import { Provider } from "react-redux"; -import React from "react"; -import { act, render, screen, waitFor } from "@testing-library/react"; -import { Cookies, CookiesProvider } from "react-cookie"; -import configureStore from "redux-mock-store"; - -jest.mock("./utils/Notifications"); -jest.mock("./redux/EditorSlice", () => { - const actual = jest.requireActual("./redux/EditorSlice"); - return { - ...actual, - saveProject: jest.fn(), - }; -}); - -describe("Browser prefers light mode", () => { - let store; - let cookies; - - beforeEach(() => { - window.matchMedia = (query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - }); - - cookies = new Cookies(); - const middlewares = []; - const mockStore = configureStore(middlewares); - const initialState = { - editor: {}, - auth: {}, - }; - store = mockStore(initialState); - }); - - test("Light mode class name added if no cookie", async () => { - const { container } = render( - - - - - , - ); - await waitFor(() => { - const app = container.querySelector("#app"); - expect(app).toHaveClass("--light"); - }); - }); - - test("Dark mode class name added if cookie specifies dark theme", async () => { - cookies.set("theme", "dark"); - const { container } = render( - - - - - , - ); - await waitFor(() => { - const app = container.querySelector("#app"); - expect(app).toHaveClass("--dark"); - }); - }); - - afterEach(() => { - act(() => cookies.remove("theme")); - }); -}); - -describe("Browser prefers dark mode", () => { - let cookies; - let store; - - beforeEach(() => { - window.matchMedia = (query) => ({ - matches: true, - media: query, - onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - }); - cookies = new Cookies(); - const middlewares = []; - const mockStore = configureStore(middlewares); - const initialState = { - editor: {}, - auth: {}, - }; - store = mockStore(initialState); - }); - - test("Dark mode class name added if no cookie", async () => { - const { container } = render( - - - - - , - ); - await waitFor(() => { - const app = container.querySelector("#app"); - expect(app).toHaveClass("--dark"); - }); - }); - - test("Light mode class name added if cookie specifies light theme", async () => { - cookies.set("theme", "light"); - const { container } = render( - - - - - , - ); - await waitFor(() => { - const app = container.querySelector("#app"); - expect(app).toHaveClass("--light"); - }); - }); - - afterEach(() => { - act(() => cookies.remove("theme")); - }); -}); - -describe("Beta banner", () => { - let cookies; - let store; - - beforeEach(() => { - cookies = new Cookies(); - const middlewares = []; - const mockStore = configureStore(middlewares); - const initialState = { - editor: {}, - auth: {}, - }; - store = mockStore(initialState); - }); - - test("Renders beta banner if betaBannerDismissed cookie not set", async () => { - render( - - - - - , - ); - await waitFor(() => { - const app = screen.queryByText("betaBanner.message"); - expect(app).toBeInTheDocument(); - }); - }); - - test("Does not render beta banner if betaBannerDismissed cookie is true", async () => { - cookies.set("betaBannerDismissed", "true"); - render( - - - - - , - ); - await waitFor(() => { - const app = screen.queryByText("betaBanner.message"); - expect(app).not.toBeInTheDocument(); - }); - }); - - afterEach(() => { - act(() => cookies.remove("betaBannerDismissed")); - }); -}); diff --git a/src/assets/stylesheets/BetaBanner.scss b/src/assets/stylesheets/BetaBanner.scss deleted file mode 100644 index c78d366e5..000000000 --- a/src/assets/stylesheets/BetaBanner.scss +++ /dev/null @@ -1,67 +0,0 @@ -@use './rpf_design_system/spacing' as *; -@use './rpf_design_system/font-weight' as *; -@use './rpf_design_system/colours' as *; - -.editor-banner { - display: flex; - align-items: center; - padding: $space-0-25 $space-1-5; - - &--beta { - background-color: $rpf-teal-400; - color: $rpf-text-primary; - - svg { - fill: $rpf-black; - } - } - - &--beta__icon { - padding: $space-0-25; - border-radius: 8px; - background-color: $rpf-teal-200; - font-weight: $font-weight-bold; - margin-inline-end: $space-1-5; - } -} - -.editor-banner__message { - padding: $space-0-25 0; -} - -.editor-banner__link { - display: inline; - font-weight: $font-weight-bold; - padding-inline: 0; - text-decoration: underline; - - &.btn--tertiary { - color: $rpf-text-primary; - - &:hover { - color: $rpf-grey-600; - } - } -} - -.editor-banner__link--feedback { - padding-inline-end: $space-0-25; - white-space: nowrap; -} - -.editor-banner__close-button { - margin-inline-start: auto; - - &.btn--tertiary { - svg { - fill: $rpf-text-primary; - margin-inline-end: 0; - } - - &:hover { - svg { - fill: $rpf-grey-600; - } - } - } -} diff --git a/src/assets/stylesheets/GlobalNav.scss b/src/assets/stylesheets/GlobalNav.scss deleted file mode 100644 index 29fc7b9f0..000000000 --- a/src/assets/stylesheets/GlobalNav.scss +++ /dev/null @@ -1,72 +0,0 @@ -@use "./rpf_design_system/spacing" as *; -@use "./rpf_design_system/font-weight" as *; -@use "./rpf_design_system/colours" as *; -@use "./rpf_design_system/mixins" as *; - -.editor-global-nav-wrapper { - flex: 0 1 auto; - z-index: 2; -} - -.editor-global-nav { - display: flex; - align-items: center; - background-color: $rpf-dark-blue; - block-size: 100%; - z-index: 2; - - &__account { - margin-inline-start: auto; - margin-inline-end: $space-2; - block-size: 100%; - - svg { - fill: $rpf-white; - margin-block: 0; - margin-inline: 0 $space-0-5; - } - - .dropdown-button { - padding: 0 $space-1-5; - - &:hover { - svg { - fill: $rpf-white; - } - } - } - - img { - block-size: $space-2-5; - margin: $space-0-25 0; - } - } - - &__home { - display: flex; - align-items: center; - margin-inline-start: $space-2; - padding: $space-0-5 0; - font-weight: $font-weight-bold; - text-decoration: none; - color: $rpf-white; - - span { - margin-inline-start: $space-1-5; - } - - &:visited { - color: $rpf-white; - } - - @include md-width { - margin-inline-start: $space-3; - } - } -} - -.--dark { - .editor-global-nav { - background-color: $rpf-grey-850; - } -} diff --git a/src/components/AppRoutes.jsx b/src/components/AppRoutes.jsx deleted file mode 100644 index cd3ca2a2d..000000000 --- a/src/components/AppRoutes.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { lazy, Suspense } from "react"; -import { Route, Routes, Navigate, useParams } from "react-router-dom"; -import * as Sentry from "@sentry/react"; - -const Callback = lazy(() => - import(/* webpackPrefetch: true */ "../containers/Callback"), -); -const SilentRenew = lazy(() => - import(/* webpackPrefetch: true */ "../utils/SilentRenew"), -); -const LocaleLayout = lazy(() => - import(/* webpackPrefetch: true */ "./LocaleLayout/LocaleLayout"), -); -const LandingPage = lazy(() => - import(/* webpackPrefetch: true */ "./LandingPage/LandingPage"), -); -const ProjectIndex = lazy(() => - import(/* webpackPrefetch: true */ "./ProjectIndex/ProjectIndex"), -); -const ProjectComponentLoader = lazy(() => - import(/* webpackPrefetch: true */ "../containers/ProjectComponentLoader"), -); -const EmbeddedViewer = lazy(() => - import(/* webpackPrefetch: true */ "./EmbeddedViewer/EmbeddedViewer"), -); - -const suspense = (comp) => }>{comp}; - -const projectLinkRedirects = [ - "/null/projects/:identifier", - "/projects/:identifier", -]; -const localeRedirects = ["/", "/projects"]; - -const ProjectsRedirect = () => { - const { identifier } = useParams(); - return ; -}; - -const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); - -const AppRoutes = () => ( - - )} /> - - )} /> - )}> - )} /> - )} /> - )} - /> - )} - /> - - - {/* Redirects will be moved into a cloudflare worker. This is just interim */} - - {projectLinkRedirects.map((link) => { - return } />; - })} - - {localeRedirects.map((link) => { - return ( - } - /> - ); - })} - -); - -export default AppRoutes; diff --git a/src/components/BetaBanner/BetaBanner.jsx b/src/components/BetaBanner/BetaBanner.jsx deleted file mode 100644 index 74e415aaf..000000000 --- a/src/components/BetaBanner/BetaBanner.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from "react"; -import { useCookies } from "react-cookie"; -import { useTranslation } from "react-i18next"; -import { useDispatch } from "react-redux"; - -import { Link } from "react-router-dom"; -import Button from "../Button/Button"; -import ExternalLinkIcon from "../../assets/icons/external_link.svg"; -import { showBetaModal } from "../../redux/EditorSlice"; - -import "../../assets/stylesheets/BetaBanner.scss"; -import CloseIcon from "../../utils/CloseIcon"; - -const BetaBanner = () => { - const dispatch = useDispatch(); - const { t } = useTranslation(); - const [cookies, setCookie] = useCookies(["betaBannerDismissed"]); - - const closeBanner = () => { - setCookie("betaBannerDismissed", "true", { path: "/" }); - }; - const showModal = () => { - dispatch(showBetaModal()); - }; - const isShowing = !cookies.betaBannerDismissed; - - const handleKeyDown = (e) => { - const enterKey = 13; - const spaceKey = 32; - if (e.keyCode === enterKey || e.keyCode === spaceKey) { - e.preventDefault(); - showModal(); - } - }; - - return isShowing ? ( -
- Beta - - {t("betaBanner.message")} - - {t("betaBanner.modalLink")} - - . - {t("betaBanner.feedbackText")} - - { - <> - - {t("betaBanner.feedback")} - - - - } - - {t("betaBanner.feedbackImprove")} - -
- ) : ( - <> - ); -}; - -export default BetaBanner; diff --git a/src/components/BetaBanner/BetaBanner.test.js b/src/components/BetaBanner/BetaBanner.test.js deleted file mode 100644 index 680017b43..000000000 --- a/src/components/BetaBanner/BetaBanner.test.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from "react"; -import { fireEvent, render, screen } from "@testing-library/react"; -import { Provider } from "react-redux"; -import configureStore from "redux-mock-store"; -import { Cookies, CookiesProvider } from "react-cookie"; -import BetaBanner from "./BetaBanner"; -import { MemoryRouter } from "react-router-dom"; - -let cookies; -let store; - -beforeEach(() => { - cookies = new Cookies(); - const middlewares = []; - const mockStore = configureStore(middlewares); - const initialState = {}; - store = mockStore(initialState); - render( - - - - - - - , - ); -}); - -test("Banner shows", () => { - expect(screen.queryByText("betaBanner.message")).toBeInTheDocument(); -}); - -test("Clicking close button sets cookie", () => { - const closeButton = screen.getAllByRole("button").pop(); - fireEvent.click(closeButton); - expect(cookies.cookies.betaBannerDismissed).toBe("true"); -}); - -test("Clicking link dispatches modal action", () => { - const modalLink = screen.queryByText("betaBanner.modalLink"); - fireEvent.click(modalLink); - expect(store.getActions()).toEqual([{ type: "editor/showBetaModal" }]); -}); - -afterEach(() => { - cookies.remove("betaBannerDismissed"); -}); diff --git a/src/components/EmbeddedViewer/EmbeddedViewer.jsx b/src/components/EmbeddedViewer/EmbeddedViewer.jsx deleted file mode 100644 index 6f5da8f82..000000000 --- a/src/components/EmbeddedViewer/EmbeddedViewer.jsx +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import "../../assets/stylesheets/EmbeddedViewer.scss"; -import "../../assets/stylesheets/Project.scss"; -import React, { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { useParams, useSearchParams } from "react-router-dom"; - -import { useProject } from "../../hooks/useProject"; -import { - setBrowserPreview, - setEmbedded, - setPage, -} from "../../redux/EditorSlice"; -import Output from "../Editor/Output/Output"; -import NotFoundModalEmbedded from "../Modals/NotFoundModalEmbedded"; -import AccessDeniedNoAuthModalEmbedded from "../Modals/AccessDeniedNoAuthModalEmbedded"; - -const EmbeddedViewer = () => { - const dispatch = useDispatch(); - - const page = useSelector((state) => state.editor.page); - const loading = useSelector((state) => state.editor.loading); - const browserPreview = useSelector((state) => state.editor.browserPreview); - const user = useSelector((state) => state.auth.user) || {}; - const notFoundModalShowing = useSelector( - (state) => state.editor.notFoundModalShowing, - ); - const accessDeniedNoAuthModalShowing = useSelector( - (state) => state.editor.accessDeniedNoAuthModalShowing, - ); - const { identifier } = useParams(); - const [searchParams, setSearchParams] = useSearchParams(); - - dispatch(setEmbedded()); - - if (searchParams.get("browserPreview") === "true") { - dispatch(setBrowserPreview(true)); - } - - if (searchParams.get("page")) { - dispatch(setPage(searchParams.get("page"))); - } - - useProject({ - projectIdentifier: identifier, - accessToken: user.access_token, - }); - - useEffect(() => { - if (browserPreview) { - setSearchParams({ - ...Object.fromEntries([...searchParams]), - page, - }); - } - }, [page]); - - return ( -
- {loading === "success" ? : null} - {notFoundModalShowing ? : null} - {accessDeniedNoAuthModalShowing ? ( - - ) : null} -
- ); -}; - -export default EmbeddedViewer; diff --git a/src/components/EmbeddedViewer/EmbeddedViewer.test.js b/src/components/EmbeddedViewer/EmbeddedViewer.test.js deleted file mode 100644 index 1e01a396c..000000000 --- a/src/components/EmbeddedViewer/EmbeddedViewer.test.js +++ /dev/null @@ -1,233 +0,0 @@ -import React from "react"; -import EmbeddedViewer from "./EmbeddedViewer"; - -import { Provider } from "react-redux"; -import { MemoryRouter } from "react-router-dom"; -import configureStore from "redux-mock-store"; -import { render, screen } from "@testing-library/react"; - -import { useProject } from "../../hooks/useProject"; -import { setBrowserPreview, setPage } from "../../redux/EditorSlice"; - -let mockBrowserPreview = false; -let testPage = "index.html"; - -jest.mock("react-router-dom", () => ({ - ...jest.requireActual("react-router-dom"), - useParams: () => ({ - identifier: "my-amazing-project", - }), - useSearchParams: () => [ - { - get: (key) => { - if (key === "browserPreview") { - return mockBrowserPreview; - } else if (key === "page") { - return testPage; - } - }, - }, - ], -})); - -jest.mock("../../hooks/useProject", () => ({ - useProject: jest.fn(), -})); - -let initialState; -let store; - -beforeEach(() => { - initialState = { - editor: { - project: { - components: [ - { - name: "main", - extension: "py", - }, - ], - project_type: "python", - }, - isBrowserPreview: false, - loading: "failed", - notFoundModalShowing: false, - accessDeniedNoAuthModalShowing: false, - accessDeniedWithAuthModalShowing: false, - }, - auth: { - user: { - access_token: "my_token", - }, - }, - }; - window.crossOriginIsolated = true; -}); - -test("Renders without crashing", () => { - initialState = { - ...initialState, - editor: { - ...initialState.editor, - loading: "success", - }, - }; - - const mockStore = configureStore([]); - store = mockStore(initialState); - - const { asFragment } = render( - - - , - ); - expect(asFragment()).toMatchSnapshot(); -}); - -test("Loads project with correct params", () => { - initialState = { - ...initialState, - editor: { - ...initialState.editor, - isBrowserPreview: false, - loading: "success", - }, - }; - - const mockStore = configureStore([]); - store = mockStore(initialState); - - render( - - - , - ); - expect(useProject).toHaveBeenCalledWith({ - projectIdentifier: "my-amazing-project", - accessToken: "my_token", - }); -}); - -test("Loads project with correct params if browser preview", () => { - initialState = { - ...initialState, - editor: { - ...initialState.editor, - isBrowserPreview: true, - loading: "success", - }, - }; - - const mockStore = configureStore([]); - store = mockStore(initialState); - mockBrowserPreview = "true"; - - render( - - - , - ); - expect(useProject).toHaveBeenCalledWith({ - projectIdentifier: "my-amazing-project", - accessToken: "my_token", - }); - - expect(store.getActions()).toEqual( - expect.arrayContaining([setBrowserPreview(true)]), - ); -}); - -test("Renders the expected modal when the project can't be found", () => { - initialState = { - ...initialState, - editor: { - ...initialState.editor, - notFoundModalShowing: true, - }, - }; - - const mockStore = configureStore([]); - store = mockStore(initialState); - - render( - -
- -
-
, - ); - - expect( - screen.queryByText("project.notFoundModal.projectsSiteLinkText"), - ).toBeInTheDocument(); -}); - -test("Renders the expected modal when the project is found but user is not authorised", () => { - initialState = { - ...initialState, - editor: { - ...initialState.editor, - accessDeniedNoAuthModalShowing: true, - modals: { - accessDenied: { identifier: "huh", projectType: "oh" }, - }, - }, - }; - - const mockStore = configureStore([]); - store = mockStore(initialState); - - render( - -
- -
-
, - ); - - expect( - screen.queryByText("project.accessDeniedNoAuthModal.projectsSiteLinkText"), - ).toBeInTheDocument(); -}); - -describe("When page first loaded from search params", () => { - let store; - - beforeEach(async () => { - mockBrowserPreview = "true"; - initialState = { - ...initialState, - editor: { - ...initialState.editor, - isBrowserPreview: false, - }, - }; - - const mockStore = configureStore([]); - store = mockStore(initialState); - - render( - - -
- -
-
-
, - ); - }); - - test("Dispatches action to set browser preview", () => { - expect(store.getActions()).toEqual( - expect.arrayContaining([setBrowserPreview(true)]), - ); - }); - - test("Dispatches action to set page", () => { - expect(store.getActions()).toEqual( - expect.arrayContaining([setPage(testPage)]), - ); - }); -}); diff --git a/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap b/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap deleted file mode 100644 index 8299905e9..000000000 --- a/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap +++ /dev/null @@ -1,161 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Renders without crashing 1`] = ` - -
-
-
-
-
-
    - -
-
-
-
-          
-
-
-