CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
Room context menu → "Add to Section" submenu: every u.<name> tag in use across your rooms as a checkable item, plus a "New section…" field (validated: non-empty, ≤ 40 chars, no dots, unique). Toggling writes or deletes the standard u.<name> room tag (order 0.5), so sections sync across devices and other clients see the same tags. The menu row reads "Sections: Raids, Off-topic" once a room is in any. Home renders each section as a collapsible category between Favorites and Rooms (alphabetical; members by tag order then name; the same closed-state store and unread-only-when-collapsed behaviour as the built-in categories). A sectioned room leaves the plain Rooms list but keeps a Favorite / Low Priority placement. Empty sections don't exist by construction; rename is retag (v2). Derivation in utils/roomSections.ts with unit tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
46 lines
1.8 KiB
TypeScript
46 lines
1.8 KiB
TypeScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { deriveRoomSections, sectionTag, validateSectionName } from './roomSections';
|
|
|
|
describe('deriveRoomSections', () => {
|
|
it('groups by u.* tags, orders members by order then name, sections by name', () => {
|
|
const { sections, sectioned } = deriveRoomSections([
|
|
{ roomId: '!a', name: 'Alpha', tags: { 'u.Raids': { order: 0.7 } } },
|
|
{ roomId: '!b', name: 'Beta', tags: { 'u.Raids': { order: 0.2 }, 'm.favourite': {} } },
|
|
{ roomId: '!c', name: 'Gamma', tags: { 'u.Raids': {}, 'u.off-topic': { order: '0.1' } } },
|
|
{ roomId: '!d', name: 'Delta', tags: { 'm.lowpriority': {} } },
|
|
{ roomId: '!e', name: 'Eps', tags: undefined },
|
|
]);
|
|
assert.deepEqual(
|
|
sections.map((s) => [s.name, s.tag, s.rooms]),
|
|
[
|
|
['off-topic', 'u.off-topic', ['!c']],
|
|
['Raids', 'u.Raids', ['!b', '!a', '!c']],
|
|
],
|
|
);
|
|
assert.deepEqual([...sectioned].sort(), ['!a', '!b', '!c']);
|
|
});
|
|
|
|
it('has no empty sections', () => {
|
|
assert.deepEqual(deriveRoomSections([{ roomId: '!a', name: 'A', tags: {} }]).sections, []);
|
|
});
|
|
|
|
it('ignores m.* and malformed u. tags', () => {
|
|
const { sections } = deriveRoomSections([
|
|
{ roomId: '!a', name: 'A', tags: { 'm.favourite': {}, 'u.': {}, 'x.y': {} } },
|
|
]);
|
|
assert.deepEqual(sections, []);
|
|
});
|
|
});
|
|
|
|
describe('section names', () => {
|
|
it('builds the tag and validates input', () => {
|
|
assert.equal(sectionTag(' Raids '), 'u.Raids');
|
|
assert.equal(validateSectionName('Raids', []), undefined);
|
|
assert.ok(validateSectionName('', []));
|
|
assert.ok(validateSectionName('raids', ['Raids']));
|
|
assert.ok(validateSectionName('a.b', []));
|
|
assert.ok(validateSectionName('x'.repeat(41), []));
|
|
});
|
|
});
|