feat: add fnOS packaging, storage workflows and release pipeline

This commit is contained in:
2026-08-11 18:05:49 +08:00
parent c5922f9b08
commit 95932f0199
181 changed files with 24024 additions and 1164 deletions
+78
View File
@@ -0,0 +1,78 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
const source = (path) => readFile(new URL(`../${path}`, import.meta.url), 'utf8');
test('login decorations cannot intercept touch input', async () => {
const login = await source('src/pages/login/Login.vue');
const loginBox = await source('src/pages/login/LoginBox.vue');
assert.match(login, /&::before[\s\S]*?pointer-events:\s*none/);
assert.match(login, /&::after[\s\S]*?pointer-events:\s*none/);
assert.match(loginBox, /html-type="submit"/);
assert.match(loginBox, /class="login-submit/);
});
test('legacy WebViews do not depend on the :has selector to finish mounting', async () => {
const html = await source('index.html');
const main = await source('src/main.ts');
assert.doesNotMatch(html, /:has\(/);
assert.match(html, /#stepin-app\.app-mounted/);
assert.match(main, /classList\.add\('app-mounted'\)/);
});
test('mobile overlays stay above fixed navigation', async () => {
const nav = await source('src/components/layout/MobileBottomNav.vue');
assert.match(nav, /z-index:\s*900/);
assert.match(nav, /:z-index="1300"/);
assert.match(nav, /destroy-on-close/);
});
test('every primary mobile page provides an explicit title', async () => {
const routes = await source('src/router/routes.ts');
for (const title of ['数据看板', '同步记录', '任务中心', '关注博主', '抖音授权', '系统设置', '系统日志']) {
assert.match(routes, new RegExp(`mobileTitle: '${title}'`));
}
});
test('failed migration record removal is explicit, responsive, and never starts sync automatically', async () => {
const modal = await source('src/components/FailedMigrationRecordRemovalModal.vue');
assert.match(modal, /影响全部历史迁移任务/);
assert.match(modal, /旧本地文件、已上传的 WebDAV 文件/);
assert.match(modal, /下一次正常同步/);
assert.match(modal, /RemoveStorageMigrationFailedRecords/);
assert.doesNotMatch(modal, /StartTaskSync/);
assert.match(modal, /max-height:\s*calc\(100dvh - 190px\)/);
assert.match(modal, /grid-template-columns:\s*1fr 1fr/);
});
test('live monitoring and email notification stay per-blogger and touch friendly', async () => {
const follow = await source('src/pages/followd/index.vue');
const settings = await source('src/pages/set/AppSet.vue');
assert.match(follow, /直播监测/);
assert.match(follow, /开播邮件/);
assert.match(follow, /UpdateFollowLiveMonitor/);
assert.match(follow, /UpdateFollowLiveEmail/);
assert.match(follow, /每 5 分钟自动检查/);
assert.match(follow, /\.live-monitor-row[\s\S]*?min-height:\s*44px/);
assert.match(follow, /\.live-refresh-btn[\s\S]*?width:\s*44px/);
assert.match(follow, /@media \(max-width:\s*768px\)[\s\S]*?\.card-main-content\s*\{[\s\S]*?display:\s*grid/);
assert.match(follow, /\.card-content\s*\{[\s\S]*?display:\s*contents/);
assert.match(follow, /\.live-monitor-row\s*\{[\s\S]*?grid-column:\s*1 \/ -1/);
assert.match(follow, /\.card-path-sync-container\s*\{[\s\S]*?grid-template-columns:\s*minmax\(0, 1fr\) max-content/);
assert.match(follow, /\.sync-switch-wrapper\s*\{[\s\S]*?flex-shrink:\s*0/);
assert.match(settings, /邮箱通知/);
assert.match(settings, /发送测试邮件/);
assert.match(settings, /SSL\/TLS(常用 465/);
assert.match(settings, /STARTTLS(常用 587/);
});
test('storage capability detection is always visible and touch friendly in task center', async () => {
const tasks = await source('src/pages/tasks/index.vue');
assert.match(tasks, /class="storage-probe-action"/);
assert.match(tasks, /summary\.storageHealth\?\.status === 1 \? '重新检测存储' : '检测存储'/);
assert.match(tasks, /CloudSyncOutlined/);
assert.doesNotMatch(tasks, /v-if="summary\.storageHealth\?\.status === 1"[\s\S]{0,500}#action/);
assert.match(tasks, /\.storage-probe-action\s*\{[\s\S]*?grid-column:\s*1 \/ -1/);
assert.match(tasks, /\.task-actions :deep\(\.ant-btn\)\s*\{[\s\S]*?min-height:\s*44px/);
});