fix: some
All checks were successful
构建并部署前端到生产环境 / build-and-deploy (push) Successful in 2m16s

This commit is contained in:
luo
2026-10-13 18:02:13 +08:00
parent 97dec4dc65
commit 697ff9c33c
6 changed files with 263 additions and 54 deletions

View File

@@ -1,6 +1,8 @@
// Local-only visual QA: no request reaches the real backend.
import { preview } from 'vite';
let notificationRead = false;
const server = await preview({
configFile: false,
build: { outDir: 'dist/build/h5' },
@@ -19,6 +21,14 @@ const server = await preview({
expiry_reminder: { remaining_days: 0, expires_at: '2026-10-13T16:00:00Z', level: 'red', level_name: '即将到期' }
};
if (req.url.startsWith('/__mock/notifications')) data = { count: 0, items: [], total: 0 };
if (process.env.H5_MOCK_NOTIFICATIONS === '1') {
if (req.url.startsWith('/__mock/notifications/unread-count')) data = { count: notificationRead ? 0 : 1 };
else if (/^\/__mock\/notifications\/42\/read/.test(req.url)) { notificationRead = true; data = { success: true }; }
else if (req.url.startsWith('/__mock/notifications/read-all')) { notificationRead = true; data = { updated_count: 1 }; }
else if (req.url.startsWith('/__mock/notifications')) data = {
items: [{ id: 42, title: '套餐临期提醒', body: '您的套餐即将到期,请及时续费。', category: 'expiry', severity: 'warning', is_read: notificationRead, created_at: '2026-10-13T08:00:00+08:00' }], total: 1
};
}
const conflict = req.url.startsWith('/__mock/auth/bind-phone');
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ code: conflict ? 1192 : 0, msg: conflict ? '该资产已绑定手机号,如需更换请联系管理员' : 'success', data }));

View File

@@ -351,13 +351,43 @@ test('home shows shared reminder once and excludes only expiry from old notifica
};
const asset = (await r.load('store/asset.js')).useAssetStore();
asset.receiveExpiryReminder({ token: 'session-A', identifier: 'A' }, { remaining_days: 0, level: 'red' });
const page = await r.load('pages/index/index.vue', ['visibleExpiryReminder', 'loadUnreadNotifications', 'notificationItems', 'closeExpiryReminder']);
r.shown[0](); await r.tick();
const page = await r.load('pages/index/index.vue', ['visibleExpiryReminder', 'loadUnreadNotifications', 'notificationItems', 'notificationPopupShow', 'closeExpiryReminder', 'onPopupCandidateClose']);
await r.shown[0]();
assert.equal(page.visibleExpiryReminder.value, null);
assert.equal(asset.state.expiryReminder.shown, false);
page.onPopupCandidateClose('empty'); await r.tick();
assert.equal(page.visibleExpiryReminder.value.remaining_days, 0);
await page.loadUnreadNotifications();
assert.deepEqual(plain(page.notificationItems.value).map(item => item.id), [2]);
assert.equal(page.notificationPopupShow.value, false);
page.closeExpiryReminder();
r.hidden[0](); r.shown[0](); await r.tick();
assert.equal(page.notificationPopupShow.value, true);
r.hidden[0](); await r.shown[0](); page.onPopupCandidateClose('empty'); await r.tick();
assert.equal(page.visibleExpiryReminder.value, null);
});
test('expiry popup waits for an open notification and closing it does not mark expiry read', async () => {
const r = runtime();
let markReads = 0;
r.api.assetApi = { getInfo: async () => ({ identifier: 'A' }) };
r.api.notificationApi = {
getList: async () => ({ items: [{ id: 2, category: 'approval', is_read: false }] }),
getUnreadCount: async () => ({ count: 1 }), markRead: async () => { markReads++; }
};
const page = await r.load('pages/index/index.vue', ['visibleExpiryReminder', 'notificationPopupShow', 'onPopupCandidateClose', 'closeNotificationPopup', 'closeExpiryReminder']);
await r.shown[0](); page.onPopupCandidateClose('empty');
await new Promise(resolve => setImmediate(resolve));
assert.equal(page.notificationPopupShow.value, true);
const asset = (await r.load('store/asset.js')).useAssetStore();
asset.receiveExpiryReminder({ token: 'session-A', identifier: 'A' }, { remaining_days: 7, level: 'purple' });
await r.tick();
assert.equal(page.visibleExpiryReminder.value, null);
assert.equal(asset.state.expiryReminder.shown, false);
page.closeNotificationPopup();
assert.equal(page.visibleExpiryReminder.value.remaining_days, 7);
const previousReads = markReads;
page.closeExpiryReminder();
assert.equal(markReads, previousReads);
assert.equal(page.visibleExpiryReminder.value, null);
});
@@ -378,6 +408,26 @@ test('notification center refreshes server scope after read-all; failures keep u
}
});
test('clicking a notification card updates its read badge only after successful mark-read', async () => {
for (const fail of [false, true]) {
const r = runtime();
let marked = false;
r.api.notificationApi = {
getUnreadCount: async () => ({ count: marked ? 0 : 1 }),
markRead: async id => { assert.equal(id, 42); if (fail) throw new Error('offline'); marked = true; }
};
const page = await r.load('pages/notifications/notifications.vue', ['notifications', 'markItemRead', 'unreadCount']);
page.notifications.push({ id: 42, is_read: false, title: '套餐提醒' });
page.unreadCount.value = 1;
await page.markItemRead(page.notifications[0]);
assert.equal(page.notifications[0].is_read, !fail);
assert.equal(page.unreadCount.value, fail ? 1 : 0);
}
const source = readFileSync(resolve(root, 'pages/notifications/notifications.vue'), 'utf8');
assert.match(source, /item\.is_read \? '已读' : '未读'/);
assert.doesNotMatch(source, /border-left|unread-dot/);
});
test('notification center rejects an old asset response after switching context', async () => {
const r = runtime();
const oldList = deferred();