mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
fix: leave read-only calendars out of the task editor's calendar list
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { TaskModal } from '../task-modal';
|
||||
import type { Calendar, CalendarTask } from '@/lib/jmap/types';
|
||||
|
||||
// The task editor's Calendar select offered the read-only Birthdays
|
||||
// calendar, which the event editor leaves out.
|
||||
|
||||
const writable = { mayReadItems: true, mayWriteAll: true, mayWriteOwn: true, mayDelete: true };
|
||||
const readOnly = { mayReadItems: true, mayWriteAll: false, mayWriteOwn: false, mayDelete: false };
|
||||
const calendars = [
|
||||
{ id: 'work', name: 'Work', isShared: false, myRights: writable },
|
||||
{ id: 'personal', name: 'Personal', isShared: false, myRights: writable },
|
||||
{ id: 'birthdays', name: 'Birthdays', isShared: false, myRights: readOnly },
|
||||
{ id: 'feed', name: 'Holidays feed', isShared: false, myRights: writable },
|
||||
] as unknown as Calendar[];
|
||||
const isSubscriptionCalendar = (id: string) => id === 'feed';
|
||||
|
||||
describe('task editor calendar select', () => {
|
||||
it('lists only the calendars a task can go into', () => {
|
||||
render(<TaskModal calendars={calendars} isSubscriptionCalendar={isSubscriptionCalendar} onSave={vi.fn()} onClose={vi.fn()} />);
|
||||
const select = screen.getByLabelText('tasks.calendar');
|
||||
const names = within(select).getAllByRole('option').map((o) => o.textContent);
|
||||
expect(names).toEqual(['Work', 'Personal']);
|
||||
});
|
||||
|
||||
it('keeps the calendar of a task that is already in a read-only one', () => {
|
||||
const task = {
|
||||
id: 't1', '@type': 'Task', title: 'Card for Ann', calendarIds: { birthdays: true },
|
||||
progress: 'needs-action', priority: 0, due: null, showWithoutTime: true, alerts: null,
|
||||
} as unknown as CalendarTask;
|
||||
render(<TaskModal task={task} calendars={calendars} onSave={vi.fn()} onClose={vi.fn()} />);
|
||||
const select = screen.getByLabelText('tasks.calendar') as HTMLSelectElement;
|
||||
expect(select.value).toBe('birthdays');
|
||||
});
|
||||
});
|
||||
@@ -2219,6 +2219,7 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps =
|
||||
key={editTask?.id ?? 'new-task'}
|
||||
task={editTask}
|
||||
calendars={displayCalendars}
|
||||
isSubscriptionCalendar={isSubscriptionCalendar}
|
||||
onSave={handleSaveTask}
|
||||
onDelete={handleDeleteTask}
|
||||
onDirtyChange={setTaskEditorDirty}
|
||||
@@ -2380,6 +2381,7 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps =
|
||||
key={editTask?.id ?? 'new-task'}
|
||||
task={editTask}
|
||||
calendars={displayCalendars}
|
||||
isSubscriptionCalendar={isSubscriptionCalendar}
|
||||
onSave={handleSaveTask}
|
||||
onDelete={handleDeleteTask}
|
||||
onDirtyChange={setTaskEditorDirty}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PluginSlot } from "@/components/plugins/plugin-slot";
|
||||
import { taskToView } from "@/lib/plugin-projection";
|
||||
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||
import { useConfirmDialog } from "@/hooks/use-confirm-dialog";
|
||||
import { canCreateEventsIn } from "@/lib/calendar-editability";
|
||||
|
||||
interface TaskModalProps {
|
||||
task?: CalendarTask | null;
|
||||
@@ -25,6 +26,8 @@ interface TaskModalProps {
|
||||
/** Told whether the form holds changes that aren't saved, so the page can ask before it drops them. */
|
||||
onDirtyChange?: (dirty: boolean) => void;
|
||||
isMobile?: boolean;
|
||||
/** Subscribed feeds are read-only: no tasks go there. */
|
||||
isSubscriptionCalendar?: (calendarId: string) => boolean;
|
||||
}
|
||||
|
||||
type PriorityLevel = "none" | "high" | "medium" | "low";
|
||||
@@ -54,6 +57,7 @@ export function TaskModal({
|
||||
onClose,
|
||||
onDirtyChange,
|
||||
isMobile: _isMobile,
|
||||
isSubscriptionCalendar,
|
||||
}: TaskModalProps) {
|
||||
const t = useTranslations("calendar");
|
||||
const tCommon = useTranslations("common");
|
||||
@@ -63,7 +67,9 @@ export function TaskModal({
|
||||
// task-detail-section: the saved task, for an existing task only.
|
||||
const pluginTaskProps = useMemo(() => (task ? { task: taskToView(task) } : null), [task]);
|
||||
|
||||
const writableCalendars = calendars.filter(c => !c.isShared || c.myRights?.mayWriteAll || c.myRights?.mayWriteOwn);
|
||||
// The calendars a task can go into: not read-only ones (Birthdays,
|
||||
// feeds), the same rule as the event editor.
|
||||
const writableCalendars = calendars.filter((c) => canCreateEventsIn(c, isSubscriptionCalendar));
|
||||
const defaultCalendarId = writableCalendars[0]?.id ?? calendars[0]?.id ?? "";
|
||||
|
||||
const [title, setTitle] = useState(task?.title ?? "");
|
||||
@@ -80,6 +86,8 @@ export function TaskModal({
|
||||
}
|
||||
return defaultCalendarId;
|
||||
});
|
||||
// A task already in a read-only calendar keeps it listed.
|
||||
const calendarOptions = calendars.filter((c) => c.id === calendarId || writableCalendars.includes(c));
|
||||
const [alertOption, setAlertOption] = useState<AlertOption>(() => {
|
||||
if (!task?.alerts) return "none";
|
||||
const first = Object.values(task.alerts)[0];
|
||||
@@ -313,7 +321,7 @@ export function TaskModal({
|
||||
</div>
|
||||
|
||||
{/* Calendar */}
|
||||
{writableCalendars.length > 1 && (
|
||||
{calendarOptions.length > 1 && (
|
||||
<div className="space-y-2">
|
||||
<label htmlFor={`${id}-calendar`} className="text-xs font-medium text-muted-foreground">
|
||||
{t("tasks.calendar")}
|
||||
@@ -324,7 +332,7 @@ export function TaskModal({
|
||||
onChange={(e) => setCalendarId(e.target.value)}
|
||||
className="rounded-md border border-input bg-background px-3 py-1.5 text-sm w-full"
|
||||
>
|
||||
{writableCalendars.map((cal) => (
|
||||
{calendarOptions.map((cal) => (
|
||||
<option key={cal.id} value={cal.id}>{cal.name}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
Reference in New Issue
Block a user