using MeetingAssistant.LaunchProfiles; using MeetingAssistant.Recording; using MeetingAssistant.Taskbar; using System.Drawing; using System.Runtime.Versioning; namespace MeetingAssistant.Tests; public sealed class TaskbarIconTests { [Fact] public void IdleMenuOffersStartForEachConfiguredProfile() { var menu = MeetingTaskbarMenuBuilder.Build( Status(), [Profile("default", "Ctrl+Alt+M"), Profile("english", "Ctrl+Alt+E")]); Assert.Equal(RecordingProcessState.Idle, menu.State); Assert.Contains(menu.Items, item => item.Action == MeetingTaskbarAction.EditRules && item.Text == "Settings and logs"); Assert.Contains(menu.Items, item => item.Action == MeetingTaskbarAction.StartRecording && item.ProfileName == "default" && item.Text == "Start meeting recording (default)\tCtrl+Alt+M"); Assert.Contains(menu.Items, item => item.Action == MeetingTaskbarAction.StartRecording && item.ProfileName == "english" && item.Text == "Start meeting recording (english)\tCtrl+Alt+E"); Assert.DoesNotContain(menu.Items, item => item.Action == MeetingTaskbarAction.StopRecording); Assert.DoesNotContain(menu.Items, item => item.Action == MeetingTaskbarAction.AbortRecording); } [Fact] public void RecordingMenuOffersStopAbortAndOtherProfileSwitches() { var menu = MeetingTaskbarMenuBuilder.Build( Status(isRecording: true, state: RecordingProcessState.Recording, profile: "default"), [Profile("default", "Ctrl+Alt+M"), Profile("english", "Ctrl+Alt+E"), Profile("french", "Ctrl+Alt+F")]); Assert.Equal(RecordingProcessState.Recording, menu.State); Assert.Contains(menu.Items, item => item.Action == MeetingTaskbarAction.StopRecording); Assert.Contains(menu.Items, item => item.Action == MeetingTaskbarAction.AbortRecording); Assert.Contains(menu.Items, item => item.Action == MeetingTaskbarAction.SwitchProfile && item.ProfileName == "english" && item.Text == "Switch to english\tCtrl+Alt+E"); Assert.Contains(menu.Items, item => item.Action == MeetingTaskbarAction.SwitchProfile && item.ProfileName == "french" && item.Text == "Switch to french\tCtrl+Alt+F"); Assert.DoesNotContain(menu.Items, item => item.Action == MeetingTaskbarAction.SwitchProfile && item.ProfileName == "default"); Assert.DoesNotContain(menu.Items, item => item.Action == MeetingTaskbarAction.StartRecording); } [Fact] public void ProcessingMenuShowsSummarizingButAllowsStartingNewRecordings() { var menu = MeetingTaskbarMenuBuilder.Build( Status(state: RecordingProcessState.Summarizing, profile: "default"), [Profile("default"), Profile("english")]); Assert.Equal(RecordingProcessState.Summarizing, menu.State); Assert.Contains(menu.Items, item => item.Action == MeetingTaskbarAction.StartRecording && item.ProfileName == "default"); Assert.Contains(menu.Items, item => item.Action == MeetingTaskbarAction.StartRecording && item.ProfileName == "english"); Assert.DoesNotContain(menu.Items, item => item.Action == MeetingTaskbarAction.StopRecording); Assert.DoesNotContain(menu.Items, item => item.Action == MeetingTaskbarAction.AbortRecording); } [Fact] public void RecordingStateWinsOverOlderSummarizingWork() { var menu = MeetingTaskbarMenuBuilder.Build( Status(isRecording: true, state: RecordingProcessState.Recording, profile: "english"), [Profile("default"), Profile("english")]); Assert.Equal(RecordingProcessState.Recording, menu.State); } [Fact] [SupportedOSPlatform("windows")] public void TaskbarIconGlyphsAreVisuallyCentered() { foreach (var state in new[] { RecordingProcessState.Idle, RecordingProcessState.Recording, RecordingProcessState.Summarizing }) { using var bitmap = TaskbarIconRenderer.RenderBitmap(state); var bounds = GetVisibleGlyphBounds(bitmap); Assert.True( IsCentered(GetCenter(bounds.Left, bounds.Right)), $"{state} glyph is horizontally off-center: {bounds}"); Assert.True( IsCentered(GetCenter(bounds.Top, bounds.Bottom)), $"{state} glyph is vertically off-center: {bounds}"); } } private static LaunchProfile Profile(string name, string hotkey = "") { return new LaunchProfile(name, new MeetingAssistantOptions { Hotkey = { Toggle = hotkey } }); } private static RecordingStatus Status( bool isRecording = false, RecordingProcessState state = RecordingProcessState.Idle, string? profile = null) { return new RecordingStatus( isRecording, state == RecordingProcessState.Idle ? null : "transcript.md", state == RecordingProcessState.Idle ? null : "meeting.md", state == RecordingProcessState.Idle ? null : "context.md", state == RecordingProcessState.Idle ? null : "summary.md", state, profile); } [SupportedOSPlatform("windows")] private static Rectangle GetVisibleGlyphBounds(Bitmap bitmap) { var left = bitmap.Width; var top = bitmap.Height; var right = -1; var bottom = -1; for (var y = 0; y < bitmap.Height; y++) { for (var x = 0; x < bitmap.Width; x++) { var pixel = bitmap.GetPixel(x, y); if (pixel.A < 32 || pixel.GetBrightness() < 0.58f) { continue; } left = Math.Min(left, x); top = Math.Min(top, y); right = Math.Max(right, x); bottom = Math.Max(bottom, y); } } Assert.True(right >= left && bottom >= top, "Expected the icon to contain a visible white glyph."); return Rectangle.FromLTRB(left, top, right, bottom); } private static double GetCenter(int start, int end) { return (start + end) / 2.0; } private static bool IsCentered(double center) { return center is >= 7.25 and <= 8.75; } }