feat(viewer): MSK datetime + done-cutoff expander [v0.3.0]
Closes 2 more UX-round1 fixes (5/6 done; task-numbers remains design-first cross-repo, awaiting user decision). ux-full-datetime: formatMskDateTime(iso) → "YYYY-MM-DD HH:MM МСК". UTC+3 fixed (no-DST since 2011). Tooltip retains full ISO for copy-paste. Relative age stays in card-meta. Mirrored in board.js renderCard re-render path. ux-done-cutoff: source unchanged (render-side cutoff). Done column sorts by last_commit_iso desc, first 10 visible, rest get data-overflow="true" (CSS hides). Column bottom gets "show N more" expander; click toggles col.show-overflow class + button text "collapse". Non-done columns untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -225,6 +225,77 @@ describe('renderBoard', () => {
|
||||
expect(html).not.toMatch(/<span class="badge owner"><\/span>/);
|
||||
});
|
||||
|
||||
test('done column: shows only the 10 most recent by last_commit_iso, others get data-overflow', () => {
|
||||
// 12 done records, distinct iso descending from 2026-05-22 backwards
|
||||
const records = Array.from({ length: 12 }, (_, i) =>
|
||||
rec({
|
||||
slug: `done-${String(i).padStart(2, '0')}`,
|
||||
status: 'done',
|
||||
status_emoji: '🟢',
|
||||
last_commit_iso: `2026-05-${String(22 - i).padStart(2, '0')}T12:00:00Z`,
|
||||
}),
|
||||
);
|
||||
const html = renderBoard(records, { generatedAt: now });
|
||||
|
||||
// most-recent 10 (done-00..done-09) visible, last 2 (done-10, done-11) overflow
|
||||
const overflowSlugs = [...html.matchAll(
|
||||
/data-slug="(done-\d+)"[^>]*data-overflow="true"/g,
|
||||
)].map((m) => m[1]);
|
||||
expect(overflowSlugs.sort()).toEqual(['done-10', 'done-11']);
|
||||
|
||||
// first 10 don't carry data-overflow="true"
|
||||
for (let i = 0; i < 10; i++) {
|
||||
expect(html).toMatch(
|
||||
new RegExp(`data-slug="done-${String(i).padStart(2, '0')}"[^>]*data-overflow="false"`),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('done column with overflow renders a "show N more" expander button', () => {
|
||||
const records = Array.from({ length: 12 }, (_, i) =>
|
||||
rec({
|
||||
slug: `d-${i}`,
|
||||
status: 'done',
|
||||
status_emoji: '🟢',
|
||||
last_commit_iso: `2026-05-${String(22 - i).padStart(2, '0')}T12:00:00Z`,
|
||||
}),
|
||||
);
|
||||
const html = renderBoard(records, { generatedAt: now });
|
||||
|
||||
expect(html).toMatch(
|
||||
/<button[^>]*class="done-expander"[^>]*>[^<]*show 2 more[^<]*<\/button>/,
|
||||
);
|
||||
});
|
||||
|
||||
test('done column ≤10 records: no expander button rendered', () => {
|
||||
const records = Array.from({ length: 8 }, (_, i) =>
|
||||
rec({
|
||||
slug: `d-${i}`,
|
||||
status: 'done',
|
||||
status_emoji: '🟢',
|
||||
last_commit_iso: `2026-05-${String(22 - i).padStart(2, '0')}T12:00:00Z`,
|
||||
}),
|
||||
);
|
||||
const html = renderBoard(records, { generatedAt: now });
|
||||
|
||||
expect(html).not.toContain('done-expander');
|
||||
});
|
||||
|
||||
test('non-done columns are NOT subject to the cutoff (no overflow attr, no expander)', () => {
|
||||
const records = Array.from({ length: 12 }, (_, i) =>
|
||||
rec({
|
||||
slug: `open-${i}`,
|
||||
status: 'open',
|
||||
status_emoji: '⚪',
|
||||
last_commit_iso: `2026-05-${String(22 - i).padStart(2, '0')}T12:00:00Z`,
|
||||
}),
|
||||
);
|
||||
const html = renderBoard(records, { generatedAt: now });
|
||||
|
||||
expect(html).not.toMatch(/data-overflow="true"/);
|
||||
expect(html).not.toContain('done-expander');
|
||||
});
|
||||
|
||||
test('renders slug as a visible <code class="card-slug"> on every card', () => {
|
||||
const html = renderBoard(
|
||||
[rec({ slug: 'board-viewer-pointers', title: 'pointers' })],
|
||||
@@ -234,13 +305,26 @@ describe('renderBoard', () => {
|
||||
expect(html).toMatch(/<code class="card-slug">board-viewer-pointers<\/code>/);
|
||||
});
|
||||
|
||||
test('renders last_commit_iso as a short YYYY-MM-DD date span', () => {
|
||||
test('renders last_commit_iso as YYYY-MM-DD HH:MM МСК (Moscow time, UTC+3, no DST)', () => {
|
||||
const html = renderBoard(
|
||||
[rec({ slug: 'x', last_commit_iso: '2026-05-19T08:30:00Z' })],
|
||||
{ generatedAt: now },
|
||||
);
|
||||
|
||||
expect(html).toMatch(/<span class="date"[^>]*>2026-05-19<\/span>/);
|
||||
// 08:30 UTC + 3 = 11:30 МСК
|
||||
expect(html).toMatch(/<span class="date"[^>]*>2026-05-19 11:30 МСК<\/span>/);
|
||||
// tooltip retains full ISO for copy-paste
|
||||
expect(html).toContain('title="2026-05-19T08:30:00Z"');
|
||||
});
|
||||
|
||||
test('formats Moscow time correctly across midnight UTC rollover', () => {
|
||||
const html = renderBoard(
|
||||
[rec({ slug: 'x', last_commit_iso: '2026-05-19T22:00:00Z' })],
|
||||
{ generatedAt: now },
|
||||
);
|
||||
|
||||
// 22:00 UTC + 3 = 01:00 МСК next day
|
||||
expect(html).toMatch(/2026-05-20 01:00 МСК/);
|
||||
});
|
||||
|
||||
test('omits date span when last_commit_iso is null', () => {
|
||||
|
||||
Reference in New Issue
Block a user