014b5edd31
PR Checks / test-and-build (pull_request) Successful in 15m52s
Add format and location steps to the Telegram /newsession wizard, persist offline addresses in sessions.location_address, and render online links/offline addresses in schedule messages. Bump version to 3.10.0.
299 lines
12 KiB
C#
299 lines
12 KiB
C#
using System;
|
|
using System.Linq;
|
|
using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
|
|
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
|
|
using Telegram.Bot.Types.ReplyMarkups;
|
|
|
|
namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
|
|
|
|
/// <summary>
|
|
/// Verifies the shape of each step's rendered keyboard: which buttons are
|
|
/// present, where the Back/Cancel affordances sit, and that the title text
|
|
/// is non-empty. Tests use substring matching so they survive label tweaks
|
|
/// (e.g. emoji prefixes, suffix additions like "· 4 ч").
|
|
/// </summary>
|
|
public sealed class WizardStepRenderTests
|
|
{
|
|
[Fact]
|
|
public void TypeStep_HasBothChoicesAndCancel_ButNoBack()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.Type);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Одну игру", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Пул игр", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
|
|
Assert.DoesNotContain(labels, l => l.Contains("Назад", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void TitleStep_HasBackAndCancel_ButNoChoiceButtons()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.Title);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Назад", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void SystemStep_HasKnownSystemButtons()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.System);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("D&D 5e", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Pathfinder 2e", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Call of Cthulhu", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("GURPS", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Fate", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Другое", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Пропустить", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void DurationStep_HasPresetButtons()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.Duration);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains("3 часа", labels);
|
|
Assert.Contains("4 часа", labels);
|
|
Assert.Contains("5 часов", labels);
|
|
Assert.Contains("6 часов", labels);
|
|
}
|
|
|
|
[Fact]
|
|
public void CapacityStep_HasWaitlistButtons()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.Capacity);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Waitlist вкл", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Без waitlist", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Без лимита", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatStep_HasOnlineAndOfflineButtons()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.Format);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Online", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Offline", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void LocationStep_ForOnline_AsksForLink()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.Location, new WizardPayload { Format = WizardSessionFormat.Online });
|
|
|
|
Assert.Contains("ссыл", text, StringComparison.OrdinalIgnoreCase);
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Назад", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void LocationStep_ForOffline_AsksForAddress()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.Location, new WizardPayload { Format = WizardSessionFormat.Offline });
|
|
|
|
Assert.Contains("адрес", text, StringComparison.OrdinalIgnoreCase);
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Назад", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void VisibilityStep_HasAllFourVisibilityOptions()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.Visibility);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Публичная в общем showcase", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Публичная в витрине клуба", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Только для членов клуба", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Выбрать клуб", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void PickClub_WithoutClubs_RendersEmptyHint()
|
|
{
|
|
var (text, kb) = WizardStep.Render(
|
|
NewDraft(WizardStepNames.PickClub),
|
|
new WizardPayload(),
|
|
clubs: null);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
Assert.Contains("нет клубов", text, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public void PickClub_WithOneClub_RendersClubButton()
|
|
{
|
|
var clubId = Guid.NewGuid();
|
|
var (text, kb) = WizardStep.Render(
|
|
NewDraft(WizardStepNames.PickClub),
|
|
new WizardPayload(),
|
|
new[] { new WizardClubOption(clubId, "Awesome Club") });
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
Assert.Contains("Awesome Club", ButtonLabels(kb));
|
|
}
|
|
|
|
[Fact]
|
|
public void PublishStep_HasPublishAndChatOnlyButtons()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.Publish);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Опубликовать", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Только в чате", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void ConfirmStep_HasCreateAndCancel()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.Confirm, new WizardPayload
|
|
{
|
|
Type = WizardCreationType.Single,
|
|
Title = "My Game",
|
|
Format = WizardSessionFormat.Offline,
|
|
LocationAddress = "Москва, ул. Кубиков, 12",
|
|
});
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
Assert.Contains("My Game", text);
|
|
Assert.Contains("Offline", text);
|
|
Assert.Contains("Москва, ул. Кубиков, 12", text);
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Создать", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void PoolSystemDuration_HasPresetsAndCustom()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.PoolSystemDuration);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("D&D 5e", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Pathfinder 2e", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Call of Cthulhu", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("GURPS", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Другое", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void PoolAddSlots_HasAddAndDone_AndShowsCurrentCount()
|
|
{
|
|
var payload = new WizardPayload
|
|
{
|
|
Type = WizardCreationType.Pool,
|
|
Title = "My Pool",
|
|
Pool = new WizardPoolInput
|
|
{
|
|
Slots =
|
|
{
|
|
new WizardSlotInput { MaxPlayers = 4 },
|
|
new WizardSlotInput { MaxPlayers = 5 },
|
|
},
|
|
},
|
|
};
|
|
var (text, kb) = WizardStep.Render(
|
|
NewDraft(WizardStepNames.PoolAddSlots),
|
|
payload);
|
|
|
|
Assert.Contains("My Pool", text);
|
|
Assert.Contains("2", text);
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Добавить слот", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Готово", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void PoolSlotDateTime_HasBackAndCancel_ButNoChoiceButtons()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.PoolSlotDateTime);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Назад", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void PoolSlotCapacity_HasWaitlistButtons()
|
|
{
|
|
var (text, kb) = Render(WizardStepNames.PoolSlotCapacity);
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(text));
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Waitlist вкл", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Без waitlist", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void PoolConfirm_HasCreatePoolAndCancel()
|
|
{
|
|
var payload = new WizardPayload
|
|
{
|
|
Type = WizardCreationType.Pool,
|
|
Title = "Pool",
|
|
System = "Dnd5e",
|
|
DurationMinutes = 240,
|
|
Pool = new WizardPoolInput
|
|
{
|
|
Slots =
|
|
{
|
|
new WizardSlotInput { MaxPlayers = 4, Waitlist = true, ScheduledAt = DateTimeOffset.UtcNow.AddDays(7) },
|
|
new WizardSlotInput { MaxPlayers = 5, Waitlist = false, ScheduledAt = DateTimeOffset.UtcNow.AddDays(14) },
|
|
},
|
|
},
|
|
};
|
|
var (text, kb) = WizardStep.Render(
|
|
NewDraft(WizardStepNames.PoolConfirm),
|
|
payload);
|
|
|
|
Assert.Contains("Pool", text);
|
|
Assert.Contains("2", text); // slot count
|
|
var labels = ButtonLabels(kb);
|
|
Assert.Contains(labels, l => l.Contains("Создать пул", StringComparison.Ordinal));
|
|
Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_UnknownStep_Throws()
|
|
{
|
|
var draft = new WizardDraft { Step = "Bogus" };
|
|
Assert.Throws<InvalidOperationException>(() => WizardStep.Render(draft, new WizardPayload()));
|
|
}
|
|
|
|
private static (string text, InlineKeyboardMarkup kb) Render(string step, WizardPayload? payload = null)
|
|
=> WizardStep.Render(NewDraft(step), payload ?? new WizardPayload());
|
|
|
|
private static WizardDraft NewDraft(string step) => new()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ChatId = "42",
|
|
Step = step,
|
|
PayloadJson = "{}",
|
|
};
|
|
|
|
private static string[] ButtonLabels(InlineKeyboardMarkup kb) =>
|
|
kb.InlineKeyboard
|
|
.SelectMany(row => row.Select(b => b.Text))
|
|
.ToArray();
|
|
}
|