mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-04 16:06:41 +00:00
feat(calendar): order calendars by sortOrder and move them from the drawer
Calendars were listed in the order the server returns them (creation order on Stalwart) and sortOrder was never read. Sort them like the webmail now does and as JMAP Calendars asks: lower sortOrder first, equal sortOrder by name (case- and accent-insensitive, numbers by value), each account's calendars kept together. The store keeps the list sorted, so the drawer, the event editor and the other pickers agree. The drawer's long-press panel gains Move up / Move down for the user's own calendars. A move renumbers that account's calendars 1, 2, 3... and writes sortOrder only where it changes; the panel stays open so a calendar can be moved several places. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7108431dbb
commit
3c0af52c4e
@@ -0,0 +1,94 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { compareCalendars, moveCalendar, sortCalendars, sortOrderUpdates } from '../calendar-sort';
|
||||
import type { Calendar } from '../../api/types';
|
||||
|
||||
function cal(overrides: Partial<Calendar>): Calendar {
|
||||
return { id: 'c', name: 'Cal', sortOrder: 0, ...overrides };
|
||||
}
|
||||
|
||||
const names = (list: Calendar[]) => list.map((c) => c.name);
|
||||
const ids = (list: Calendar[]) => list.map((c) => c.id);
|
||||
|
||||
describe('sortCalendars', () => {
|
||||
it('orders by sortOrder, whatever order the server listed them in', () => {
|
||||
const list = [
|
||||
cal({ id: 'b', name: 'Default', sortOrder: 20 }),
|
||||
cal({ id: 'n', name: 'Holidays', sortOrder: 30 }),
|
||||
cal({ id: 'e', name: 'Work', sortOrder: 1 }),
|
||||
cal({ id: 'd', name: 'Personal', sortOrder: 10 }),
|
||||
];
|
||||
expect(names(sortCalendars(list))).toEqual(['Work', 'Personal', 'Default', 'Holidays']);
|
||||
});
|
||||
|
||||
it('sorts equal sortOrder by name, ignoring case and accents, numbers by value', () => {
|
||||
const list = [
|
||||
cal({ id: '1', name: 'zeta' }),
|
||||
cal({ id: '2', name: 'Team 10' }),
|
||||
cal({ id: '3', name: 'Éclair' }),
|
||||
cal({ id: '4', name: 'alpha' }),
|
||||
cal({ id: '5', name: 'Team 2' }),
|
||||
cal({ id: '6', name: 'Beta' }),
|
||||
];
|
||||
expect(names(sortCalendars(list))).toEqual(['alpha', 'Beta', 'Éclair', 'Team 2', 'Team 10', 'zeta']);
|
||||
});
|
||||
|
||||
it('treats a missing sortOrder as 0', () => {
|
||||
const list = [cal({ id: 'a', name: 'B', sortOrder: 1 }), cal({ id: 'b', name: 'A', sortOrder: undefined })];
|
||||
expect(names(sortCalendars(list))).toEqual(['A', 'B']);
|
||||
});
|
||||
|
||||
it('keeps each account together, in the order the accounts came', () => {
|
||||
const list = [
|
||||
cal({ id: 'own-2', name: 'Own 2', sortOrder: 2 }),
|
||||
cal({ id: 'own-1', name: 'Own 1', sortOrder: 1 }),
|
||||
cal({ id: 'x:b', name: 'Shared B', sortOrder: 0, isShared: true, accountId: 'x' }),
|
||||
cal({ id: 'y:a', name: 'Other A', sortOrder: 0, isShared: true, accountId: 'y' }),
|
||||
cal({ id: 'x:a', name: 'Shared A', sortOrder: 5, isShared: true, accountId: 'x' }),
|
||||
];
|
||||
expect(ids(sortCalendars(list))).toEqual(['own-1', 'own-2', 'x:b', 'x:a', 'y:a']);
|
||||
});
|
||||
|
||||
it('does not change the list it is given', () => {
|
||||
const list = [cal({ id: 'b', sortOrder: 2 }), cal({ id: 'a', sortOrder: 1 })];
|
||||
sortCalendars(list);
|
||||
expect(ids(list)).toEqual(['b', 'a']);
|
||||
});
|
||||
|
||||
it('compareCalendars puts the lower sortOrder first', () => {
|
||||
expect(compareCalendars(cal({ sortOrder: 1, name: 'Z' }), cal({ sortOrder: 2, name: 'A' }))).toBeLessThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortOrderUpdates', () => {
|
||||
it('numbers the calendars 1, 2, 3 in the given order and skips unchanged ones', () => {
|
||||
const list = [cal({ id: 'a', sortOrder: 1 }), cal({ id: 'b', sortOrder: 2 }), cal({ id: 'c', sortOrder: 3 })];
|
||||
expect(sortOrderUpdates(list, ['a', 'c', 'b'])).toEqual([
|
||||
{ id: 'c', sortOrder: 2 },
|
||||
{ id: 'b', sortOrder: 3 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('moveCalendar', () => {
|
||||
const list = [
|
||||
cal({ id: 'work', name: 'Work', sortOrder: 1 }),
|
||||
cal({ id: 'home', name: 'Home', sortOrder: 10 }),
|
||||
cal({ id: 'tasks', name: 'Tasks', sortOrder: 15 }),
|
||||
cal({ id: 'holidays', name: 'Holidays', sortOrder: 30 }),
|
||||
cal({ id: 'x:team', name: 'Team', sortOrder: 0, isShared: true, accountId: 'x' }),
|
||||
];
|
||||
|
||||
it('moving up puts the calendar just before the target', () => {
|
||||
expect(moveCalendar(list, 'holidays', 'home')).toEqual(['work', 'holidays', 'home', 'tasks']);
|
||||
});
|
||||
|
||||
it('moving down puts the calendar just after the target', () => {
|
||||
expect(moveCalendar(list, 'work', 'home')).toEqual(['home', 'work', 'tasks', 'holidays']);
|
||||
});
|
||||
|
||||
it('refuses a move across accounts, onto itself or to an unknown id', () => {
|
||||
expect(moveCalendar(list, 'work', 'x:team')).toBeNull();
|
||||
expect(moveCalendar(list, 'work', 'work')).toBeNull();
|
||||
expect(moveCalendar(list, 'work', 'gone')).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user