From dd2824f1aea9561800eeadce707628ff34edd5c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Satg=C3=A9?= Date: Sat, 15 Feb 2025 11:34:36 +0100 Subject: [PATCH] Playwright tests --- .github/workflows/test.yml | 29 +++++++++++++++ .gitignore | 2 +- build.js | 8 ++-- package-lock.json | 75 ++++++++++++++++++++++++++++++++++++++ package.json | 7 +++- playwright.config.js | 19 ++++++++++ readme.md | 2 + src/bundler.js | 48 ++++++++++++++++++------ src/ui.css | 10 +++++ src/ui.js | 29 +++++++++++---- tests/helpers.js | 44 ++++++++++++++++++++++ tests/ui.spec.js | 51 ++++++++++++++++++++++++++ 12 files changed, 299 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 playwright.config.js create mode 100644 tests/helpers.js create mode 100644 tests/ui.spec.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..3a56c01 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: test +on: push +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 1 + strategy: + matrix: + node-version: [18.x] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Cache node_modules + id: cache-node-modules + uses: actions/cache@v3 + with: + key: ${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} + path: node_modules + - name: Install dependencies + if: steps.cache-node-modules.outputs.cache-hit != 'true' + run: npm ci + - name: Install Playwright browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npm run test-playwright diff --git a/.gitignore b/.gitignore index 53c37a1..1521c8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -dist \ No newline at end of file +dist diff --git a/build.js b/build.js index d7426c4..df79d0f 100644 --- a/build.js +++ b/build.js @@ -1,14 +1,14 @@ const esbuild = require('esbuild') -const fs = require('fs') -const fsp = require('fs').promises -const path = require('path') +const fs = require('node:fs') +const fsp = require('node:fs').promises +const path = require('node:path') +const httpdir = require('httpdir') const srcPath = path.join(__dirname, 'src') const distPath = path.join(__dirname, 'dist') build() if (process.argv.includes('--watch')) { - const httpdir = require('/usr/local/lib/node_modules/httpdir') const server = httpdir.createServer({ basePath: distPath, httpPort: 9697 }) server.onStart(({ urls }) => { console.log(urls.join('\n')) diff --git a/package-lock.json b/package-lock.json index 1faeaa6..36a5b16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,10 @@ "htm": "^3.1.1", "preact": "^10.23.1", "prismjs": "^1.29.0" + }, + "devDependencies": { + "@playwright/test": "^1.50.1", + "httpdir": "^2.1.0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -378,6 +382,21 @@ "node": ">=18" } }, + "node_modules/@playwright/test": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz", + "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", + "dev": true, + "dependencies": { + "playwright": "1.50.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@preact/signals": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.3.0.tgz", @@ -456,11 +475,67 @@ "node": ">=18" } }, + "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/htm": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==" }, + "node_modules/httpdir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/httpdir/-/httpdir-2.1.0.tgz", + "integrity": "sha512-cWtKzdMrV9bYtUlvKKHH0kLLnR3Yg8onRPxGjQ5Z515jAsP071upqq2JYl566vCIWAxqTTajEDkyfqYXj/Um3w==", + "dev": true, + "bin": { + "httpdir": "src/cli.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/playwright": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz", + "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", + "dev": true, + "dependencies": { + "playwright-core": "1.50.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz", + "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/preact": { "version": "10.23.1", "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.1.tgz", diff --git a/package.json b/package.json index cc1b2a8..662b874 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "author": "Johan Satgé", "private": true, "scripts": { - "build": "node build.js" + "build": "node build.js", + "test-playwright": "npm run build && playwright test" }, "repository": { "type": "git", @@ -25,5 +26,9 @@ "htm": "^3.1.1", "preact": "^10.23.1", "prismjs": "^1.29.0" + }, + "devDependencies": { + "@playwright/test": "^1.50.1", + "httpdir": "^2.1.0" } } diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..888a9e8 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,19 @@ +const { defineConfig } = require('@playwright/test') +const httpdir = require('httpdir') + +export default defineConfig({ + use: { + baseURL: 'http://localhost:9698', + headless: true, + acceptDownloads: true, + }, + testMatch: 'tests/*.spec.js', + outputDir: 'dist/tests-results', + webServer: { + command: 'node_modules/.bin/httpdir dist 9698', + url: 'http://localhost:9698', + reuseExistingServer: true, + stdout: 'pipe', + stderr: 'pipe', + }, +}) diff --git a/readme.md b/readme.md index ea6efcd..29f3407 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,7 @@ # Standalone Preact Builder ⚛️ +[![Test](https://github.com/johansatge/standalone-preact-builder/actions/workflows/test.yml/badge.svg)](https://github.com/johansatge/standalone-preact-builder/actions) + > Build custom, self-contained & self-hosted Preact script in the browser --- diff --git a/src/bundler.js b/src/bundler.js index 4ab6885..822b2ba 100644 --- a/src/bundler.js +++ b/src/bundler.js @@ -31,7 +31,6 @@ function wasmJsResolver() { // Function sends back the code, sample usage code and stats export async function buildBundle(requestedImports, format) { const { bundleSource, bundleComments, usage } = getBundleSource(requestedImports, format) - console.log('BUNDLE SOURCE', bundleSource) await esbuildInitPromise const params = { stdin: { @@ -139,7 +138,8 @@ function getAppUsageWithHtm(withSignals, withUseState,) { ' const value = count.value', ' return html`', '

Hello ${props.name}!

', - ' ', + '

Count: ${count.value}

', + ' ', ' `', ' }', '', @@ -153,7 +153,8 @@ function getAppUsageWithHtm(withSignals, withUseState,) { ' const [value, setValue] = useState(0)', ' return html`', '

Hello ${props.name}!

', - ' ', + '

Count: ${value}

', + ' ', ' `', ' }', '', @@ -162,21 +163,46 @@ function getAppUsageWithHtm(withSignals, withUseState,) { } return [ '', - ' function App(props) {', - ' return html`

Hello ${props.name}!

`', + ' class App extends Component {', + ' constructor(props) {', + ' super(props)', + ' this.state = { count: 0 }', + ' }', + ' incrementCount = () => {', + ' this.setState((prevState) => ({ count: prevState.count + 1 }))', + ' }', + ' render() {', + ' return html`', + '

Hello ${this.props.name}!

', + '

Count: ${this.state.count}

', + ' ', + ' `', + ' }', ' }', - '', - ' render(html`<${App} name="World" />`, document.querySelector(\'#root\'))', + ' render(h(App, { name: \'World\' }), document.querySelector(\'#root\'))', ] - } function getAppUsageWithoutHtm() { return [ - ' function App(props) {', - ' return h(\'h1\', null, `Hello ${props.name}!`)', + '', + ' class App extends Component {', + ' constructor(props) {', + ' super(props)', + ' this.state = { count: 0 }', + ' }', + ' incrementCount = () => {', + ' this.setState((prevState) => ({ count: prevState.count + 1 }))', + ' }', + ' render() {', + ' return h(\'div\', null, [', + ' h(\'h1\', null, [`Hello ${this.props.name}!`]),', + ' h(\'p\', null, [`Count: ${this.state.count}`]),', + ' h(\'button\', { onClick: this.incrementCount }, [\'Increment\']),', + ' ])', + ' }', ' }', - ' render(App({ name: \'World\' }), document.querySelector(\'#root\'))', + ' render(h(App, { name: \'World\' }), document.querySelector(\'#root\'))', ] } diff --git a/src/ui.css b/src/ui.css index cf41974..fd2d1b1 100644 --- a/src/ui.css +++ b/src/ui.css @@ -172,6 +172,11 @@ body { border-radius: 4px; } +.check-button:disabled { + opacity: 0.5; + cursor: default; +} + .columns { display: grid; grid-template-columns: repeat(4, 1fr); @@ -278,6 +283,11 @@ input[type="radio"]:checked::after { border-radius: 50%; } +input[type="radio"]:disabled { + opacity: 0.5; + cursor: default; +} + .loader { position: absolute; box-sizing: border-box; diff --git a/src/ui.js b/src/ui.js index 386ca47..f7155c1 100644 --- a/src/ui.js +++ b/src/ui.js @@ -41,7 +41,7 @@ const defaultImports = { htm: ['htm'], } const mandatoryImports = { - preact: ['h', 'render'] + preact: ['h', 'render', 'Component'] } const html = htm.bind(h) @@ -159,18 +159,25 @@ function App({ defaultImports }) { Feel free to fine-tune the imports you need to get a fully customized version of Preact though! `} - - + +
${Object.keys(window.preactEcosystem.imports).map((pkg) => html`

${pkg} - ${window.preactEcosystem.versions[pkg]} + + ${window.preactEcosystem.versions[pkg]} +

<${ImportsList} pkg="${pkg}" imports=${window.preactEcosystem.imports[pkg]} selectedImports=${selectedImports} onImportChange=${onImportChange} + isLoadingBundle=${isLoadingBundle} />
`)} @@ -187,6 +194,7 @@ function App({ defaultImports }) { type="radio" name="format" value="esm" onChange=${onFormatChange} checked=${format === 'esm'} + disabled=${isLoadingBundle} /> ESM @@ -195,6 +203,7 @@ function App({ defaultImports }) { type="radio" name="format" value="iife" onChange=${onFormatChange} checked=${format === 'iife'} + disabled=${isLoadingBundle} /> IIFE @@ -206,10 +215,12 @@ function App({ defaultImports }) { >

- - + Size: ${bundle.sizeKb || 0}Kb (${bundle.sizeGzippedKb || 0}Kb gzipped) @@ -221,6 +232,7 @@ function App({ defaultImports }) { @@ -230,13 +242,14 @@ function App({ defaultImports }) { ` } -function ImportsList({ pkg, imports, selectedImports, onImportChange }) { +function ImportsList({ pkg, imports, selectedImports, onImportChange, isLoadingBundle }) { return imports.map((imp) => html`