diff --git a/.github/workflows/halo.yaml b/.github/workflows/halo.yaml
index 0778b7742..f44d639b7 100644
--- a/.github/workflows/halo.yaml
+++ b/.github/workflows/halo.yaml
@@ -67,6 +67,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: halo-sigs/actions/halo-next-docker-build@main # change the version to specific ref or release tag while the action is stable.
+ if: github.event_name != 'pull_request'
with:
image-name: ${{ github.event_name == 'release' && 'halo' || 'halo-dev' }}
ghcr-token: ${{ secrets.GHCR_TOKEN }}
@@ -75,3 +76,18 @@ jobs:
push: ${{ github.event_name == 'push' || github.event_name == 'release' }} # we only push to GHCR if the push is to the next branch
console-ref: ${{ github.event_name == 'release' && github.ref || 'main' }}
platforms: linux/amd64,linux/arm64/v8,linux/ppc64le,linux/s390x
+ - uses: halo-sigs/actions/halo-next-docker-build@main
+ if: github.event_name == 'pull_request'
+ with:
+ image-name: halo-dev
+ push: false
+ console-ref: false
+ load: true
+ platforms: ""
+ - name: test
+ run: |
+ sudo curl -L https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
+ sudo chmod u+x /usr/local/bin/docker-compose
+
+ docker tag ghcr.io/halo-dev/halo-dev:pr-${{ github.event.number }} ghcr.io/halo-dev/halo-dev:dev
+ cd e2e && ./start.sh
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bb827c9c6..dfe0cf7ef 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -48,6 +48,10 @@ git pull upstream master
git push
```
+### E2E
+
+Please consider adding some [e2e test cases](e2e/README.md) to make sure the APIs work as expected.
+
### 开发规范
请参考 [https://docs.halo.run/developer-guide/core/code-style](https://docs.halo.run/developer-guide/core/code-style),请确保所有代码格式化之后再提交。
diff --git a/console/console-src/modules/contents/attachments/AttachmentList.vue b/console/console-src/modules/contents/attachments/AttachmentList.vue
index 8372c1b71..226e1e45e 100644
--- a/console/console-src/modules/contents/attachments/AttachmentList.vue
+++ b/console/console-src/modules/contents/attachments/AttachmentList.vue
@@ -293,7 +293,6 @@ onMounted(() => {
>
diff --git a/console/console-src/modules/contents/attachments/components/AttachmentListItem.vue b/console/console-src/modules/contents/attachments/components/AttachmentListItem.vue
index 27aaaa08a..954271692 100644
--- a/console/console-src/modules/contents/attachments/components/AttachmentListItem.vue
+++ b/console/console-src/modules/contents/attachments/components/AttachmentListItem.vue
@@ -122,7 +122,6 @@ const { operationItems } = useOperationItemExtensionPoint(
>
diff --git a/console/console-src/modules/contents/comments/CommentList.vue b/console/console-src/modules/contents/comments/CommentList.vue
index 7b8c99b77..8910acf5c 100644
--- a/console/console-src/modules/contents/comments/CommentList.vue
+++ b/console/console-src/modules/contents/comments/CommentList.vue
@@ -236,7 +236,6 @@ const handleApproveInBatch = async () => {
>
@@ -375,7 +374,6 @@ const handleApproveInBatch = async () => {
diff --git a/console/console-src/modules/contents/pages/DeletedSinglePageList.vue b/console/console-src/modules/contents/pages/DeletedSinglePageList.vue
index 38b361a0b..ec37d3d16 100644
--- a/console/console-src/modules/contents/pages/DeletedSinglePageList.vue
+++ b/console/console-src/modules/contents/pages/DeletedSinglePageList.vue
@@ -240,7 +240,6 @@ watch(
>
@@ -308,7 +307,6 @@ watch(
diff --git a/console/console-src/modules/contents/pages/SinglePageList.vue b/console/console-src/modules/contents/pages/SinglePageList.vue
index 80be4af5e..89131295f 100644
--- a/console/console-src/modules/contents/pages/SinglePageList.vue
+++ b/console/console-src/modules/contents/pages/SinglePageList.vue
@@ -325,7 +325,6 @@ watch(selectedPageNames, (newValue) => {
>
diff --git a/console/console-src/modules/contents/pages/components/SinglePageListItem.vue b/console/console-src/modules/contents/pages/components/SinglePageListItem.vue
index ac4aff6d3..60be79293 100644
--- a/console/console-src/modules/contents/pages/components/SinglePageListItem.vue
+++ b/console/console-src/modules/contents/pages/components/SinglePageListItem.vue
@@ -130,7 +130,6 @@ const handleDelete = async () => {
diff --git a/console/console-src/modules/contents/posts/DeletedPostList.vue b/console/console-src/modules/contents/posts/DeletedPostList.vue
index c6e07737f..6837127c6 100644
--- a/console/console-src/modules/contents/posts/DeletedPostList.vue
+++ b/console/console-src/modules/contents/posts/DeletedPostList.vue
@@ -233,7 +233,6 @@ watch(
>
@@ -300,7 +299,6 @@ watch(
diff --git a/console/console-src/modules/contents/posts/PostList.vue b/console/console-src/modules/contents/posts/PostList.vue
index 0e622d099..85968934b 100644
--- a/console/console-src/modules/contents/posts/PostList.vue
+++ b/console/console-src/modules/contents/posts/PostList.vue
@@ -331,7 +331,6 @@ watch(selectedPostNames, (newValue) => {
>
diff --git a/console/console-src/modules/contents/posts/components/PostListItem.vue b/console/console-src/modules/contents/posts/components/PostListItem.vue
index ea64ce3ae..55acc68e7 100644
--- a/console/console-src/modules/contents/posts/components/PostListItem.vue
+++ b/console/console-src/modules/contents/posts/components/PostListItem.vue
@@ -178,7 +178,6 @@ const { startFields, endFields } = useEntityFieldItemExtensionPoint(
diff --git a/console/console-src/modules/system/plugins/PluginList.vue b/console/console-src/modules/system/plugins/PluginList.vue
index bed4996ff..87241bc52 100644
--- a/console/console-src/modules/system/plugins/PluginList.vue
+++ b/console/console-src/modules/system/plugins/PluginList.vue
@@ -167,7 +167,6 @@ onMounted(() => {
>
diff --git a/console/console-src/modules/system/plugins/components/PluginListItem.vue b/console/console-src/modules/system/plugins/components/PluginListItem.vue
index afb5e6fa1..a5916c70e 100644
--- a/console/console-src/modules/system/plugins/components/PluginListItem.vue
+++ b/console/console-src/modules/system/plugins/components/PluginListItem.vue
@@ -240,7 +240,6 @@ const { startFields, endFields } = useEntityFieldItemExtensionPoint(
diff --git a/console/console-src/modules/system/roles/RoleDetail.vue b/console/console-src/modules/system/roles/RoleDetail.vue
index 0f3cc9848..e8922503c 100644
--- a/console/console-src/modules/system/roles/RoleDetail.vue
+++ b/console/console-src/modules/system/roles/RoleDetail.vue
@@ -233,18 +233,11 @@ onMounted(() => {
v-if="!isSuperRole"
v-model="selectedRoleTemplates"
:value="role.metadata.name"
- class="h-4 w-4 rounded border-gray-300 text-indigo-600"
type="checkbox"
:disabled="isSystemReserved"
@change="handleRoleTemplateSelect"
/>
-
+
{{
diff --git a/console/console-src/modules/system/roles/components/RoleEditingModal.vue b/console/console-src/modules/system/roles/components/RoleEditingModal.vue
index 19dd9da17..00b1d1a56 100644
--- a/console/console-src/modules/system/roles/components/RoleEditingModal.vue
+++ b/console/console-src/modules/system/roles/components/RoleEditingModal.vue
@@ -219,7 +219,6 @@ const handleResetForm = () => {
diff --git a/console/console-src/modules/system/users/UserList.vue b/console/console-src/modules/system/users/UserList.vue
index 772d6077f..ef98a1bdf 100644
--- a/console/console-src/modules/system/users/UserList.vue
+++ b/console/console-src/modules/system/users/UserList.vue
@@ -299,7 +299,6 @@ onMounted(() => {
>
@@ -412,7 +411,6 @@ onMounted(() => {
=3.0.0 || >= 3.0.0-alpha.1'
+ dependencies:
+ mini-svg-data-uri: 1.4.4
+ tailwindcss: 3.3.0(postcss@8.4.21)
+ dev: true
+
/@tanstack/match-sorter-utils@8.7.6:
resolution: {integrity: sha512-2AMpRiA6QivHOUiBpQAVxjiHAA68Ei23ZUMNaRJrN6omWiSFLoYrxGcT6BXtuzp0Jw4h6HZCmGGIM/gbwebO2A==}
engines: {node: '>=12'}
@@ -8798,6 +8810,11 @@ packages:
engines: {node: '>=4'}
dev: true
+ /mini-svg-data-uri@1.4.4:
+ resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
+ hasBin: true
+ dev: true
+
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
diff --git a/console/src/components/global-search/GlobalSearchModal.vue b/console/src/components/global-search/GlobalSearchModal.vue
index aeee76ef6..343ec8b05 100644
--- a/console/src/components/global-search/GlobalSearchModal.vue
+++ b/console/src/components/global-search/GlobalSearchModal.vue
@@ -382,7 +382,7 @@ const onVisibleChange = (visible: boolean) => {
ref="globalSearchInput"
v-model="keyword"
:placeholder="$t('core.components.global_search.placeholder')"
- class="w-full py-1 text-base outline-none"
+ class="w-full px-0 py-1 text-base outline-none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
diff --git a/console/src/components/signup/SignupForm.vue b/console/src/components/signup/SignupForm.vue
index 88bdd092b..4d6a1fbce 100644
--- a/console/src/components/signup/SignupForm.vue
+++ b/console/src/components/signup/SignupForm.vue
@@ -113,11 +113,18 @@ const inputClasses = {
:placeholder="$t('core.signup.fields.display_name.placeholder')"
:validation-label="$t('core.signup.fields.display_name.placeholder')"
:classes="inputClasses"
- :autofocus="true"
type="text"
validation="required"
>
+
> = {
tagSelect: {
...textClassification,
inner: `${textClassification.inner} !overflow-visible !h-auto min-h-[2.25rem]`,
- input: `w-0 flex-grow outline-0 bg-transparent py-1 px-3 block transition-all appearance-none text-sm antialiased`,
+ input: `w-0 flex-grow bg-transparent py-1 px-3 block transition-all text-sm`,
"post-tags-wrapper": "flex w-full items-center",
"post-tags": "flex w-full flex-wrap items-center",
"post-tag-wrapper": "inline-flex items-center p-1",
@@ -118,7 +117,7 @@ const theme: Record> = {
categorySelect: {
...textClassification,
inner: `${textClassification.inner} !overflow-visible !h-auto min-h-[2.25rem]`,
- input: `w-0 flex-grow outline-0 bg-transparent py-1 px-3 block transition-all appearance-none text-sm antialiased`,
+ input: `w-0 flex-grow bg-transparent py-1 px-3 block transition-all text-sm`,
"post-categories-wrapper": "flex w-full items-center",
"post-categories": "flex w-full flex-wrap items-center",
"post-categories-button":
diff --git a/console/src/locales/en.yaml b/console/src/locales/en.yaml
index ea30f344b..e5d93d423 100644
--- a/console/src/locales/en.yaml
+++ b/console/src/locales/en.yaml
@@ -28,6 +28,8 @@ core:
placeholder: Username
display_name:
placeholder: Display name
+ email:
+ placeholder: Email
password:
placeholder: Password
password_confirm:
diff --git a/console/src/locales/zh-CN.yaml b/console/src/locales/zh-CN.yaml
index 15493b293..6598026b7 100644
--- a/console/src/locales/zh-CN.yaml
+++ b/console/src/locales/zh-CN.yaml
@@ -28,6 +28,8 @@ core:
placeholder: 用户名
display_name:
placeholder: 名称
+ email:
+ placeholder: 电子邮箱
password:
placeholder: 密码
password_confirm:
diff --git a/console/src/locales/zh-TW.yaml b/console/src/locales/zh-TW.yaml
index e213195ff..449a6d7d1 100644
--- a/console/src/locales/zh-TW.yaml
+++ b/console/src/locales/zh-TW.yaml
@@ -28,6 +28,8 @@ core:
placeholder: 用戶名
display_name:
placeholder: 名稱
+ email:
+ placeholder: 電子郵箱
password:
placeholder: 密碼
password_confirm:
diff --git a/console/src/styles/tailwind.css b/console/src/styles/tailwind.css
index b5c61c956..4d9e70473 100644
--- a/console/src/styles/tailwind.css
+++ b/console/src/styles/tailwind.css
@@ -1,3 +1,26 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+/* override @tailwindcss/forms styles */
+input[type="text"],
+input[type="password"],
+input[type="email"],
+input[type="number"],
+input[type="url"],
+input[type="date"],
+input[type="datetime-local"],
+input[type="month"],
+input[type="week"],
+input[type="time"],
+input[type="search"],
+input[type="tel"],
+input:where(:not([type])),
+select,
+textarea {
+ @apply border-none py-0 focus:outline-0 focus:ring-0;
+}
+
+input[type="checkbox"] {
+ @apply rounded-sm border-gray-500;
+}
diff --git a/console/tailwind.config.js b/console/tailwind.config.js
index 9e030c7ad..e807b8c95 100644
--- a/console/tailwind.config.js
+++ b/console/tailwind.config.js
@@ -24,6 +24,7 @@ module.exports = {
require("@tailwindcss/aspect-ratio"),
require("@formkit/themes/tailwindcss"),
require("@tailwindcss/container-queries"),
+ require("@tailwindcss/forms"),
require("tailwindcss-themer")({
defaultTheme: {
extend: {
diff --git a/console/uc-src/modules/profile/components/PersonalAccessTokenCreationModal.vue b/console/uc-src/modules/profile/components/PersonalAccessTokenCreationModal.vue
index ee0dc2f8c..46f77adfd 100644
--- a/console/uc-src/modules/profile/components/PersonalAccessTokenCreationModal.vue
+++ b/console/uc-src/modules/profile/components/PersonalAccessTokenCreationModal.vue
@@ -179,7 +179,6 @@ const { copy } = useClipboard({
diff --git a/e2e/Dockerfile b/e2e/Dockerfile
new file mode 100644
index 000000000..95fa5de25
--- /dev/null
+++ b/e2e/Dockerfile
@@ -0,0 +1,4 @@
+FROM linuxsuren/api-testing:v0.0.14
+WORKDIR /workspace
+COPY testsuite.yaml .
+CMD [ "atest", "run", "-p", "testsuite.yaml", "--level=debug" ]
diff --git a/e2e/README.md b/e2e/README.md
new file mode 100644
index 000000000..6a1906111
--- /dev/null
+++ b/e2e/README.md
@@ -0,0 +1,17 @@
+Please add the corresponding e2e (aka end-to-end) test cases if you add or update APIs.
+
+## How to work
+* Start and watch the [docker-compose](https://docs.docker.com/compose/) via [the script](start.sh)
+ * It has three containers: database, Halo, and testing
+* Run the e2e testing via [api-testing](https://github.com/LinuxSuRen/api-testing)
+ * It will run the test cases from top to bottom
+ * You can add the necessary asserts to it
+
+## Run locally
+Please follow these steps if you want to run the e2e testing locally.
+
+> Please make sure you have installed docker-compose v2
+
+* Build project via `./gradlew clean build -x check` in root directory of this repository
+* Build image via `docker build . -t ghcr.io/halo-dev/halo-dev:dev`
+* Change the directory to `e2e`, then execute `./start.sh`
diff --git a/e2e/compose.yaml b/e2e/compose.yaml
new file mode 100644
index 000000000..74e703d88
--- /dev/null
+++ b/e2e/compose.yaml
@@ -0,0 +1,48 @@
+version: '3.1'
+services:
+ testing:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ links:
+ - halo
+ depends_on:
+ halo:
+ condition: service_healthy
+ halo:
+ image: ghcr.io/halo-dev/halo-dev:dev
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8090/actuator/health/readiness"]
+ interval: 30s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
+ command:
+ - --spring.r2dbc.url=r2dbc:pool:postgresql://halodb/halo
+ - --spring.r2dbc.username=halo
+ # PostgreSQL 的密码,请保证与下方 POSTGRES_PASSWORD 的变量值一致。
+ - --spring.r2dbc.password=openpostgresql
+ - --spring.sql.init.platform=postgresql
+ # 外部访问地址,请根据实际需要修改
+ # - --halo.external-url=http://localhost:8090/
+ links:
+ - postgresql
+ depends_on:
+ postgresql:
+ condition: service_healthy
+ postgresql:
+ image: postgres:15.4
+ container_name: halodb
+ restart: on-failure:3
+ ports:
+ - "5432:5432"
+ healthcheck:
+ test: [ "CMD", "pg_isready" ]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ environment:
+ - POSTGRES_PASSWORD=openpostgresql
+ - POSTGRES_USER=halo
+ - POSTGRES_DB=halo
+ - PGUSER=halo
diff --git a/e2e/start.sh b/e2e/start.sh
new file mode 100755
index 000000000..0df1b0592
--- /dev/null
+++ b/e2e/start.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+file=$1
+if [ "$file" == "" ]
+then
+ file=compose.yaml
+fi
+
+docker-compose version
+docker-compose -f "$file" up --build -d
+
+while true
+do
+ docker-compose -f "$file" ps | grep testing
+ if [ $? -eq 1 ]
+ then
+ code=-1
+ docker-compose -f "$file" logs | grep e2e-testing
+ docker-compose -f "$file" logs | grep e2e-testing | grep Usage
+ if [ $? -eq 1 ]
+ then
+ code=0
+ echo "successed"
+ fi
+
+ docker-compose -f "$file" down
+ set -e
+ exit $code
+ fi
+ sleep 1
+done
diff --git a/e2e/testsuite.yaml b/e2e/testsuite.yaml
new file mode 100644
index 000000000..1776b5e32
--- /dev/null
+++ b/e2e/testsuite.yaml
@@ -0,0 +1,86 @@
+name: halo
+api: |
+ {{default "http://halo:8090" (env "SERVER")}}/apis
+param:
+ postName: "{{randAlpha 6}}"
+items:
+- name: init
+ request:
+ api: /api.console.halo.run/v1alpha1/system/initialize
+ method: POST
+ header:
+ Content-Type: application/json
+ body: |
+ {
+ "siteTitle": "testing",
+ "username": "admin",
+ "password": "123456",
+ "email": "testing@halo.com",
+ "password_confirm": "123456"
+ }
+ expect:
+ statusCode: 201
+- name: createPost
+ request:
+ api: /api.console.halo.run/v1alpha1/posts
+ method: POST
+ header:
+ Authorization: "Basic YWRtaW46MTIzNDU2"
+ Content-Type: application/json
+ body: |
+ {
+ "post": {
+ "spec": {
+ "title": "{{.param.postName}}",
+ "slug": "{{.param.postName}}",
+ "template": "",
+ "cover": "",
+ "deleted": false,
+ "publish": false,
+ "pinned": false,
+ "allowComment": true,
+ "visible": "PUBLIC",
+ "priority": 0,
+ "excerpt": {
+ "autoGenerate": true,
+ "raw": ""
+ },
+ "categories": [],
+ "tags": [],
+ "htmlMetas": []
+ },
+ "apiVersion": "content.halo.run/v1alpha1",
+ "kind": "Post",
+ "metadata": {
+ "name": "c31f2192-c992-47b9-86b4-f3fc0605360e",
+ "annotations": {
+ "content.halo.run/preferred-editor": "default"
+ }
+ }
+ },
+ "content": {
+ "raw": "{{.param.postName}}
",
+ "content": "{{.param.postName}}
",
+ "rawType": "HTML"
+ }
+ }
+- name: listPosts
+ request:
+ api: /api.console.halo.run/v1alpha1/posts?keyword={{.param.postName}}
+ header:
+ Authorization: "Basic YWRtaW46MTIzNDU2"
+ expect:
+ verify:
+ - data.total == 1
+- name: recyclePost
+ request:
+ api: /api.console.halo.run/v1alpha1/posts/{{(index .listPosts.items 0).post.metadata.name}}/recycle
+ method: PUT
+ header:
+ Authorization: "Basic YWRtaW46MTIzNDU2"
+- name: recover
+ request:
+ api: /content.halo.run/v1alpha1/posts/{{(index .listPosts.items 0).post.metadata.name}}
+ method: DELETE
+ header:
+ Authorization: "Basic YWRtaW46MTIzNDU2"