Add snooze support for calendar reminders (toast UI, service, DB) (#825)

* Filter reminder snooze options by default reminder

* Some updates.

* Fixing empty welcome page issue and attendee loading.

* Icon system for notifications and snooze options etc.
This commit is contained in:
Burak Kaan Köse
2026-03-04 00:12:52 +01:00
committed by GitHub
parent e816e87f61
commit 5b3739c6cf
85 changed files with 486 additions and 27 deletions
@@ -0,0 +1,48 @@
using FluentAssertions;
using Wino.Core.Domain;
using Xunit;
namespace Wino.Core.Tests;
public class CalendarReminderSnoozeOptionsTests
{
[Fact]
public void GetAllowedSnoozeMinutes_WhenDefaultIs15AndReminderIs15_Excludes30()
{
var options = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
reminderDurationInSeconds: 15 * 60,
defaultReminderDurationInSeconds: 15 * 60);
options.Should().Equal(5, 10, 15);
}
[Fact]
public void GetAllowedSnoozeMinutes_WhenReminderIs5AndDefaultIs15_DoesNotPassEventStart()
{
var options = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
reminderDurationInSeconds: 5 * 60,
defaultReminderDurationInSeconds: 15 * 60);
options.Should().Equal(5);
}
[Fact]
public void GetAllowedSnoozeMinutes_WhenDefaultReminderIsNone_UsesReminderDurationOnly()
{
var options = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
reminderDurationInSeconds: 30 * 60,
defaultReminderDurationInSeconds: 0);
options.Should().Equal(5, 10, 15, 30);
}
[Fact]
public void GetAllowedSnoozeMinutes_WhenReminderIsUnderFiveMinutes_ReturnsNoOptions()
{
var options = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
reminderDurationInSeconds: 60,
defaultReminderDurationInSeconds: 15 * 60);
options.Should().BeEmpty();
}
}
@@ -68,8 +68,8 @@ public class CalendarReminderServiceTests : IAsyncLifetime
due.Should().HaveCount(1);
due[0].CalendarItem.Id.Should().Be(calendarItem.Id);
due[0].ReminderDurationInSeconds.Should().Be(5 * 60);
due[0].ReminderKey.Should().Be($"{calendarItem.Id:N}:{5 * 60}");
sentReminderKeys.Should().Contain($"{calendarItem.Id:N}:{5 * 60}");
due[0].ReminderKey.Should().StartWith($"{calendarItem.Id:N}:{5 * 60}:");
sentReminderKeys.Should().ContainSingle(k => k.StartsWith($"{calendarItem.Id:N}:{5 * 60}:"));
}
[Fact]
@@ -108,7 +108,7 @@ public class CalendarReminderServiceTests : IAsyncLifetime
firstRun.Should().HaveCount(1);
secondRun.Should().BeEmpty();
sentReminderKeys.Should().Contain($"{calendarItem.Id:N}:{5 * 60}");
sentReminderKeys.Should().ContainSingle(k => k.StartsWith($"{calendarItem.Id:N}:{5 * 60}:"));
}
[Fact]
@@ -189,6 +189,35 @@ public class CalendarReminderServiceTests : IAsyncLifetime
due.Should().BeEmpty();
}
[Fact]
public async Task CheckAndNotifyAsync_WhenItemIsSnoozed_TriggersAtSnoozedTime()
{
var nowLocal = new DateTime(2026, 1, 1, 10, 0, 0);
var lastCheckLocal = nowLocal.AddSeconds(-30);
var calendarItem = await CreateCalendarItemWithReminderAsync(
startDate: nowLocal.AddMinutes(5),
reminderDurationInSeconds: 5 * 60,
reminderType: CalendarItemReminderType.Popup);
await _calendarService.SnoozeCalendarItemAsync(calendarItem.Id, nowLocal.AddMinutes(10));
HashSet<string> sentReminderKeys = [];
var dueAtOriginalTrigger = await _calendarService.CheckAndNotifyAsync(lastCheckLocal, nowLocal, sentReminderKeys);
dueAtOriginalTrigger.Should().BeEmpty();
var snoozeTriggerWindowStart = nowLocal.AddMinutes(10).AddSeconds(-30);
var snoozeTriggerWindowEnd = nowLocal.AddMinutes(10);
var dueAtSnoozeTime = await _calendarService.CheckAndNotifyAsync(snoozeTriggerWindowStart, snoozeTriggerWindowEnd, sentReminderKeys);
dueAtSnoozeTime.Should().HaveCount(1);
dueAtSnoozeTime[0].CalendarItem.Id.Should().Be(calendarItem.Id);
dueAtSnoozeTime[0].ReminderKey.Should().StartWith($"{calendarItem.Id:N}:{5 * 60}:");
}
private async Task<CalendarItem> CreateCalendarItemWithReminderAsync(
DateTime startDate,
long reminderDurationInSeconds,