设置弹窗的固定高度只用 dvh 写、没有回退。不认识该单位的引擎会把整条 height 与 max-height 作废,盒子退回按内容撑开,于是切分区就跳大小—— 这正是 BUG-554 现象的复发,而 BUG-554 的防复发「必须同时声明 width 与 height」只检查声明存不存在,挡不住「写了但没生效」。 实测(Chrome 151,真实产物 CSS + 复刻 DOM,1440×900):dvh 正常时四个 分区恒定 866.80×640px,**事故不复现**;摘掉 dvh 后变成 313/313/378/1130, 宽度不动——与用户描述的形状完全一致。因此机制已证实,但用户当时的浏览器 未定位,BUG-698 记为 investigating 而非 resolved。 附带发现:任务书要求照抄的重复声明式回退 `height: 100vh; height: 100dvh;` 在本仓根本发布不出去——Lightning CSS 会合并同名属性的重复声明只留最后一条, 全仓唯一那处回退(sidebar-provider)在线上早就是死的,还有一条测试专门守着 这个从未发布过的写法。改用 @supports (height: 1dvh):vh 作基线,dvh 作升级。 修复后不支持 dvh 的引擎也收敛到恒定 640px,支持的逐像素无变化。 同轮按产品决策去掉设置分区菜单的左侧/下方强调色条,选中与悬停改用面与 墨色等级区分,不用色相、不用字重。左侧会话列表的色条本轮不动。 - 新增 viewport-unit-fallback-contract(3 条,全文件),三次破坏性验证各自打红 - account-dialog-overlay 新增同尺寸契约与分区菜单契约 - 三条钉死旧 dvh 字面量的既有断言按「原值/新值/原因」更新,均未弱化 - tsc 0 错;lint 0 error / 118 warning(持平);npm test 3346/3300/fail 31, 失败清单与基线逐字相同;/ 仍 ○ Static;样式 gzip +0.38%; 快速门 pytest 段 792 passed / 1 skipped / 0 failed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
73 lines
4.5 KiB
TypeScript
73 lines
4.5 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
const root = new URL("../", import.meta.url);
|
|
const route = readFileSync(new URL("src/app/api/admin/payments/route.ts", root), "utf8");
|
|
const packagesRoute = readFileSync(new URL("src/app/api/admin/packages/route.ts", root), "utf8");
|
|
const paymentPage = readFileSync(new URL("src/app/admin/payments/page.tsx", root), "utf8");
|
|
const packagesPage = readFileSync(new URL("src/app/admin/packages/page.tsx", root), "utf8");
|
|
const paymentManagement = readFileSync(new URL("src/components/admin/payment-management.tsx", root), "utf8");
|
|
const adminApp = readFileSync(new URL("src/components/admin/admin-app.tsx", root), "utf8");
|
|
const globalsCss = readFileSync(new URL("src/app/globals.css", root), "utf8");
|
|
|
|
test("支付后台接口使用 self-hosted PostgreSQL 联表且不依赖 Supabase builder", () => {
|
|
assert.match(route, /requirePermission\("billing\.orders\.read"\)/);
|
|
assert.match(route, /queryAdminRows<PaymentOrderRow>/);
|
|
assert.match(route, /from public\.payment_orders o/);
|
|
assert.match(route, /left join public\.payment_packages p on p\.id = o\.package_id/);
|
|
assert.match(route, /left join identity\.users u on u\.id = o\.user_id/);
|
|
assert.match(route, /count\(\*\) over\(\)::text as total_count/);
|
|
assert.match(route, /order by o\.created_at desc, o\.order_no asc/);
|
|
assert.doesNotMatch(route, /createAdminSupabaseClient|auth\.admin|\.from\(|\.range\(|count: "exact"/);
|
|
assert.doesNotMatch(route, /userId|user_id:|raw_notify_payload|SUPABASE_SERVICE_ROLE_KEY/);
|
|
});
|
|
|
|
test("支付接口使用参数化筛选、独立日期统计与 ISO 日期输出", () => {
|
|
for (const field of ["status", "from", "to", "limit", "offset"]) assert.match(route, new RegExp(field));
|
|
for (const field of ["totalOrders", "paidOrders", "pendingOrders", "failedExpiredOrders", "paidAmountCents", "grantedCredits"]) assert.match(route, new RegExp(field));
|
|
assert.match(route, /max\(100\)/);
|
|
assert.match(route, /\$\$\{values\.length\}/);
|
|
assert.match(route, /statsValues/);
|
|
assert.match(route, /created_at\.toISOString\(\)/);
|
|
assert.match(route, /paid_at\?\.toISOString\(\) \?\? null/);
|
|
assert.match(route, /支付记录服务暂时不可用/);
|
|
assert.match(route, /hasMore/);
|
|
});
|
|
|
|
test("支付管理页只保留概览、默认折叠的 Z-Pay 配置与支付记录", () => {
|
|
assert.match(paymentPage, /PaymentManagement/);
|
|
for (const component of ["List", "Card", "Collapse", "Table", "Form", "Tag", "Alert", "Statistic"]) assert.match(paymentManagement, new RegExp(`\\b${component}\\b`));
|
|
assert.match(paymentManagement, /支付概览/);
|
|
assert.match(paymentManagement, /<Collapse[\s\S]*defaultActiveKey=\{\[\]\}[\s\S]*label: "Z-Pay(易支付)渠道配置"/);
|
|
assert.match(paymentManagement, /children: \([\s\S]*测试可用性(当前已保存配置)[\s\S]*保存配置[\s\S]*<Form<EpaySettingsForm>/);
|
|
assert.match(paymentManagement, /支付记录/);
|
|
assert.match(paymentManagement, /\/api\/admin\/payments/);
|
|
assert.doesNotMatch(paymentManagement, /套餐列表|套餐设置|添加套餐|\/api\/admin\/packages|PackageManagement/);
|
|
});
|
|
|
|
test("统一商品资源进入导航且旧套餐 API 明确跳转", () => {
|
|
assert.match(adminApp, /name: "products", list: "\/admin\/products"[\s\S]*name: "subscriptions"/);
|
|
assert.match(adminApp, /ShoppingOutlined/);
|
|
assert.match(packagesPage, /PackageManagement/);
|
|
assert.match(packagesRoute, /NextResponse\.redirect\(new URL\(replacement, request\.url\), 308\)/);
|
|
});
|
|
|
|
test("旧套餐写 API 已删除,不再假成功写脱节表", () => {
|
|
assert.doesNotMatch(packagesRoute, /payment_packages|queryAdminRows|insert into|update public/);
|
|
for (const method of ["POST", "PATCH", "DELETE"]) {
|
|
assert.match(packagesRoute, new RegExp(`export function ${method}\\(\\)`));
|
|
}
|
|
assert.match(packagesRoute, /status: 410/);
|
|
assert.match(packagesRoute, /replacement/);
|
|
});
|
|
|
|
test("后台使用独立的全视口纵向滚动容器而不修改全局聊天溢出边界", () => {
|
|
assert.match(adminApp, /<div className="admin-app-shell">[\s\S]*<ConfigProvider/);
|
|
assert.match(globalsCss, /html, body \{[^}]*overflow: hidden;/);
|
|
// BUG-698: vh base, dvh upgrade behind a feature query (see viewport-unit-fallback-contract).
|
|
assert.match(globalsCss, /\.admin-app-shell \{ height: 100vh; min-height: 0; overflow-y: auto; \}/);
|
|
assert.match(globalsCss, /@supports \(height: 1dvh\)[\s\S]*?\.admin-app-shell \{ height: 100dvh; \}/);
|
|
assert.match(globalsCss, /\.admin-app-shell > \*, \.admin-app-shell \.ant-layout \{ min-height: 100%; \}/);
|
|
});
|