Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ jobs:
--build-name "$APP_VERSION" \
--build-number "$APP_BUILD_NUMBER"

- name: Prepare unsigned candidate app
run: |
tool/release/prepare_unsigned_app.sh \
"build/macos/Build/Products/Release/$PRODUCT_NAME.app"

- name: Verify app architectures
shell: bash
run: |
Expand All @@ -106,6 +111,11 @@ jobs:
exit 1
fi

- name: Smoke-test candidate app
run: |
tool/release/smoke_test_app.sh \
"build/macos/Build/Products/Release/$PRODUCT_NAME.app"

- name: Create candidate package
id: package
env:
Expand Down
15 changes: 14 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,25 @@ jobs:
run: flutter pub get

- name: Verify formatting
run: dart format --output=none --set-exit-if-changed lib test
run: dart format --output=none --set-exit-if-changed lib test integration_test

- name: Analyze
run: flutter analyze

- name: Test
run: flutter test

- name: Run macOS UI automation
run: tool/test/run_ui_tests.sh

- name: Build macOS release app
run: flutter build macos --release

- name: Prepare unsigned test app
run: |
tool/release/prepare_unsigned_app.sh \
build/macos/Build/Products/Release/Floatick.app

- name: Verify app architectures
shell: bash
run: |
Expand All @@ -60,3 +68,8 @@ jobs:
echo "Expected a universal app, found: $architectures" >&2
exit 1
fi

- name: Smoke-test release app
run: |
tool/release/smoke_test_app.sh \
build/macos/Build/Products/Release/Floatick.app
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,17 @@ sudo xcodebuild -runFirstLaunch
### Verify a change

```bash
dart format --output=none --set-exit-if-changed lib test
dart format --output=none --set-exit-if-changed lib test integration_test
flutter analyze
flutter test
tool/test/run_ui_tests.sh
flutter build macos --release
```

The release app is written to
`build/macos/Build/Products/Release/Floatick.app`.
See the [testing guide](./docs/TESTING.md) for the automated user journeys and
the macOS system boundaries that remain in Draft acceptance.

## Project structure

Expand All @@ -136,6 +139,7 @@ lib/
l10n/ English and Simplified Chinese resources
macos/Runner/ AppKit window shell and Sparkle integration
test/ Repository, ViewModel, and widget tests
integration_test/ Real-engine macOS user journeys
tool/ Icon and release tooling
```

Expand Down
6 changes: 5 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,17 @@ sudo xcodebuild -runFirstLaunch
### 验证改动

```bash
dart format --output=none --set-exit-if-changed lib test
dart format --output=none --set-exit-if-changed lib test integration_test
flutter analyze
flutter test
tool/test/run_ui_tests.sh
flutter build macos --release
```

Release 应用位于
`build/macos/Build/Products/Release/Floatick.app`。
完整自动化用户链路和仍需 Draft 人工验收的 macOS 系统边界见
[测试指南](./docs/TESTING.md)。

## 项目结构

Expand All @@ -129,6 +132,7 @@ lib/
l10n/ 英文与简体中文资源
macos/Runner/ AppKit 窗口外壳与 Sparkle 集成
test/ Repository、ViewModel 和 Widget 测试
integration_test/ 真实 macOS 引擎用户链路
tool/ 图标与发布工具
```

Expand Down
31 changes: 19 additions & 12 deletions docs/DEVELOPMENT_WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ git switch -c feature/short-description
- 修改代码前先确认根因和影响范围。
- 核心逻辑补单元测试;交互变更补 Widget 测试。
- 优先运行与改动直接相关的测试。
- 完整 UI 自动化使用 `tool/test/run_ui_tests.sh`,覆盖真实 macOS Flutter 引擎和
AppKit 原生边界;详细范围见 [TESTING.md](TESTING.md)。
- 本地需要观察 UI 时运行:

```bash
Expand All @@ -81,8 +83,9 @@ PR 必须合入 `main`。PR CI 会执行:
1. Dart 格式检查;
2. `flutter analyze`;
3. `flutter test`;
4. macOS Release 构建;
5. `arm64` 和 `x86_64` 双架构检查。
4. macOS UI 自动化与 AppKit 原生边界测试;
5. macOS Release 构建与首次启动烟测;
6. `arm64` 和 `x86_64` 双架构检查。

CI 通过后才能合并。普通开发不直接推送 `main`。

Expand All @@ -93,25 +96,26 @@ CI 通过后才能合并。普通开发不直接推送 `main`。
从准备发布的 `main` 提交创建发布分支:

```bash
VERSION=X.Y.Z
git fetch origin
git switch main
git pull --ff-only
git switch -c release/0.1.0
git switch -c "release/$VERSION"
```

`pubspec.yaml` 必须包含公开版本和递增的构建号:

```yaml
version: 0.1.0+1
version: X.Y.Z+N
```

### 2. 生成 Draft Release

```bash
git push -u origin release/0.1.0
git push -u origin "release/$VERSION"
```

每次推送 `release/0.1.0` 都会重新运行候选工作流,生成:
每次推送 `release/X.Y.Z` 都会重新运行候选工作流,生成:

- Universal macOS DMG;
- SHA-256 校验文件;
Expand Down Expand Up @@ -144,11 +148,12 @@ merge commit 合并。不要 squash 或 rebase 候选提交。
### 2. 标记经过测试的准确提交

```bash
VERSION=X.Y.Z
git fetch origin
candidate_sha=$(git rev-parse origin/release/0.1.0)
candidate_sha=$(git rev-parse "origin/release/$VERSION")
git merge-base --is-ancestor "$candidate_sha" origin/main
git tag -a v0.1.0 "$candidate_sha" -m "Floatick 0.1.0"
git push origin v0.1.0
git tag -a "v$VERSION" "$candidate_sha" -m "Floatick $VERSION"
git push origin "v$VERSION"
```

标签必须指向 Draft 对应的候选提交,不能指向另一个重新构建的提交。
Expand Down Expand Up @@ -185,8 +190,8 @@ https://lucaslushuo.github.io/floatick/appcast.xml
```

- 首个正式版本发布前,该文件还不存在,候选包会显示“更新服务暂未就绪”。
- 发布 `v0.1.0` 并批准 production 后,工作流会部署签名 appcast。
- `v0.1.0` 需要用户手动下载安装一次
- 发布首个 `vX.Y.Z` 并批准 production 后,工作流会部署签名 appcast。
- 首个带 Sparkle 的正式版本需要用户手动下载安装一次
- 从后续版本开始,旧版本会通过 Sparkle 发现、下载、验证并安装更新。

Sparkle EdDSA 保护更新链路,但不能替代 Apple Developer ID 签名和公证。
Expand All @@ -196,7 +201,9 @@ Sparkle EdDSA 保护更新链路,但不能替代 Apple Developer ID 签名和
生产版本出现紧急问题时,从最新稳定标签创建补丁发布分支:

```bash
git switch -c release/0.1.1 v0.1.0
LATEST_TAG=$(git describe --tags --abbrev=0)
NEXT_VERSION=X.Y.Z
git switch -c "release/$NEXT_VERSION" "$LATEST_TAG"
```

提高公开版本和构建号,然后继续使用同一套:
Expand Down
39 changes: 25 additions & 14 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,35 @@ be used for normal development.
Create a release branch from the commit intended for the release:

```bash
VERSION=X.Y.Z
git fetch origin
git switch main
git pull --ff-only
git switch -c release/0.1.0
git switch -c "release/$VERSION"
```

Set the matching public version and an increasing positive build number:

```yaml
version: 0.1.0+1
version: X.Y.Z+N
```

Then push the branch:

```bash
git push -u origin release/0.1.0
git push -u origin "release/$VERSION"
```

Every push to `release/0.1.0` runs the Release Candidate workflow. It:
Every push to `release/X.Y.Z` runs the Release Candidate workflow. It:

1. validates that the branch name matches `pubspec.yaml`;
2. runs formatting, analysis, and tests;
3. builds the universal release-mode macOS app;
4. creates the DMG, SHA-256 checksum, and build manifest;
5. creates or updates a Draft Release associated with
`candidate/v0.1.0`.
4. normalizes embedded code to one ad-hoc identity for the unsigned candidate;
5. verifies both architectures and launches the app on the Apple silicon runner;
6. creates the DMG, SHA-256 checksum, and build manifest;
7. creates or updates a Draft Release associated with
`candidate/vX.Y.Z`.

Only users with push access can list Draft Releases through the GitHub API.
Because the repository is public, the temporary source tag itself is visible,
Expand Down Expand Up @@ -102,19 +105,20 @@ is rejected and a network failure leaves the installed app usable.

## Promote the accepted candidate

Open a pull request from `release/0.1.0` into `main` and use a merge commit.
Open a pull request from `release/X.Y.Z` into `main` and use a merge commit.
Do not squash or rebase this release pull request: the accepted release-branch
commit must remain reachable from `main` so the tag can identify the exact
binary that was tested.

After the pull request is merged:

```bash
VERSION=X.Y.Z
git fetch origin
candidate_sha=$(git rev-parse origin/release/0.1.0)
candidate_sha=$(git rev-parse "origin/release/$VERSION")
git merge-base --is-ancestor "$candidate_sha" origin/main
git tag -a v0.1.0 "$candidate_sha" -m "Floatick 0.1.0"
git push origin v0.1.0
git tag -a "v$VERSION" "$candidate_sha" -m "Floatick $VERSION"
git push origin "v$VERSION"
```

Pushing the stable tag starts the Release workflow. Its preflight job has no
Expand Down Expand Up @@ -143,8 +147,9 @@ the already published assets.
After a successful release, delete the release branch:

```bash
git push origin --delete release/0.1.0
git branch -d release/0.1.0
VERSION=X.Y.Z
git push origin --delete "release/$VERSION"
git branch -d "release/$VERSION"
```

## Hotfixes
Expand All @@ -153,7 +158,9 @@ For a production-only hotfix, branch from the latest stable tag instead of
including unrelated unreleased work:

```bash
git switch -c release/0.1.1 v0.1.0
LATEST_TAG=$(git describe --tags --abbrev=0)
NEXT_VERSION=X.Y.Z
git switch -c "release/$NEXT_VERSION" "$LATEST_TAG"
```

Apply the fix, increase both the public version and build number, and use the
Expand Down Expand Up @@ -182,6 +189,10 @@ For local layout testing only:

```bash
flutter build macos --release
tool/release/prepare_unsigned_app.sh \
build/macos/Build/Products/Release/Floatick.app
tool/release/smoke_test_app.sh \
build/macos/Build/Products/Release/Floatick.app
tool/release/create_dmg.sh \
build/macos/Build/Products/Release/Floatick.app \
build/release/Floatick-local.dmg \
Expand Down
90 changes: 90 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Floatick 测试指南

Floatick 的自动化测试分为四层。目标不是追求一个模糊的“覆盖率数字”,而是让每一层
验证它最擅长的边界。

```mermaid
flowchart TB
A["单元与 Repository 测试<br/>领域规则、失败回滚、JSON 持久化"]
B["Widget 测试<br/>组件状态、布局与交互分支"]
C["macOS Integration Test<br/>真实 Flutter 引擎、键盘输入与完整用户链路"]
D["原生与 Release 烟测<br/>AppKit 可访问入口、启动和首次工作区"]
A --> B --> C --> D
```

## 一键运行 UI 自动化

```bash
tool/test/run_ui_tests.sh
```

该命令会:

1. 在真实 macOS Flutter 引擎上运行 `integration_test/floatick_ui_test.dart`;
2. 使用临时目录隔离数据,不会读写 `~/.floatick`;
3. 运行 AppKit 原生可访问入口测试。

## 当前自动覆盖

| 场景 | 覆盖层 |
| --- | --- |
| 首次启动生成欢迎 Todo 和 Tags | Integration + Release smoke |
| 创建含 Markdown 内容的 Todo | Integration |
| 双击详情、完成、归档、恢复、搜索 | Integration |
| 退出前后的本地 JSON 持久化 | Integration |
| Tag 创建、Todo 关联、多选 OR 筛选和清空 | Integration |
| Sticky Board 创建、添加现有 Todo、Pin/Unpin | Integration |
| 置顶、登录启动、主题等设置与原生调用边界 | Integration |
| 悬浮图标的 macOS Accessibility button/press contract | XCTest |
| Release 应用启动、独立首次工作区和 JSON 有效性 | Release smoke |

普通单元与 Widget 测试仍使用:

```bash
flutter test
```

只运行真实 macOS 用户链路:

```bash
flutter test integration_test/floatick_ui_test.dart -d macos
```

只运行原生边界:

```bash
xcodebuild test \
-workspace macos/Runner.xcworkspace \
-scheme Runner \
-configuration Debug \
-destination 'platform=macOS' \
-only-testing:RunnerTests \
CODE_SIGNING_ALLOWED=NO \
FLUTTER_TARGET=lib/main.dart
```

## CI

Pull Request CI 会依次执行:

1. 格式与静态检查;
2. 单元和 Widget 测试;
3. macOS UI 自动化与原生边界测试;
4. Universal Release 构建;
5. Release 应用首次启动烟测。

任何一层失败都会阻止合并。

## 必须人工验收的系统边界

Flutter Integration Test 不能操作 macOS 原生系统界面,因此下列行为仍放在 Draft
Release 人工验收中:

- DMG 拖拽安装、Gatekeeper 和“仍要打开”;
- Sparkle 的真实下载、签名验证、替换应用和重启;
- 多显示器上的悬浮图标拖动与展开方向;
- 60/120Hz 动画、滚动和窗口缩放的主观流畅度;
- 真实登录启动以及不同 macOS 版本的窗口层级。

这些项目不是遗漏,而是由操作系统或外部进程控制;自动化负责提前拦截确定性的功能
回归,Draft 验收负责最终用户环境。
Loading