fix(admin): keep invite filters inside the list card

Unify status filters, search, bulk actions and pagination in one invite list card. Remove the standalone filter summary and preserve usable search widths on small screens, with structural regression coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-30 19:10:13 +08:00
1 parent a6776ffe0a
commit 5732be847e
3 files changed
+60 -21

No files matched your search

@@ -40,6 +40,43 @@ function hasBooleanAttr(tag, name) {
assert.match(tag, new RegExp(`(?:\\s|^)${name}(?:\\s|>|$)`));
}
test('filters, selection, table and pagination share one list card', () => {
const { html } = harness();
const cards = [...html.matchAll(/<section\b[^>]*class="[^"]*\baksrt-native-card\b[^"]*"[^>]*>[\s\S]*?<\/section>/g)];
assert.equal(cards.length, 1);
const card = cards[0][0];
assert.match(card, /aksrt-inv-list-card/);
assert.match(card, /<h2 class="aksrt-native-card__title">邀请码列表<\/h2>/);
for (const marker of ['class="subsubsub"', 'class="aksrt-native-filters"', '清除筛选', 'class="aksrt-inv-bulk"', 'data-aksrt-inv-selection', 'aksrt-inv-table', 'class="tablenav bottom"']) {
assert.ok(card.includes(marker), `${marker} belongs to the list card`);
}
assert.doesNotMatch(html, /aksrt-inv-filter-summary|关键词:/);
assert.doesNotMatch(read('assets', 'css', 'admin', 'invite-codes.css'), /aksrt-inv-filter-summary/);
});
test('search and bulk actions remain independent sibling forms inside the list card', () => {
const { html } = harness();
let depth = 0;
let count = 0;
for (const [tag] of html.matchAll(/<\/?form\b[^>]*>/g)) {
if (tag.startsWith('</')) {
depth--;
} else {
count++;
depth++;
}
assert.ok(depth >= 0 && depth <= 1, 'forms must never nest');
}
assert.equal(depth, 0);
assert.equal(count, 4);
const searchEnd = html.indexOf('</form>', html.indexOf('class="aksrt-native-filters"'));
const listStart = html.lastIndexOf('<form', html.indexOf('data-aksrt-inv-list'));
assert.ok(searchEnd < listStart);
const listEnd = html.indexOf('</form>', listStart);
assert.ok(html.indexOf('class="tablenav bottom"', listStart) < listEnd);
assert.ok(listEnd < html.indexOf('</section>', listStart));
});
test('page renders add and export dialogs beside, not inside, the list form', () => {
const { html } = harness();
const listMarker = html.indexOf('data-aksrt-inv-list');