mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-20 21:00:52 +00:00
test: run frontend unit tests in browsers (#38860)
Run them in headless [vitest browser mode](https://vitest.dev/guide/browser/) in chromium and firefox. Similar UX than current tests, it's about 5 times as slow (goes from 1s to 5s on my machine), but definitely worth it as it removes all happy-dom problems. --------- Signed-off-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -73,8 +73,9 @@ jobs:
|
|||||||
frontend:
|
frontend:
|
||||||
- "*.ts"
|
- "*.ts"
|
||||||
- "web_src/**"
|
- "web_src/**"
|
||||||
- "tools/generate-svg.ts"
|
- "tools/**/*.ts"
|
||||||
- "tools/generate-svg-vscode-extensions.json"
|
- "tools/**/*.json"
|
||||||
|
- "tools/playwright.sh"
|
||||||
- "tsconfig.json"
|
- "tsconfig.json"
|
||||||
- "assets/emoji.json"
|
- "assets/emoji.json"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
@@ -134,6 +135,7 @@ jobs:
|
|||||||
e2e:
|
e2e:
|
||||||
- "tests/e2e/**"
|
- "tests/e2e/**"
|
||||||
- "tools/test-e2e.sh"
|
- "tools/test-e2e.sh"
|
||||||
|
- "tools/playwright.sh"
|
||||||
- "playwright.config.ts"
|
- "playwright.config.ts"
|
||||||
|
|
||||||
shell:
|
shell:
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ jobs:
|
|||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
- run: make lint-frontend
|
- run: make lint-frontend
|
||||||
- run: make checks-frontend
|
- run: make checks-frontend
|
||||||
|
- run: make playwright
|
||||||
- run: make test-frontend
|
- run: make test-frontend
|
||||||
- run: make frontend
|
- run: make frontend
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ SHELLCHECK_IMAGE ?= docker.io/koalaman/shellcheck:v0.11.0@sha256:61862eba1fcf09a
|
|||||||
|
|
||||||
CONTAINER_RUNTIME ?= $(shell hash docker >/dev/null 2>&1 && echo docker || echo podman)
|
CONTAINER_RUNTIME ?= $(shell hash docker >/dev/null 2>&1 && echo docker || echo podman)
|
||||||
|
|
||||||
|
PLAYWRIGHT_BROWSERS ?= chromium firefox
|
||||||
|
PLAYWRIGHT_FLAGS ?=
|
||||||
|
|
||||||
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
||||||
ifeq ($(HAS_GO), yes)
|
ifeq ($(HAS_GO), yes)
|
||||||
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
|
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
|
||||||
@@ -389,7 +392,7 @@ test-backend: ## test backend files
|
|||||||
@$(GO) test $(GOTEST_FLAGS) -tags='$(TAGS)' $(GO_TEST_PACKAGES)
|
@$(GO) test $(GOTEST_FLAGS) -tags='$(TAGS)' $(GO_TEST_PACKAGES)
|
||||||
|
|
||||||
.PHONY: test-frontend
|
.PHONY: test-frontend
|
||||||
test-frontend: node_modules ## test frontend files
|
test-frontend: playwright ## test frontend files
|
||||||
pnpm exec vitest
|
pnpm exec vitest
|
||||||
|
|
||||||
.PHONY: test-check
|
.PHONY: test-check
|
||||||
@@ -484,11 +487,11 @@ migrations.individual.test\#%:
|
|||||||
|
|
||||||
.PHONY: playwright
|
.PHONY: playwright
|
||||||
playwright: deps-frontend
|
playwright: deps-frontend
|
||||||
@CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) ./tools/test-e2e.sh install
|
@./tools/playwright.sh $(PLAYWRIGHT_FLAGS) $(PLAYWRIGHT_BROWSERS)
|
||||||
|
|
||||||
.PHONY: test-e2e
|
.PHONY: test-e2e
|
||||||
test-e2e: playwright frontend backend
|
test-e2e: playwright frontend backend
|
||||||
@CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) EXECUTABLE=$(EXECUTABLE) ./tools/test-e2e.sh run $(GITEA_TEST_E2E_FLAGS)
|
@CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) EXECUTABLE=$(EXECUTABLE) ./tools/test-e2e.sh $(GITEA_TEST_E2E_FLAGS)
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: frontend backend ## build everything
|
build: frontend backend ## build everything
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ go test -run '^TestName$' ./modulepath/
|
|||||||
make test-backend#TestName
|
make test-backend#TestName
|
||||||
```
|
```
|
||||||
|
|
||||||
Frontend unit tests run with [Vitest](https://vitest.dev/):
|
Frontend unit tests run with [Vitest](https://vitest.dev/) browser mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make test-frontend
|
make test-frontend
|
||||||
|
|||||||
+1
-1
@@ -1095,7 +1095,7 @@ export default defineConfig([
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.test.ts', 'web_src/js/test/setup.ts'],
|
files: ['**/*.test.ts', 'web_src/js/vitest.setup.ts'],
|
||||||
plugins: {vitest},
|
plugins: {vitest},
|
||||||
languageOptions: {globals: globals.vitest},
|
languageOptions: {globals: globals.vitest},
|
||||||
rules: {
|
rules: {
|
||||||
|
|||||||
+1
-1
@@ -83,6 +83,7 @@
|
|||||||
"@types/toastify-js": "1.12.4",
|
"@types/toastify-js": "1.12.4",
|
||||||
"@typescript-eslint/parser": "8.67.0",
|
"@typescript-eslint/parser": "8.67.0",
|
||||||
"@vitejs/plugin-vue": "6.0.8",
|
"@vitejs/plugin-vue": "6.0.8",
|
||||||
|
"@vitest/browser-playwright": "4.1.10",
|
||||||
"@vitest/eslint-plugin": "1.6.27",
|
"@vitest/eslint-plugin": "1.6.27",
|
||||||
"eslint": "10.8.1",
|
"eslint": "10.8.1",
|
||||||
"eslint-import-resolver-typescript": "4.4.5",
|
"eslint-import-resolver-typescript": "4.4.5",
|
||||||
@@ -94,7 +95,6 @@
|
|||||||
"eslint-plugin-vue-scoped-css": "3.1.3",
|
"eslint-plugin-vue-scoped-css": "3.1.3",
|
||||||
"eslint-plugin-wc": "3.1.0",
|
"eslint-plugin-wc": "3.1.0",
|
||||||
"globals": "17.11.0",
|
"globals": "17.11.0",
|
||||||
"happy-dom": "20.11.2",
|
|
||||||
"jiti": "2.7.0",
|
"jiti": "2.7.0",
|
||||||
"linguist-languages": "9.4.0",
|
"linguist-languages": "9.4.0",
|
||||||
"markdownlint-cli": "0.49.1",
|
"markdownlint-cli": "0.49.1",
|
||||||
|
|||||||
Generated
+93
-9
@@ -231,9 +231,12 @@ importers:
|
|||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
specifier: 8.67.0
|
specifier: 8.67.0
|
||||||
version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
|
version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
|
||||||
|
'@vitest/browser-playwright':
|
||||||
|
specifier: 4.1.10
|
||||||
|
version: 4.1.10(playwright@1.62.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10)
|
||||||
'@vitest/eslint-plugin':
|
'@vitest/eslint-plugin':
|
||||||
specifier: 1.6.27
|
specifier: 1.6.27
|
||||||
version: 1.6.27(@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)(vitest@4.1.10(@types/node@26.2.0)(happy-dom@20.11.2)(jsdom@20.0.3(supports-color@10.2.2))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)))
|
version: 1.6.27(@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)(vitest@4.1.10)
|
||||||
eslint:
|
eslint:
|
||||||
specifier: 10.8.1
|
specifier: 10.8.1
|
||||||
version: 10.8.1(jiti@2.7.0)(supports-color@10.2.2)
|
version: 10.8.1(jiti@2.7.0)(supports-color@10.2.2)
|
||||||
@@ -264,9 +267,6 @@ importers:
|
|||||||
globals:
|
globals:
|
||||||
specifier: 17.11.0
|
specifier: 17.11.0
|
||||||
version: 17.11.0
|
version: 17.11.0
|
||||||
happy-dom:
|
|
||||||
specifier: 20.11.2
|
|
||||||
version: 20.11.2
|
|
||||||
jiti:
|
jiti:
|
||||||
specifier: 2.7.0
|
specifier: 2.7.0
|
||||||
version: 2.7.0
|
version: 2.7.0
|
||||||
@@ -311,7 +311,7 @@ importers:
|
|||||||
version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
|
version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: 4.1.10
|
specifier: 4.1.10
|
||||||
version: 4.1.10(@types/node@26.2.0)(happy-dom@20.11.2)(jsdom@20.0.3(supports-color@10.2.2))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))
|
version: 4.1.10(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(jsdom@20.0.3(supports-color@10.2.2))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))
|
||||||
vue-tsc:
|
vue-tsc:
|
||||||
specifier: 3.3.9
|
specifier: 3.3.9
|
||||||
version: 3.3.9(typescript@6.0.3)
|
version: 3.3.9(typescript@6.0.3)
|
||||||
@@ -350,6 +350,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
|
resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@blazediff/core@1.9.1':
|
||||||
|
resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==}
|
||||||
|
|
||||||
'@braintree/sanitize-url@7.1.2':
|
'@braintree/sanitize-url@7.1.2':
|
||||||
resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}
|
resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}
|
||||||
|
|
||||||
@@ -933,6 +936,9 @@ packages:
|
|||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
'@polka/url@1.0.0-next.29':
|
||||||
|
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||||
|
|
||||||
'@popperjs/core@2.11.8':
|
'@popperjs/core@2.11.8':
|
||||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
||||||
|
|
||||||
@@ -1519,6 +1525,17 @@ packages:
|
|||||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
|
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
vue: ^3.2.25
|
vue: ^3.2.25
|
||||||
|
|
||||||
|
'@vitest/browser-playwright@4.1.10':
|
||||||
|
resolution: {integrity: sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==}
|
||||||
|
peerDependencies:
|
||||||
|
playwright: '*'
|
||||||
|
vitest: 4.1.10
|
||||||
|
|
||||||
|
'@vitest/browser@4.1.10':
|
||||||
|
resolution: {integrity: sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==}
|
||||||
|
peerDependencies:
|
||||||
|
vitest: 4.1.10
|
||||||
|
|
||||||
'@vitest/eslint-plugin@1.6.27':
|
'@vitest/eslint-plugin@1.6.27':
|
||||||
resolution: {integrity: sha512-X1RCAfwbatG4GFbJ/1PIHP9MSZMt0JJThqbYID+vS4L783k6QGlLPe421wQE8dHXuGCcenVLsydiM4qzsYWxhg==}
|
resolution: {integrity: sha512-X1RCAfwbatG4GFbJ/1PIHP9MSZMt0JJThqbYID+vS4L783k6QGlLPe421wQE8dHXuGCcenVLsydiM4qzsYWxhg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -3347,6 +3364,10 @@ packages:
|
|||||||
moo@0.5.3:
|
moo@0.5.3:
|
||||||
resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==}
|
resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==}
|
||||||
|
|
||||||
|
mrmime@2.0.1:
|
||||||
|
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
@@ -3547,6 +3568,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
pngjs@7.0.0:
|
||||||
|
resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==}
|
||||||
|
engines: {node: '>=14.19.0'}
|
||||||
|
|
||||||
points-on-curve@0.2.0:
|
points-on-curve@0.2.0:
|
||||||
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
|
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
|
||||||
|
|
||||||
@@ -3830,6 +3855,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
sirv@3.0.2:
|
||||||
|
resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
slash@3.0.0:
|
slash@3.0.0:
|
||||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4058,6 +4087,10 @@ packages:
|
|||||||
toastify-js@1.12.0:
|
toastify-js@1.12.0:
|
||||||
resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==}
|
resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==}
|
||||||
|
|
||||||
|
totalist@3.0.1:
|
||||||
|
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
tough-cookie@4.1.4:
|
tough-cookie@4.1.4:
|
||||||
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
|
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -4422,6 +4455,8 @@ snapshots:
|
|||||||
'@babel/helper-string-parser': 7.29.7
|
'@babel/helper-string-parser': 7.29.7
|
||||||
'@babel/helper-validator-identifier': 7.29.7
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
|
|
||||||
|
'@blazediff/core@1.9.1': {}
|
||||||
|
|
||||||
'@braintree/sanitize-url@7.1.2': {}
|
'@braintree/sanitize-url@7.1.2': {}
|
||||||
|
|
||||||
'@cacheable/memory@2.2.0':
|
'@cacheable/memory@2.2.0':
|
||||||
@@ -5153,6 +5188,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
playwright: 1.62.1
|
playwright: 1.62.1
|
||||||
|
|
||||||
|
'@polka/url@1.0.0-next.29': {}
|
||||||
|
|
||||||
'@popperjs/core@2.11.8': {}
|
'@popperjs/core@2.11.8': {}
|
||||||
|
|
||||||
'@primer/octicons@19.33.0':
|
'@primer/octicons@19.33.0':
|
||||||
@@ -5508,11 +5545,13 @@ snapshots:
|
|||||||
|
|
||||||
'@types/unist@2.0.11': {}
|
'@types/unist@2.0.11': {}
|
||||||
|
|
||||||
'@types/whatwg-mimetype@3.0.2': {}
|
'@types/whatwg-mimetype@3.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@types/ws@8.18.1':
|
'@types/ws@8.18.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 26.2.0
|
'@types/node': 26.2.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@types/yargs-parser@21.0.3': {}
|
'@types/yargs-parser@21.0.3': {}
|
||||||
|
|
||||||
@@ -5692,7 +5731,37 @@ snapshots:
|
|||||||
vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)
|
vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)
|
||||||
vue: 3.5.41(typescript@6.0.3)
|
vue: 3.5.41(typescript@6.0.3)
|
||||||
|
|
||||||
'@vitest/eslint-plugin@1.6.27(@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)(vitest@4.1.10(@types/node@26.2.0)(happy-dom@20.11.2)(jsdom@20.0.3(supports-color@10.2.2))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)))':
|
'@vitest/browser-playwright@4.1.10(playwright@1.62.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10)':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/browser': 4.1.10(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10)
|
||||||
|
'@vitest/mocker': 4.1.10(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))
|
||||||
|
playwright: 1.62.1
|
||||||
|
tinyrainbow: 3.1.0
|
||||||
|
vitest: 4.1.10(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(jsdom@20.0.3(supports-color@10.2.2))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- msw
|
||||||
|
- utf-8-validate
|
||||||
|
- vite
|
||||||
|
|
||||||
|
'@vitest/browser@4.1.10(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10)':
|
||||||
|
dependencies:
|
||||||
|
'@blazediff/core': 1.9.1
|
||||||
|
'@vitest/mocker': 4.1.10(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))
|
||||||
|
'@vitest/utils': 4.1.10
|
||||||
|
magic-string: 0.30.21
|
||||||
|
pngjs: 7.0.0
|
||||||
|
sirv: 3.0.2
|
||||||
|
tinyrainbow: 3.1.0
|
||||||
|
vitest: 4.1.10(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(jsdom@20.0.3(supports-color@10.2.2))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))
|
||||||
|
ws: 8.21.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- msw
|
||||||
|
- utf-8-validate
|
||||||
|
- vite
|
||||||
|
|
||||||
|
'@vitest/eslint-plugin@1.6.27(@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)(vitest@4.1.10)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.67.0
|
'@typescript-eslint/scope-manager': 8.67.0
|
||||||
'@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
|
'@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
|
||||||
@@ -5700,7 +5769,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
|
'@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
vitest: 4.1.10(@types/node@26.2.0)(happy-dom@20.11.2)(jsdom@20.0.3(supports-color@10.2.2))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))
|
vitest: 4.1.10(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(jsdom@20.0.3(supports-color@10.2.2))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -5997,6 +6066,7 @@ snapshots:
|
|||||||
buffer-image-size@0.6.4:
|
buffer-image-size@0.6.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 26.2.0
|
'@types/node': 26.2.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
buffer@5.7.1:
|
buffer@5.7.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7106,6 +7176,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
optional: true
|
||||||
|
|
||||||
has-bigints@1.1.0:
|
has-bigints@1.1.0:
|
||||||
optional: true
|
optional: true
|
||||||
@@ -7902,6 +7973,8 @@ snapshots:
|
|||||||
|
|
||||||
moo@0.5.3: {}
|
moo@0.5.3: {}
|
||||||
|
|
||||||
|
mrmime@2.0.1: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
muggle-string@0.4.1: {}
|
muggle-string@0.4.1: {}
|
||||||
@@ -8096,6 +8169,8 @@ snapshots:
|
|||||||
|
|
||||||
pluralize@8.0.0: {}
|
pluralize@8.0.0: {}
|
||||||
|
|
||||||
|
pngjs@7.0.0: {}
|
||||||
|
|
||||||
points-on-curve@0.2.0: {}
|
points-on-curve@0.2.0: {}
|
||||||
|
|
||||||
points-on-path@0.2.1:
|
points-on-path@0.2.1:
|
||||||
@@ -8420,6 +8495,12 @@ snapshots:
|
|||||||
|
|
||||||
signal-exit@4.1.0: {}
|
signal-exit@4.1.0: {}
|
||||||
|
|
||||||
|
sirv@3.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@polka/url': 1.0.0-next.29
|
||||||
|
mrmime: 2.0.1
|
||||||
|
totalist: 3.0.1
|
||||||
|
|
||||||
slash@3.0.0: {}
|
slash@3.0.0: {}
|
||||||
|
|
||||||
slash@5.1.0: {}
|
slash@5.1.0: {}
|
||||||
@@ -8713,6 +8794,8 @@ snapshots:
|
|||||||
|
|
||||||
toastify-js@1.12.0: {}
|
toastify-js@1.12.0: {}
|
||||||
|
|
||||||
|
totalist@3.0.1: {}
|
||||||
|
|
||||||
tough-cookie@4.1.4:
|
tough-cookie@4.1.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
psl: 1.15.0
|
psl: 1.15.0
|
||||||
@@ -8888,7 +8971,7 @@ snapshots:
|
|||||||
jiti: 2.7.0
|
jiti: 2.7.0
|
||||||
yaml: 2.9.0
|
yaml: 2.9.0
|
||||||
|
|
||||||
vitest@4.1.10(@types/node@26.2.0)(happy-dom@20.11.2)(jsdom@20.0.3(supports-color@10.2.2))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)):
|
vitest@4.1.10(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(jsdom@20.0.3(supports-color@10.2.2))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 4.1.10
|
'@vitest/expect': 4.1.10
|
||||||
'@vitest/mocker': 4.1.10(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))
|
'@vitest/mocker': 4.1.10(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))
|
||||||
@@ -8912,6 +8995,7 @@ snapshots:
|
|||||||
why-is-node-running: 2.3.0
|
why-is-node-running: 2.3.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 26.2.0
|
'@types/node': 26.2.0
|
||||||
|
'@vitest/browser-playwright': 4.1.10(playwright@1.62.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10)
|
||||||
happy-dom: 20.11.2
|
happy-dom: 20.11.2
|
||||||
jsdom: 20.0.3(supports-color@10.2.2)
|
jsdom: 20.0.3(supports-color@10.2.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
|||||||
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# playwright only supports ubuntu/debian officially, and on CI VMs its system deps are pre-installed
|
||||||
|
if [ -z "${GITHUB_ACTIONS:-}" ] && [ "$(uname -s)" = "Linux" ] && grep -qE '^ID(_LIKE)?=.*(ubuntu|debian)' /etc/os-release 2>/dev/null; then
|
||||||
|
pnpm exec playwright install --with-deps "$@"
|
||||||
|
else
|
||||||
|
pnpm exec playwright install "$@"
|
||||||
|
fi
|
||||||
+3
-25
@@ -44,13 +44,6 @@ wait_for_container() {
|
|||||||
echo "Container is ready."
|
echo "Container is ready."
|
||||||
}
|
}
|
||||||
|
|
||||||
CMD="${1:-run}"
|
|
||||||
if [ "$CMD" = "install" ] || [ "$CMD" = "run" ]; then
|
|
||||||
[ $# -gt 0 ] && shift
|
|
||||||
else
|
|
||||||
CMD="run"
|
|
||||||
fi
|
|
||||||
|
|
||||||
detect_playwright_mode
|
detect_playwright_mode
|
||||||
|
|
||||||
if [ "$PLAYWRIGHT_MODE" = "container" ]; then
|
if [ "$PLAYWRIGHT_MODE" = "container" ]; then
|
||||||
@@ -65,25 +58,10 @@ if [ "$PLAYWRIGHT_MODE" = "container" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
PLAYWRIGHT_IMAGE="mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-noble"
|
PLAYWRIGHT_IMAGE="mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-noble"
|
||||||
fi
|
echo "Running playwright in container"
|
||||||
|
if ! "$CONTAINER_RUNTIME" image inspect "$PLAYWRIGHT_IMAGE" >/dev/null 2>&1; then
|
||||||
if [ "$CMD" = "install" ]; then
|
"$CONTAINER_RUNTIME" pull "$PLAYWRIGHT_IMAGE"
|
||||||
if [ "$PLAYWRIGHT_MODE" = "local" ]; then
|
|
||||||
# on GitHub Actions VMs, playwright's system deps are pre-installed
|
|
||||||
if [ -z "${GITHUB_ACTIONS:-}" ]; then
|
|
||||||
# shellcheck disable=SC2086 # flag string
|
|
||||||
pnpm exec playwright install --with-deps chromium firefox ${PLAYWRIGHT_FLAGS:-}
|
|
||||||
else
|
|
||||||
# shellcheck disable=SC2086 # flag string
|
|
||||||
pnpm exec playwright install chromium firefox ${PLAYWRIGHT_FLAGS:-}
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Running playwright in container as host distro is not supported by playwright directly"
|
|
||||||
if ! "$CONTAINER_RUNTIME" image inspect "$PLAYWRIGHT_IMAGE" >/dev/null 2>&1; then
|
|
||||||
"$CONTAINER_RUNTIME" pull "$PLAYWRIGHT_IMAGE"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
exit 0
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create isolated work directory
|
# Create isolated work directory
|
||||||
|
|||||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ declare module '*.svg' {
|
|||||||
export default value;
|
export default value;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '*.css' {
|
declare module '*.txt' {
|
||||||
const value: string;
|
const value: string;
|
||||||
export default value;
|
export default value;
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-11
@@ -1,25 +1,44 @@
|
|||||||
import {defineConfig} from 'vitest/config';
|
import {defineConfig} from 'vitest/config';
|
||||||
|
import {playwright} from '@vitest/browser-playwright';
|
||||||
import {sharedPlugins, vueDefines} from './tools/shared.ts';
|
import {sharedPlugins, vueDefines} from './tools/shared.ts';
|
||||||
|
import {env} from 'node:process';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
include: [
|
|
||||||
'web_src/**/*.test.ts',
|
|
||||||
'tools/eslint-rules/**/*.test.ts',
|
|
||||||
],
|
|
||||||
setupFiles: ['web_src/js/vitest.setup.ts'],
|
|
||||||
environment: 'happy-dom',
|
|
||||||
testTimeout: 20000,
|
testTimeout: 20000,
|
||||||
open: false,
|
|
||||||
allowOnly: true,
|
allowOnly: true,
|
||||||
passWithNoTests: true,
|
passWithNoTests: true,
|
||||||
globals: true,
|
globals: true,
|
||||||
watch: false,
|
watch: false,
|
||||||
isolate: false,
|
projects: [
|
||||||
sequence: {
|
{
|
||||||
concurrent: true,
|
extends: true,
|
||||||
},
|
test: {
|
||||||
|
name: 'browser',
|
||||||
|
include: ['web_src/**/*.test.ts'],
|
||||||
|
setupFiles: ['web_src/js/vitest.setup.ts'],
|
||||||
|
browser: {
|
||||||
|
enabled: true,
|
||||||
|
provider: playwright(),
|
||||||
|
headless: true,
|
||||||
|
screenshotFailures: false,
|
||||||
|
instances: ((env.PLAYWRIGHT_BROWSERS || 'chromium firefox')
|
||||||
|
.split(' ') as Array<'chromium' | 'firefox' | 'webkit'>)
|
||||||
|
.map((browser) => ({browser, name: browser})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
extends: true,
|
||||||
|
test: {
|
||||||
|
name: 'node',
|
||||||
|
include: ['tools/**/*.test.ts'],
|
||||||
|
environment: 'node',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
publicDir: false,
|
||||||
define: vueDefines,
|
define: vueDefines,
|
||||||
plugins: sharedPlugins(),
|
plugins: sharedPlugins(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {execPseudoSelectorCommands, handleFetchActionErrorFields, handleFetchActionSuccessJson} from './common-fetch-action.ts';
|
import {execPseudoSelectorCommands, handleFetchActionErrorFields, handleFetchActionSuccessJson} from './common-fetch-action.ts';
|
||||||
import {createElementFromHTML} from '../utils/dom.ts';
|
import {createElementFromHTML} from '../utils/dom.ts';
|
||||||
import {normalizeTestHtml} from '../utils/testhelper.ts';
|
import {captureNavigations, normalizeTestHtml} from '../utils/testhelper.ts';
|
||||||
|
|
||||||
test('execPseudoSelectorCommands', () => {
|
test('execPseudoSelectorCommands', () => {
|
||||||
window.document.body.innerHTML = `
|
window.document.body.innerHTML = `
|
||||||
@@ -41,23 +41,11 @@ test('execPseudoSelectorCommands', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleFetchActionSuccessJson', async () => {
|
test('handleFetchActionSuccessJson', async () => {
|
||||||
const spyAssign = vi.spyOn(window.location, 'assign').mockImplementation(() => {});
|
const navigations = captureNavigations();
|
||||||
const spyReload = vi.spyOn(window.location, 'reload').mockImplementation(() => {});
|
|
||||||
|
|
||||||
await handleFetchActionSuccessJson(document.body, {redirect: '/'});
|
await handleFetchActionSuccessJson(document.body, {redirect: '/'});
|
||||||
expect(spyAssign).toHaveBeenCalledTimes(1);
|
|
||||||
expect(spyReload).toHaveBeenCalledTimes(0);
|
|
||||||
vi.resetAllMocks();
|
|
||||||
|
|
||||||
await handleFetchActionSuccessJson(document.body, {redirect: ''});
|
await handleFetchActionSuccessJson(document.body, {redirect: ''});
|
||||||
expect(spyAssign).toHaveBeenCalledTimes(0);
|
|
||||||
expect(spyReload).toHaveBeenCalledTimes(1);
|
|
||||||
vi.resetAllMocks();
|
|
||||||
|
|
||||||
await handleFetchActionSuccessJson(document.body, {});
|
await handleFetchActionSuccessJson(document.body, {});
|
||||||
expect(spyAssign).toHaveBeenCalledTimes(0);
|
expect(navigations.map((n) => n.type)).toEqual(['push', 'reload', 'reload']);
|
||||||
expect(spyReload).toHaveBeenCalledTimes(1);
|
|
||||||
vi.resetAllMocks();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handleFetchActionErrorFields', () => {
|
test('handleFetchActionErrorFields', () => {
|
||||||
|
|||||||
@@ -3,13 +3,8 @@ import {POST} from '../modules/fetch.ts';
|
|||||||
import {createSortable} from '../modules/sortable.ts';
|
import {createSortable} from '../modules/sortable.ts';
|
||||||
import type {SortableEvent} from 'sortablejs';
|
import type {SortableEvent} from 'sortablejs';
|
||||||
|
|
||||||
vi.mock('../modules/fetch.ts', () => ({
|
vi.mock('../modules/fetch.ts', () => ({POST: vi.fn()}));
|
||||||
POST: vi.fn(),
|
vi.mock('../modules/sortable.ts', () => ({createSortable: vi.fn()}));
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock('../modules/sortable.ts', () => ({
|
|
||||||
createSortable: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const branchesHTML = `
|
const branchesHTML = `
|
||||||
<div id="protected-branches-list" data-update-priority-url="some/repo/branches/priority">
|
<div id="protected-branches-list" data-update-priority-url="some/repo/branches/priority">
|
||||||
@@ -26,29 +21,29 @@ const branchesHTML = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
describe('Repository Branch Settings', () => {
|
describe('Repository Branch Settings', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.mocked(createSortable).mockClear();
|
||||||
|
vi.mocked(POST).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
test('should initialize sortable for protected branches list', () => {
|
test('should initialize sortable for protected branches list', () => {
|
||||||
document.body.innerHTML = branchesHTML;
|
document.body.innerHTML = branchesHTML;
|
||||||
const callsBefore = vi.mocked(createSortable).mock.calls.length;
|
|
||||||
initRepoSettingsBranchesDrag();
|
initRepoSettingsBranchesDrag();
|
||||||
const newCalls = vi.mocked(createSortable).mock.calls.slice(callsBefore);
|
expect(createSortable).toHaveBeenCalledTimes(1);
|
||||||
expect(newCalls).toHaveLength(1);
|
expect(createSortable).toHaveBeenCalledWith(document.querySelector('#protected-branches-list'), expect.objectContaining({handle: '.drag-handle', animation: 150}));
|
||||||
expect(newCalls[0][0]).toBe(document.querySelector('#protected-branches-list'));
|
|
||||||
expect(newCalls[0][1]).toMatchObject({handle: '.drag-handle', animation: 150});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not initialize if protected branches list is not present', () => {
|
test('should not initialize if protected branches list is not present', () => {
|
||||||
document.querySelector('#protected-branches-list')?.remove();
|
document.body.replaceChildren();
|
||||||
const callsBefore = vi.mocked(createSortable).mock.calls.length;
|
|
||||||
initRepoSettingsBranchesDrag();
|
initRepoSettingsBranchesDrag();
|
||||||
expect(vi.mocked(createSortable).mock.calls.length).toBe(callsBefore);
|
expect(createSortable).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should post new order after sorting', () => {
|
test('should post new order after sorting', () => {
|
||||||
document.body.innerHTML = branchesHTML;
|
document.body.innerHTML = branchesHTML;
|
||||||
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
|
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
|
||||||
const callsBefore = vi.mocked(createSortable).mock.calls.length;
|
|
||||||
initRepoSettingsBranchesDrag();
|
initRepoSettingsBranchesDrag();
|
||||||
const onEnd = vi.mocked(createSortable).mock.calls[callsBefore][1]!.onEnd!;
|
const onEnd = vi.mocked(createSortable).mock.calls[0][1]!.onEnd!;
|
||||||
onEnd(new Event('SortableEvent') as SortableEvent);
|
onEnd(new Event('SortableEvent') as SortableEvent);
|
||||||
expect(POST).toHaveBeenCalledWith(
|
expect(POST).toHaveBeenCalledWith(
|
||||||
'some/repo/branches/priority',
|
'some/repo/branches/priority',
|
||||||
|
|||||||
@@ -1,47 +1,35 @@
|
|||||||
import {navigateToIframeLink} from './render-iframe.ts';
|
import {navigateToIframeLink} from './render-iframe.ts';
|
||||||
|
import {captureNavigations} from '../utils/testhelper.ts';
|
||||||
|
|
||||||
describe('navigateToIframeLink', () => {
|
describe('navigateToIframeLink', () => {
|
||||||
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
|
|
||||||
const assignSpy = vi.spyOn(window.location, 'assign').mockImplementation(() => undefined);
|
|
||||||
|
|
||||||
test('safe links', () => {
|
test('safe links', () => {
|
||||||
|
const navigations = captureNavigations();
|
||||||
|
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
|
||||||
navigateToIframeLink('http://example.com', '_blank');
|
navigateToIframeLink('http://example.com', '_blank');
|
||||||
expect(openSpy).toHaveBeenCalledWith('http://example.com/', '_blank', 'noopener,noreferrer');
|
expect(openSpy).toHaveBeenCalledWith('http://example.com/', '_blank', 'noopener,noreferrer');
|
||||||
vi.clearAllMocks();
|
|
||||||
|
|
||||||
navigateToIframeLink('https://example.com', '_self');
|
navigateToIframeLink('https://example.com', '_self');
|
||||||
expect(assignSpy).toHaveBeenCalledWith('https://example.com/');
|
expect(navigations.at(-1)!.url).toEqual('https://example.com/');
|
||||||
vi.clearAllMocks();
|
|
||||||
|
|
||||||
navigateToIframeLink('https://example.com', null);
|
navigateToIframeLink('https://example.com', null);
|
||||||
expect(assignSpy).toHaveBeenCalledWith('https://example.com/');
|
expect(navigations.at(-1)!.url).toEqual('https://example.com/');
|
||||||
vi.clearAllMocks();
|
|
||||||
|
|
||||||
navigateToIframeLink('/path', '');
|
navigateToIframeLink('/path', '');
|
||||||
expect(assignSpy).toHaveBeenCalledWith('http://localhost:3000/path');
|
expect(navigations.at(-1)!.url).toEqual(`${window.location.origin}/path`);
|
||||||
vi.clearAllMocks();
|
|
||||||
|
|
||||||
// input can be any type & any value, keep the same behavior as `window.location.href = 0`
|
// input can be any type & any value, keep the same behavior as `window.location.href = 0`
|
||||||
navigateToIframeLink(0, {});
|
navigateToIframeLink(0, {});
|
||||||
expect(assignSpy).toHaveBeenCalledWith('http://localhost:3000/0');
|
expect(navigations.at(-1)!.url).toEqual(`${window.location.origin}/0`);
|
||||||
vi.clearAllMocks();
|
expect(navigations).toHaveLength(4);
|
||||||
|
openSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('unsafe links', () => {
|
test('unsafe links', () => {
|
||||||
|
const navigations = captureNavigations();
|
||||||
|
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
|
||||||
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
|
||||||
// eslint-disable-next-line no-script-url
|
// eslint-disable-next-line no-script-url
|
||||||
navigateToIframeLink('javascript:void(0);', '_blank');
|
navigateToIframeLink('javascript:void(0);', '_blank');
|
||||||
expect(openSpy).toHaveBeenCalledTimes(0);
|
|
||||||
expect(assignSpy).toHaveBeenCalledTimes(0);
|
|
||||||
expect(window.location.href).toBe('http://localhost:3000/');
|
|
||||||
vi.clearAllMocks();
|
|
||||||
|
|
||||||
navigateToIframeLink('data:image/svg+xml;utf8,<svg></svg>', '');
|
navigateToIframeLink('data:image/svg+xml;utf8,<svg></svg>', '');
|
||||||
expect(openSpy).toHaveBeenCalledTimes(0);
|
expect(openSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(assignSpy).toHaveBeenCalledTimes(0);
|
expect(navigations).toEqual([]);
|
||||||
expect(window.location.href).toBe('http://localhost:3000/');
|
openSpy.mockRestore();
|
||||||
errorSpy.mockRestore();
|
errorSpy.mockRestore();
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,18 +5,19 @@ beforeEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('isGiteaError', () => {
|
test('isGiteaError', () => {
|
||||||
|
const {origin} = window.location;
|
||||||
expect(isGiteaError('', '')).toBe(true);
|
expect(isGiteaError('', '')).toBe(true);
|
||||||
expect(isGiteaError('moz-extension://abc/content.js', '')).toBe(false);
|
expect(isGiteaError('moz-extension://abc/content.js', '')).toBe(false);
|
||||||
expect(isGiteaError('safari-extension://abc/content.js', '')).toBe(false);
|
expect(isGiteaError('safari-extension://abc/content.js', '')).toBe(false);
|
||||||
expect(isGiteaError('safari-web-extension://abc/content.js', '')).toBe(false);
|
expect(isGiteaError('safari-web-extension://abc/content.js', '')).toBe(false);
|
||||||
expect(isGiteaError('chrome-extension://abc/content.js', '')).toBe(false);
|
expect(isGiteaError('chrome-extension://abc/content.js', '')).toBe(false);
|
||||||
expect(isGiteaError('https://other-site.com/script.js', '')).toBe(false);
|
expect(isGiteaError('https://other-site.com/script.js', '')).toBe(false);
|
||||||
expect(isGiteaError('http://localhost:3000/some/page', '')).toBe(true);
|
expect(isGiteaError(`${origin}/some/page`, '')).toBe(true);
|
||||||
expect(isGiteaError('http://localhost:3000/assets/js/index.abc123.js', '')).toBe(true);
|
expect(isGiteaError(`${origin}/assets/js/index.abc123.js`, '')).toBe(true);
|
||||||
expect(isGiteaError('', `Error\n at chrome-extension://abc/content.js:1:1`)).toBe(false);
|
expect(isGiteaError('', `Error\n at chrome-extension://abc/content.js:1:1`)).toBe(false);
|
||||||
expect(isGiteaError('', `Error\n at https://other-site.com/script.js:1:1`)).toBe(false);
|
expect(isGiteaError('', `Error\n at https://other-site.com/script.js:1:1`)).toBe(false);
|
||||||
expect(isGiteaError('', `Error\n at http://localhost:3000/assets/js/index.abc123.js:1:1`)).toBe(true);
|
expect(isGiteaError('', `Error\n at ${origin}/assets/js/index.abc123.js:1:1`)).toBe(true);
|
||||||
expect(isGiteaError('http://localhost:3000/assets/js/index.js', `Error\n at chrome-extension://abc/content.js:1:1`)).toBe(false);
|
expect(isGiteaError(`${origin}/assets/js/index.js`, `Error\n at chrome-extension://abc/content.js:1:1`)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('showGlobalErrorMessage', () => {
|
test('showGlobalErrorMessage', () => {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ test('renderAnsi', () => {
|
|||||||
|
|
||||||
// treat "\033[0K" and "\033[0J" (Erase display/line) as "\r", then it will be covered to "\n" finally.
|
// treat "\033[0K" and "\033[0J" (Erase display/line) as "\r", then it will be covered to "\n" finally.
|
||||||
expect(renderAnsi('a\x1b[Kb\x1b[2Jc')).toEqual('a\nb\nc');
|
expect(renderAnsi('a\x1b[Kb\x1b[2Jc')).toEqual('a\nb\nc');
|
||||||
expect(renderAnsi('\x1b[48;5;88ma\x1b[38;208;48;5;159mb\x1b[m')).toEqual(`<span style="background-color: #870000;">a</span><span style="background-color: #afffff;">b</span>`);
|
expect(renderAnsi('\x1b[48;5;88ma\x1b[38;208;48;5;159mb\x1b[m')).toEqual(`<span style="background-color: rgb(135, 0, 0);">a</span><span style="background-color: rgb(175, 255, 255);">b</span>`);
|
||||||
|
|
||||||
// URLs in ANSI output become clickable links
|
// URLs in ANSI output become clickable links
|
||||||
const link = (url: string) => `<a href="${url}" target="_blank">${url}</a>`;
|
const link = (url: string) => `<a href="${url}" target="_blank">${url}</a>`;
|
||||||
@@ -39,7 +39,7 @@ test('renderAnsi', () => {
|
|||||||
expect(renderAnsi('\x1b[4;58;5;9mx')).toEqual('<span class="ansi-underline" style="text-decoration-color: var(--color-ansi-bright-red);">x</span>');
|
expect(renderAnsi('\x1b[4;58;5;9mx')).toEqual('<span class="ansi-underline" style="text-decoration-color: var(--color-ansi-bright-red);">x</span>');
|
||||||
|
|
||||||
// a color as ":" sub-parameters, with and without a color space id, not consuming the codes after
|
// a color as ":" sub-parameters, with and without a color space id, not consuming the codes after
|
||||||
expect(renderAnsi('\x1b[38:2::255:0:0ma\x1b[48:2:0:0:255mb')).toEqual('<span style="color: #ff0000;">a</span><span style="color: #ff0000; background-color: #0000ff;">b</span>');
|
expect(renderAnsi('\x1b[38:2::255:0:0ma\x1b[48:2:0:0:255mb')).toEqual('<span style="color: rgb(255, 0, 0);">a</span><span style="color: rgb(255, 0, 0); background-color: rgb(0, 0, 255);">b</span>');
|
||||||
expect(renderAnsi('\x1b[1;38:5:9;4mx')).toEqual('<span class="ansi-bold ansi-underline ansi-bright-red-fg">x</span>');
|
expect(renderAnsi('\x1b[1;38:5:9;4mx')).toEqual('<span class="ansi-bold ansi-underline ansi-bright-red-fg">x</span>');
|
||||||
// a private CSI carries no style, even ending in "m", and does not split the run around it
|
// a private CSI carries no style, even ending in "m", and does not split the run around it
|
||||||
expect(renderAnsi('\x1b[31mred\x1b[>4;2m!')).toEqual('<span class="ansi-red-fg">red!</span>');
|
expect(renderAnsi('\x1b[31mred\x1b[>4;2m!')).toEqual('<span class="ansi-red-fg">red!</span>');
|
||||||
|
|||||||
@@ -26,15 +26,16 @@ test('createElementFromAttrs', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('querySingleVisibleElem', () => {
|
test('querySingleVisibleElem', () => {
|
||||||
let el = createElementFromHTML('<div></div>');
|
const el = document.createElement('div');
|
||||||
|
document.body.append(el); // layout, and thus visibility, is only computed in the document
|
||||||
expect(querySingleVisibleElem(el, 'span')).toBeNull();
|
expect(querySingleVisibleElem(el, 'span')).toBeNull();
|
||||||
el = createElementFromHTML('<div><span>foo</span></div>');
|
el.innerHTML = '<span>foo</span>';
|
||||||
expect(querySingleVisibleElem(el, 'span')!.textContent).toEqual('foo');
|
expect(querySingleVisibleElem(el, 'span')!.textContent).toEqual('foo');
|
||||||
el = createElementFromHTML('<div><span style="display: none;">foo</span><span>bar</span></div>');
|
el.innerHTML = '<span style="display: none;">foo</span><span>bar</span>';
|
||||||
expect(querySingleVisibleElem(el, 'span')!.textContent).toEqual('bar');
|
expect(querySingleVisibleElem(el, 'span')!.textContent).toEqual('bar');
|
||||||
el = createElementFromHTML('<div><span class="some-class tw-hidden">foo</span><span>bar</span></div>');
|
el.innerHTML = '<span class="some-class tw-hidden">foo</span><span>bar</span>';
|
||||||
expect(querySingleVisibleElem(el, 'span')!.textContent).toEqual('bar');
|
expect(querySingleVisibleElem(el, 'span')!.textContent).toEqual('bar');
|
||||||
el = createElementFromHTML('<div><span>foo</span><span>bar</span></div>');
|
el.innerHTML = '<span>foo</span><span>bar</span>';
|
||||||
expect(() => querySingleVisibleElem(el, 'span')).toThrow('Expected exactly one visible element');
|
expect(() => querySingleVisibleElem(el, 'span')).toThrow('Expected exactly one visible element');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {debounce} from './func.ts';
|
import {debounce} from './func.ts';
|
||||||
import type {Promisable} from '../types.ts';
|
import type {Promisable} from '../types.ts';
|
||||||
import type $ from 'jquery';
|
import type $ from 'jquery';
|
||||||
import {isInFrontendUnitTest} from './testhelper.ts';
|
|
||||||
|
|
||||||
type ArrayLikeIterable<T> = ArrayLike<T> & Iterable<T>; // for NodeListOf and Array
|
type ArrayLikeIterable<T> = ArrayLike<T> & Iterable<T>; // for NodeListOf and Array
|
||||||
type ElementArg = Element | string | ArrayLikeIterable<Element> | ReturnType<typeof $>;
|
type ElementArg = Element | string | ArrayLikeIterable<Element> | ReturnType<typeof $>;
|
||||||
@@ -73,11 +72,6 @@ export function queryElemSiblings<T extends Element>(el: Element, selector = '*'
|
|||||||
|
|
||||||
/** it works like jQuery.children: only the direct children are selected */
|
/** it works like jQuery.children: only the direct children are selected */
|
||||||
export function queryElemChildren<T extends Element>(parent: Element | ParentNode, selector = '*', fn?: ElementsCallback<T>): ArrayLikeIterable<T> {
|
export function queryElemChildren<T extends Element>(parent: Element | ParentNode, selector = '*', fn?: ElementsCallback<T>): ArrayLikeIterable<T> {
|
||||||
if (isInFrontendUnitTest()) {
|
|
||||||
// https://github.com/capricorn86/happy-dom/issues/1620 : ":scope" doesn't work
|
|
||||||
const selected = Array.from<T>(parent.children as any).filter((child) => child.matches(selector));
|
|
||||||
return applyElemsCallback<T>(selected, fn);
|
|
||||||
}
|
|
||||||
return applyElemsCallback<T>(parent.querySelectorAll(`:scope > ${selector}`), fn);
|
return applyElemsCallback<T>(parent.querySelectorAll(`:scope > ${selector}`), fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,8 +255,7 @@ export function isElemVisible(el: HTMLElement): boolean {
|
|||||||
// Check if an element is visible, equivalent to jQuery's `:visible` pseudo.
|
// Check if an element is visible, equivalent to jQuery's `:visible` pseudo.
|
||||||
// This function DOESN'T account for all possible visibility scenarios, its behavior is covered by the tests of "querySingleVisibleElem"
|
// This function DOESN'T account for all possible visibility scenarios, its behavior is covered by the tests of "querySingleVisibleElem"
|
||||||
if (!el) return false;
|
if (!el) return false;
|
||||||
// checking el.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout
|
return Boolean(!el.classList.contains('tw-hidden') && (el.offsetWidth || el.offsetHeight || el.getClientRects().length));
|
||||||
return Boolean(!el.classList.contains('tw-hidden') && (el.offsetWidth || el.offsetHeight || el.getClientRects().length) && el.style.display !== 'none');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createElementFromHTML<T extends Element>(htmlString: string): T {
|
export function createElementFromHTML<T extends Element>(htmlString: string): T {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import {readFile} from 'node:fs/promises';
|
import globTestData from './glob.test.txt';
|
||||||
import * as path from 'node:path';
|
|
||||||
import {globCompile} from './glob.ts';
|
import {globCompile} from './glob.ts';
|
||||||
|
|
||||||
async function loadGlobTestData(): Promise<{caseNames: string[], caseDataMap: Record<string, string>}> {
|
function loadGlobTestData(): {caseNames: string[], caseDataMap: Record<string, string>} {
|
||||||
const fileContent = await readFile(path.join(import.meta.dirname, 'glob.test.txt'), 'utf8');
|
const fileLines = globTestData.split('\n');
|
||||||
const fileLines = fileContent.split('\n');
|
|
||||||
const caseDataMap: Record<string, string> = {};
|
const caseDataMap: Record<string, string> = {};
|
||||||
const caseNameMap: Record<string, boolean> = {};
|
const caseNameMap: Record<string, boolean> = {};
|
||||||
for (let line of fileLines) {
|
for (let line of fileLines) {
|
||||||
@@ -103,8 +101,8 @@ function loadGlobGolangCases() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
test('GlobCompiler', async () => {
|
test('GlobCompiler', () => {
|
||||||
const {caseNames, caseDataMap} = await loadGlobTestData();
|
const {caseNames, caseDataMap} = loadGlobTestData();
|
||||||
expect(caseNames.length).toBe(10); // should have 10 test cases
|
expect(caseNames.length).toBe(10); // should have 10 test cases
|
||||||
for (const caseName of caseNames) {
|
for (const caseName of caseNames) {
|
||||||
const pattern = caseDataMap[`pattern_${caseName}`];
|
const pattern = caseDataMap[`pattern_${caseName}`];
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import {GET} from '../modules/fetch.ts';
|
import {GET} from '../modules/fetch.ts';
|
||||||
import {matchEmoji, matchMention} from './match.ts';
|
import {matchEmoji, matchMention} from './match.ts';
|
||||||
|
|
||||||
vi.mock('../modules/fetch.ts', () => ({
|
vi.mock('../modules/fetch.ts', () => ({GET: vi.fn()}));
|
||||||
GET: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const testMentions = [
|
const testMentions = [
|
||||||
{key: 'user1 User 1', value: 'user1', name: 'user1', fullname: 'User 1', avatar: 'https://avatar1.com'},
|
{key: 'user1 User 1', value: 'user1', name: 'user1', fullname: 'User 1', avatar: 'https://avatar1.com'},
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
// there could be different "testing" concepts, for example: backend's "setting.IsInTesting"
|
import {onTestFinished} from 'vitest';
|
||||||
// even if backend is in testing mode, frontend could be complied in production mode
|
|
||||||
// so this function only checks if the frontend is in unit testing mode (usually from *.test.ts files)
|
/** Record and block navigations, as a real browser forbids stubbing "window.location" */
|
||||||
export function isInFrontendUnitTest() {
|
export function captureNavigations() {
|
||||||
return import.meta.env.MODE === 'test';
|
const navigations: Array<{url: string, type: NavigationType}> = [];
|
||||||
|
const onNavigate = (e: NavigateEvent) => {
|
||||||
|
navigations.push({url: e.destination.url, type: e.navigationType});
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
window.navigation.addEventListener('navigate', onNavigate);
|
||||||
|
onTestFinished(() => window.navigation.removeEventListener('navigate', onNavigate));
|
||||||
|
return navigations;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** strip common indentation from a string and trim it */
|
/** strip common indentation from a string and trim it */
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import './globals.ts';
|
import './globals.ts';
|
||||||
|
|
||||||
window.config = {
|
window.config = {
|
||||||
appUrl: 'http://localhost:3000/',
|
appUrl: `${window.location.origin}/`,
|
||||||
appSubUrl: '',
|
appSubUrl: '',
|
||||||
assetUrlPrefix: '/assets',
|
assetUrlPrefix: '/assets',
|
||||||
sharedWorkerUri: '',
|
sharedWorkerUri: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user