diff --git a/.aegir.js b/.aegir.js index 155ed69e..aa20abc1 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,7 +1,14 @@ /** @type {import('aegir').PartialOptions} */ export default { + test: { }, lint: { - files: ['src/**/*.ts', 'src/**/*.tsx', 'tests/**/*', 'test/**/*.js'] + files: [ + 'src/**/*.[jt]s', + 'src/**/*.[jt]sx', + 'test/**/*.[jt]s', + 'test/**/*.[jt]sx', + 'test-e2e/**/*.[jt]s' + ] }, dependencyCheck: { ignore: [ @@ -19,8 +26,10 @@ export default { ], productionIgnorePatterns: [ 'webpack.config.js', + 'playwright.config.js', + 'test-e2e', '.aegir.js', - '/tests', + '/test', 'dist' ] } diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1f8ebd32..3455e129 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -70,6 +70,7 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx playwright install --with-deps - run: npm run --if-present test:chrome - uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # v3.1.5 with: @@ -100,6 +101,7 @@ jobs: with: node-version: lts/* - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx playwright install --with-deps - run: npm run --if-present test:firefox - uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # v3.1.5 with: diff --git a/.gitignore b/.gitignore index e45263bd..45cdd944 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,8 @@ test-build .LSOverride # Icon must end with two \r -Icon +Icon + # Thumbnails ._* @@ -217,3 +218,9 @@ $RECYCLE.BIN/ # End of https://www.toptal.com/developers/gitignore/api/macos,node,windows,linux .vscode +dist-tsc +playwright-report +.envrc +.direnv +*.nix +.coverage diff --git a/package-lock.json b/package-lock.json index dc6222ca..e47d98ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,16 +24,19 @@ "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.0", + "@playwright/test": "^1.42.1", "@types/react": "^18.0.31", "aegir": "^42.2.2", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", + "copyfiles": "^2.4.1", "css-loader": "^6.10.0", "eslint-config-standard-with-typescript": "^34.0.1", "html-webpack-plugin": "^5.5.0", "mini-css-extract-plugin": "^2.8.1", "npm-run-all": "^4.1.5", "patch-package": "^6.5.1", + "playwright": "^1.42.1", "rimraf": "^4.4.1", "style-loader": "^3.3.4", "terser-webpack-plugin": "^5.3.10", @@ -3380,6 +3383,21 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz", + "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==", + "dev": true, + "dependencies": { + "playwright": "1.42.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", @@ -7743,6 +7761,158 @@ "webpack": "^5.1.0" } }, + "node_modules/copyfiles": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", + "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", + "dev": true, + "dependencies": { + "glob": "^7.0.5", + "minimatch": "^3.0.3", + "mkdirp": "^1.0.4", + "noms": "0.0.0", + "through2": "^2.0.1", + "untildify": "^4.0.0", + "yargs": "^16.1.0" + }, + "bin": { + "copyfiles": "copyfiles", + "copyup": "copyfiles" + } + }, + "node_modules/copyfiles/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/copyfiles/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/copyfiles/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/copyfiles/node_modules/color-convert": { + "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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/copyfiles/node_modules/color-name": { + "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 + }, + "node_modules/copyfiles/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/copyfiles/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/copyfiles/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/copyfiles/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/copyfiles/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/copyfiles/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/core-js-compat": { "version": "3.29.1", "dev": true, @@ -11381,15 +11551,6 @@ "through2": "~2.0.0" } }, - "node_modules/git-log-parser/node_modules/through2": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, "node_modules/glob": { "version": "7.2.0", "dev": true, @@ -15305,6 +15466,18 @@ "node": ">=8" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mocha": { "version": "10.2.0", "dev": true, @@ -15840,6 +16013,40 @@ "dev": true, "license": "MIT" }, + "node_modules/noms": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", + "integrity": "sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "~1.0.31" + } + }, + "node_modules/noms/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/noms/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/noms/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true + }, "node_modules/normalize-package-data": { "version": "3.0.3", "dev": true, @@ -20529,6 +20736,36 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz", + "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==", + "dev": true, + "dependencies": { + "playwright-core": "1.42.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz", + "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/playwright-test": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/playwright-test/-/playwright-test-14.1.0.tgz", @@ -20824,6 +21061,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/please-upgrade-node": { "version": "3.2.0", "dev": true, @@ -24144,6 +24395,16 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -25120,6 +25381,15 @@ "node": ">= 0.8" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.10", "dev": true, diff --git a/package.json b/package.json index 05e138bc..83a5ba62 100644 --- a/package.json +++ b/package.json @@ -8,18 +8,23 @@ "license": "MIT", "scripts": { "analyze-bundle": "webpack --env analyze", - "clean": "aegir clean", + "clean": "aegir clean dist dist-tsc", "dep-check": "aegir dep-check", "lint": "aegir lint", "lint:fix": "aegir lint --fix", - "build": "run-s dep-check lint build:webpack", - "postbuild": "aegir test --build=false", + "build": "run-s build:esm build:webpack", + "prebuild:esm": "copyfiles src/*.svg src/**/*.svg src/*.css src/**/*.css dist-tsc", + "build:esm": "tsc", "build:webpack": "webpack --env production", "serve": "webpack serve --mode=development", "serve:prod": "webpack serve --mode=production", "start": "npm run serve", - "test": "npm run test:node", - "test:node": "webpack --env test && npx mocha test-build/tests.js", + "test": "run-s test:iso 'test:node -- -b false'", + "test:iso": "aegir test -f dist-tsc/test/**/*.spec.js", + "test:browsers": "playwright test -c playwright.config.js", + "test:chrome": "playwright test -c playwright.config.js --project chromium", + "test:firefox": "playwright test -c playwright.config.js --project firefox", + "test:node": "aegir test -t node -f dist-tsc/test/node.js --cov", "postinstall": "patch-package" }, "browser": "./dist/src/index.js", @@ -48,16 +53,19 @@ "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.0", + "@playwright/test": "^1.42.1", "@types/react": "^18.0.31", "aegir": "^42.2.2", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", + "copyfiles": "^2.4.1", "css-loader": "^6.10.0", "eslint-config-standard-with-typescript": "^34.0.1", "html-webpack-plugin": "^5.5.0", "mini-css-extract-plugin": "^2.8.1", "npm-run-all": "^4.1.5", "patch-package": "^6.5.1", + "playwright": "^1.42.1", "rimraf": "^4.4.1", "style-loader": "^3.3.4", "terser-webpack-plugin": "^5.3.10", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 00000000..8f649ccb --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,47 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './test-e2e', + testMatch: /(.+\.)?(test|spec)\.[jt]s/, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: Boolean(process.env.CI), + /* Retry on CI only */ + retries: (process.env.CI != null) ? 2 : 0, + /** + * Opt out of parallel tests by setting workers to 1. + * We don't want to bombard Helia gateway with parallel requests, it's not ready for that yet. + */ + workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + // 'allow' serviceWorkers is the default, but we want to be explicit + serviceWorkers: 'allow' + }, + globalSetup: './test-e2e/global-setup.js', + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] } + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] } + } + ], + webServer: { + command: 'npm run start', + port: 3000, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI + } +}) diff --git a/src/app.tsx b/src/app.tsx index a1c1775c..a98530ee 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,10 +1,10 @@ import React, { useContext } from 'react' -import Config from './components/config.tsx' -import { ConfigContext } from './context/config-context.tsx' -import HelperUi from './helper-ui.tsx' -import { isConfigPage } from './lib/is-config-page.ts' -import { isPathOrSubdomainRequest } from './lib/path-or-subdomain.ts' -import RedirectPage from './redirectPage.tsx' +import Config from './components/config.jsx' +import { ConfigContext } from './context/config-context.jsx' +import HelperUi from './helper-ui.jsx' +import { isConfigPage } from './lib/is-config-page.js' +import { isPathOrSubdomainRequest } from './lib/path-or-subdomain.js' +import RedirectPage from './redirectPage.jsx' function App (): JSX.Element { const { isConfigExpanded, setConfigExpanded } = useContext(ConfigContext) diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 529a34ff..fb06844b 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,5 +1,5 @@ import React, { useContext } from 'react' -import { ConfigContext } from '../context/config-context.tsx' +import { ConfigContext } from '../context/config-context.jsx' import gearIcon from '../gear-icon.svg' import ipfsLogo from '../ipfs-logo.svg' @@ -7,12 +7,12 @@ export default function Header (): JSX.Element { const { isConfigExpanded, setConfigExpanded } = useContext(ConfigContext) return ( -
+
IPFS logo - IPFS Service Worker Gateway -