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"