diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/GameCreationWizard.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/GameCreationWizard.cs index d13fee5..dc870f1 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/GameCreationWizard.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/GameCreationWizard.cs @@ -259,38 +259,38 @@ public sealed class GameCreationWizard private static (string?, string?) ApplyTypeChoice(WizardPayload p, string choice) => choice switch { "single" => (WizardStepNames.Title, SetType(p, WizardCreationType.Single)), - "pool" => (WizardStepNames.Title, SetType(p, WizardCreationType.Pool)), + "pool" => (WizardStepNames.Title, SetType(p, WizardCreationType.Pool)), _ => (null, "Неизвестный выбор"), }; private static (string?, string?) ApplySystemChoice(WizardPayload p, string choice) => choice switch { "_other" => (WizardStepNames.System, null), // stay, await text - "_skip" => (NextAfterSystem(p), SetSystem(p, null)), + "_skip" => (NextAfterSystem(p), SetSystem(p, null)), { } code => (WizardStepNames.Duration, SetSystem(p, code)), }; private static (string?, string?) ApplyDurationChoice(WizardPayload p, string choice) => choice switch { "_other" => (WizardStepNames.Duration, null), - "_skip" => (NextAfterDuration(p), SetDurationMinutes(p, null)), - { } d => int.TryParse(d, out var min) + "_skip" => (NextAfterDuration(p), SetDurationMinutes(p, null)), + { } d => int.TryParse(d, out var min) ? (NextAfterDuration(p), SetDurationMinutes(p, min)) : (null, "Неверная длительность"), }; private static (string?, string?) ApplyCapacityChoice(WizardPayload p, string choice) => choice switch { - "waitlist:on" => (WizardStepNames.Visibility, SetWaitlist(p, true)), + "waitlist:on" => (WizardStepNames.Visibility, SetWaitlist(p, true)), "waitlist:off" => (WizardStepNames.Visibility, SetWaitlist(p, false)), _ => (null, "Неизвестный выбор"), }; private static (string?, string?) ApplyVisibilityChoice(WizardPayload p, string choice) => choice switch { - "public" => (NextAfterVisibility(p), SetVisibility(p, WizardVisibility.Public)), - "club" => (WizardStepNames.PickClub, SetVisibility(p, WizardVisibility.Club)), - "members" => (WizardStepNames.PickClub, SetVisibility(p, WizardVisibility.Members)), + "public" => (NextAfterVisibility(p), SetVisibility(p, WizardVisibility.Public)), + "club" => (WizardStepNames.PickClub, SetVisibility(p, WizardVisibility.Club)), + "members" => (WizardStepNames.PickClub, SetVisibility(p, WizardVisibility.Members)), "pickclub" => (WizardStepNames.PickClub, null), _ => (null, "Неизвестный выбор"), }; @@ -303,7 +303,7 @@ public sealed class GameCreationWizard private static (string?, string?) ApplyPublishChoice(WizardPayload p, string choice) => choice switch { "yes" => (WizardStepNames.Confirm, SetPublishInShowcase(p, true)), - "no" => (WizardStepNames.Confirm, SetPublishInShowcase(p, false)), + "no" => (WizardStepNames.Confirm, SetPublishInShowcase(p, false)), _ => (null, "Неизвестный выбор"), }; @@ -318,7 +318,7 @@ public sealed class GameCreationWizard private static (string?, string?) ApplyPoolAddSlotsChoice(WizardPayload p, string choice) => choice switch { - "add" => BeginNewPoolSlot(p), + "add" => BeginNewPoolSlot(p), "done" => p.Pool?.Slots.Count > 0 ? (WizardStepNames.PoolConfirm, null) : (null, "Добавьте хотя бы один слот"), @@ -327,7 +327,7 @@ public sealed class GameCreationWizard private static (string?, string?) ApplyPoolSlotCapacityChoice(WizardPayload p, string choice) => choice switch { - "waitlist:on" => (WizardStepNames.PoolAddSlots, CommitCurrentPoolSlot(p, true)), + "waitlist:on" => (WizardStepNames.PoolAddSlots, CommitCurrentPoolSlot(p, true)), "waitlist:off" => (WizardStepNames.PoolAddSlots, CommitCurrentPoolSlot(p, false)), _ => (null, "Неизвестный выбор"), }; @@ -346,23 +346,23 @@ public sealed class GameCreationWizard private static string? PreviousStep(string step, WizardPayload p) => step switch { - WizardStepNames.Title => null, // first step - WizardStepNames.Description => WizardStepNames.Title, - WizardStepNames.Cover => WizardStepNames.Description, - WizardStepNames.System => WizardStepNames.Cover, - WizardStepNames.Duration => WizardStepNames.System, - WizardStepNames.DateTime => WizardStepNames.Duration, - WizardStepNames.Capacity => WizardStepNames.DateTime, - WizardStepNames.Visibility => WizardStepNames.Capacity, - WizardStepNames.PickClub => WizardStepNames.Visibility, - WizardStepNames.Publish => WizardStepNames.PickClub, - WizardStepNames.Confirm => WizardStepNames.Publish, + WizardStepNames.Title => null, // first step + WizardStepNames.Description => WizardStepNames.Title, + WizardStepNames.Cover => WizardStepNames.Description, + WizardStepNames.System => WizardStepNames.Cover, + WizardStepNames.Duration => WizardStepNames.System, + WizardStepNames.DateTime => WizardStepNames.Duration, + WizardStepNames.Capacity => WizardStepNames.DateTime, + WizardStepNames.Visibility => WizardStepNames.Capacity, + WizardStepNames.PickClub => WizardStepNames.Visibility, + WizardStepNames.Publish => WizardStepNames.PickClub, + WizardStepNames.Confirm => WizardStepNames.Publish, WizardStepNames.PoolSystemDuration => null, // first pool step - WizardStepNames.PoolAddSlots => WizardStepNames.PoolSystemDuration, - WizardStepNames.PoolSlotDateTime => WizardStepNames.PoolAddSlots, - WizardStepNames.PoolSlotCapacity => WizardStepNames.PoolSlotDateTime, - WizardStepNames.PoolConfirm => WizardStepNames.PoolAddSlots, + WizardStepNames.PoolAddSlots => WizardStepNames.PoolSystemDuration, + WizardStepNames.PoolSlotDateTime => WizardStepNames.PoolAddSlots, + WizardStepNames.PoolSlotCapacity => WizardStepNames.PoolSlotDateTime, + WizardStepNames.PoolConfirm => WizardStepNames.PoolAddSlots, _ => null, }; @@ -381,9 +381,9 @@ public sealed class GameCreationWizard } // Mutators — return the error message if any (kept here to centralise flow). - private static string? SetTitle(WizardPayload p, string v) { p.Title = v; return null; } + private static string? SetTitle(WizardPayload p, string v) { p.Title = v; return null; } private static string? SetDescription(WizardPayload p, string? v) { p.Description = v; return null; } - private static string? SetImageUrl(WizardPayload p, string? v) { p.ImageUrl = v; p.ImageFileId = null; return null; } + private static string? SetImageUrl(WizardPayload p, string? v) { p.ImageUrl = v; p.ImageFileId = null; return null; } private static void ApplyCoverPhoto(WizardDraft d, string fileId) { var p = LoadPayload(d); @@ -393,15 +393,15 @@ public sealed class GameCreationWizard var next = NextAfterCover(p); if (next is { } s) d.Step = s; } - private static string? SetSystem(WizardPayload p, string? v) { p.System = v; return null; } - private static string? SetDurationMinutes(WizardPayload p, int? v){ p.DurationMinutes = v; return null; } + private static string? SetSystem(WizardPayload p, string? v) { p.System = v; return null; } + private static string? SetDurationMinutes(WizardPayload p, int? v) { p.DurationMinutes = v; return null; } private static string? SetScheduledAt(WizardPayload p, DateTimeOffset v) { p.Single ??= new WizardSingleInput(); p.Single.ScheduledAt = v; return null; } private static string? SetMaxPlayers(WizardPayload p, int v) { p.Single ??= new WizardSingleInput(); p.Single.MaxPlayers = v; return null; } - private static string? SetWaitlist(WizardPayload p, bool v) { p.Waitlist = v; return null; } + private static string? SetWaitlist(WizardPayload p, bool v) { p.Waitlist = v; return null; } private static string? SetVisibility(WizardPayload p, WizardVisibility? v) { p.Visibility = v; return null; } - private static string? SetClubId(WizardPayload p, Guid v) { p.ClubId = v; return null; } + private static string? SetClubId(WizardPayload p, Guid v) { p.ClubId = v; return null; } private static string? SetType(WizardPayload p, WizardCreationType v) { p.Type = v; return null; } private static string? SetPublishInShowcase(WizardPayload p, bool v) { p.PublishInShowcase = v; return null; } @@ -445,9 +445,9 @@ public sealed class GameCreationWizard } // ── Flow helpers ────────────────────────────────────────────────── - private static string? NextAfterCover(WizardPayload p) => p.Type == WizardCreationType.Pool + private static string? NextAfterCover(WizardPayload p) => p.Type == WizardCreationType.Pool ? WizardStepNames.PoolSystemDuration : WizardStepNames.System; - private static string? NextAfterSystem(WizardPayload p) => WizardStepNames.Duration; + private static string? NextAfterSystem(WizardPayload p) => WizardStepNames.Duration; private static string? NextAfterDuration(WizardPayload p) { if (p.Type == WizardCreationType.Pool) return WizardStepNames.Visibility; diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/WizardStep.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/WizardStep.cs index df95f30..6bb141e 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/WizardStep.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/WizardStep.cs @@ -24,24 +24,24 @@ public static class WizardStep { return draft.Step switch { - WizardStepNames.Type => RenderType(), - WizardStepNames.Title => RenderTitle(), - WizardStepNames.Description => RenderDescription(), - WizardStepNames.Cover => RenderCover(), - WizardStepNames.System => RenderSystem(), - WizardStepNames.Duration => RenderDuration(), - WizardStepNames.DateTime => RenderDateTime(), - WizardStepNames.Capacity => RenderCapacity(), - WizardStepNames.Visibility => RenderVisibility(), - WizardStepNames.PickClub => RenderPickClub(clubs ?? Array.Empty()), - WizardStepNames.Publish => RenderPublish(), - WizardStepNames.Confirm => RenderSingleConfirm(payload), + WizardStepNames.Type => RenderType(), + WizardStepNames.Title => RenderTitle(), + WizardStepNames.Description => RenderDescription(), + WizardStepNames.Cover => RenderCover(), + WizardStepNames.System => RenderSystem(), + WizardStepNames.Duration => RenderDuration(), + WizardStepNames.DateTime => RenderDateTime(), + WizardStepNames.Capacity => RenderCapacity(), + WizardStepNames.Visibility => RenderVisibility(), + WizardStepNames.PickClub => RenderPickClub(clubs ?? Array.Empty()), + WizardStepNames.Publish => RenderPublish(), + WizardStepNames.Confirm => RenderSingleConfirm(payload), WizardStepNames.PoolSystemDuration => RenderPoolSystemDuration(), - WizardStepNames.PoolAddSlots => RenderPoolAddSlots(payload), - WizardStepNames.PoolSlotDateTime => RenderPoolSlotDateTime(), - WizardStepNames.PoolSlotCapacity => RenderPoolSlotCapacity(), - WizardStepNames.PoolConfirm => RenderPoolConfirm(payload), + WizardStepNames.PoolAddSlots => RenderPoolAddSlots(payload), + WizardStepNames.PoolSlotDateTime => RenderPoolSlotDateTime(), + WizardStepNames.PoolSlotCapacity => RenderPoolSlotCapacity(), + WizardStepNames.PoolConfirm => RenderPoolConfirm(payload), _ => throw new InvalidOperationException($"Unknown wizard step: {draft.Step}"), }; @@ -152,10 +152,10 @@ public static class WizardStep sb.AppendLine(); sb.AppendLine($"🎲 {p.Title}"); if (!string.IsNullOrEmpty(p.Description)) sb.AppendLine($"📄 {p.Description}"); - if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}"); - if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч"); - if (p.Single?.ScheduledAt is { } at) sb.AppendLine($"📅 {at.FormatMoscow()} (МСК)"); - if (p.Single?.MaxPlayers is { } mp) sb.AppendLine($"👥 Мест: {mp}, waitlist {(p.Waitlist == true ? "вкл" : "выкл")}"); + if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}"); + if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч"); + if (p.Single?.ScheduledAt is { } at) sb.AppendLine($"📅 {at.FormatMoscow()} (МСК)"); + if (p.Single?.MaxPlayers is { } mp) sb.AppendLine($"👥 Мест: {mp}, waitlist {(p.Waitlist == true ? "вкл" : "выкл")}"); sb.AppendLine($"🔒 Видимость: {RenderVisibilityText(p.Visibility)}"); return (sb.ToString(), new InlineKeyboardMarkup(new[] { @@ -204,8 +204,8 @@ public static class WizardStep sb.AppendLine(); sb.AppendLine($"📝 {p.Title}"); if (!string.IsNullOrEmpty(p.Description)) sb.AppendLine($"📄 {p.Description}"); - if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}"); - if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч"); + if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}"); + if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч"); sb.AppendLine($"🔒 Видимость: {RenderVisibilityText(p.Visibility)}"); sb.AppendLine(); sb.AppendLine($"Слоты ({p.Pool?.Slots.Count ?? 0}):"); @@ -240,8 +240,8 @@ public static class WizardStep private static string RenderVisibilityText(WizardVisibility? v) => v switch { - WizardVisibility.Public => "публичная в общем showcase", - WizardVisibility.Club => "публичная в витрине клуба", + WizardVisibility.Public => "публичная в общем showcase", + WizardVisibility.Club => "публичная в витрине клуба", WizardVisibility.Members => "только для членов клуба", _ => "не задана", }; diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/GameCreationWizardStepTransitionsTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/GameCreationWizardStepTransitionsTests.cs index d28a007..a6a69cb 100644 --- a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/GameCreationWizardStepTransitionsTests.cs +++ b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/GameCreationWizardStepTransitionsTests.cs @@ -16,23 +16,23 @@ public sealed class GameCreationWizardStepTransitionsTests // Type → Title (single game) [InlineData(WizardStepNames.Type, "single", WizardStepNames.Title)] // Type → Title (pool) - [InlineData(WizardStepNames.Type, "pool", WizardStepNames.Title)] + [InlineData(WizardStepNames.Type, "pool", WizardStepNames.Title)] // System → Duration (a known system code) [InlineData(WizardStepNames.System, "Dnd5e", WizardStepNames.Duration)] // Duration → DateTime (single, no maxPlayers yet) [InlineData(WizardStepNames.Duration, "240", WizardStepNames.DateTime)] // Capacity → Visibility - [InlineData(WizardStepNames.Capacity, "waitlist:on", WizardStepNames.Visibility)] + [InlineData(WizardStepNames.Capacity, "waitlist:on", WizardStepNames.Visibility)] [InlineData(WizardStepNames.Capacity, "waitlist:off", WizardStepNames.Visibility)] // Visibility → Publish (public, no club) [InlineData(WizardStepNames.Visibility, "public", WizardStepNames.Publish)] // Visibility → PickClub - [InlineData(WizardStepNames.Visibility, "club", WizardStepNames.PickClub)] + [InlineData(WizardStepNames.Visibility, "club", WizardStepNames.PickClub)] [InlineData(WizardStepNames.Visibility, "members", WizardStepNames.PickClub)] [InlineData(WizardStepNames.Visibility, "pickclub", WizardStepNames.PickClub)] // Publish → Confirm [InlineData(WizardStepNames.Publish, "yes", WizardStepNames.Confirm)] - [InlineData(WizardStepNames.Publish, "no", WizardStepNames.Confirm)] + [InlineData(WizardStepNames.Publish, "no", WizardStepNames.Confirm)] public async Task ChoiceCallback_AdvancesToExpectedStep( string fromStep, string choice, string expectedStep) { @@ -149,26 +149,41 @@ public sealed class GameCreationWizardStepTransitionsTests WizardStepNames.Duration => new WizardPayload { Type = WizardCreationType.Single, Title = "T", System = "Dnd5e" }, WizardStepNames.Capacity => new WizardPayload { - Type = WizardCreationType.Single, Title = "T", System = "Dnd5e", DurationMinutes = 240, + Type = WizardCreationType.Single, + Title = "T", + System = "Dnd5e", + DurationMinutes = 240, Single = new WizardSingleInput { ScheduledAt = DateTimeOffset.UtcNow.AddDays(1) }, }, WizardStepNames.Visibility => new WizardPayload { - Type = WizardCreationType.Single, Title = "T", System = "Dnd5e", DurationMinutes = 240, + Type = WizardCreationType.Single, + Title = "T", + System = "Dnd5e", + DurationMinutes = 240, }, WizardStepNames.PickClub => new WizardPayload { - Type = WizardCreationType.Single, Title = "T", System = "Dnd5e", DurationMinutes = 240, + Type = WizardCreationType.Single, + Title = "T", + System = "Dnd5e", + DurationMinutes = 240, Visibility = WizardVisibility.Club, }, WizardStepNames.Publish => new WizardPayload { - Type = WizardCreationType.Single, Title = "T", System = "Dnd5e", DurationMinutes = 240, + Type = WizardCreationType.Single, + Title = "T", + System = "Dnd5e", + DurationMinutes = 240, Visibility = WizardVisibility.Public, }, WizardStepNames.Confirm => new WizardPayload { - Type = WizardCreationType.Single, Title = "T", System = "Dnd5e", DurationMinutes = 240, + Type = WizardCreationType.Single, + Title = "T", + System = "Dnd5e", + DurationMinutes = 240, Visibility = WizardVisibility.Public, }, _ => new WizardPayload(), diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/GameCreationWizardValidationTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/GameCreationWizardValidationTests.cs index 264e04f..cd58ea7 100644 --- a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/GameCreationWizardValidationTests.cs +++ b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/GameCreationWizardValidationTests.cs @@ -124,7 +124,10 @@ public sealed class GameCreationWizardValidationTests var draft = NewDraft(WizardStepNames.Capacity, new WizardPayload { - Type = WizardCreationType.Single, Title = "T", System = "Dnd5e", DurationMinutes = 240, + Type = WizardCreationType.Single, + Title = "T", + System = "Dnd5e", + DurationMinutes = 240, Single = new WizardSingleInput { ScheduledAt = DateTimeOffset.UtcNow.AddDays(1) }, }); drafts.Seed(draft);