using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Domain.Broadcast;
using Xunit;
namespace TeleWave.Application.Tests.Broadcast;
///
/// Подстановка плейсхолдеров. Правило одно и важное: неизвестное значение схлопывается вместе
/// с осиротевшим разделителем, а не оставляет дыру в кадре.
///
public class BumperPlaceholdersTests
{
private static readonly DateTimeOffset Moment = new(
2026,
4,
6,
21,
24,
0,
TimeSpan.FromHours(3)
);
private static BumperContext Full() =>
new(
"Первый",
4,
Moment,
"Симпсоны",
"Терминатор 2",
"с5э12",
"с1э3",
1991,
"Боевик",
new TimeOnly(21, 30),
"Вечернее кино"
);
[Theory]
[InlineData("{channel}", "Первый")]
[InlineData("{channel.number}", "4")]
[InlineData("{now.title}", "Симпсоны")]
[InlineData("{next.title}", "Терминатор 2")]
[InlineData("{now.episode}", "с5э12")]
[InlineData("{next.episode}", "с1э3")]
[InlineData("{next.year}", "1991")]
[InlineData("{next.genre}", "Боевик")]
[InlineData("{next.time}", "21:30")]
[InlineData("{time}", "21:24")]
[InlineData("{date}", "6 апреля")]
[InlineData("{weekday}", "понедельник")]
[InlineData("{slot}", "Вечернее кино")]
public void Resolve_SubstitutesEveryToken(string text, string expected) =>
Assert.Equal(expected, BumperPlaceholders.Resolve(text, Full()));
[Fact]
public void Resolve_MixesTextAndTokens()
{
var resolved = BumperPlaceholders.Resolve("ДАЛЕЕ В {next.time} — {next.title}", Full());
Assert.Equal("ДАЛЕЕ В 21:30 — Терминатор 2", resolved);
}
[Fact]
public void Resolve_CollapsesGapsFromMissingValues()
{
var empty = new BumperContext("Канал", null, Moment);
// Ни следующей программы, ни номера канала: строка схлопывается, осиротевший разделитель уходит.
Assert.Equal("СЕЙЧАС", BumperPlaceholders.Resolve("СЕЙЧАС — {now.title}", empty));
Assert.Equal("", BumperPlaceholders.Resolve("{next.title}", empty));
Assert.Equal("Канал", BumperPlaceholders.Resolve("{channel} {channel.number}", empty));
// А висящий предлог остаётся: отличить его от слова в чужом тексте нельзя.
Assert.Equal("ДАЛЕЕ В", BumperPlaceholders.Resolve("ДАЛЕЕ В {next.time}", empty));
}
[Fact]
public void Resolve_UnknownTokenBecomesEmpty()
{
Assert.Equal("Далее", BumperPlaceholders.Resolve("Далее {next.tittle}", Full()));
Assert.Equal("", BumperPlaceholders.Resolve(" ", Full()));
}
[Fact]
public void UnknownTokens_ListsOnlyStrangers()
{
Assert.Equal(
["next.tittle", "chanel"],
BumperPlaceholders.UnknownTokens("{next.title} {next.tittle} {chanel} {next.tittle}")
);
Assert.Empty(BumperPlaceholders.UnknownTokens("{channel}"));
Assert.Empty(BumperPlaceholders.UnknownTokens(null));
}
[Fact]
public void TokensIn_CollectsAcrossTexts()
{
var tokens = BumperPlaceholders.TokensIn(["СЕЙЧАС {now.title}", "", "{next.genre}"]);
Assert.Equal(["next.genre", "now.title"], tokens.Order());
}
[Theory]
[InlineData("{time}", true)]
[InlineData("{date}", true)]
[InlineData("{weekday}", true)]
[InlineData("{next.time}", false)]
[InlineData("СЕЙЧАС", false)]
[InlineData(null, false)]
public void IsVolatile_MarksTokensTiedToTheMoment(string? text, bool expected) =>
Assert.Equal(expected, BumperPlaceholders.IsVolatile(text));
[Theory]
[InlineData(5, 12, "с5э12")]
[InlineData(null, 7, "э7")]
[InlineData(3, null, null)]
[InlineData(null, null, null)]
public void Episode_FormatsWhatIsKnown(int? season, int? episode, string? expected) =>
Assert.Equal(expected, BumperPlaceholders.Episode(season, episode));
[Fact]
public void RenderedText_RoundTripsAndSurvivesGarbage()
{
var lines = new List
{
new(BumperLineStyle.Label, BumperLineColor.Accent, "СЕЙЧАС"),
new(BumperLineStyle.Title, BumperLineColor.Text, "Симпсоны"),
};
var restored = BumperRenderedText.FromJson(BumperRenderedText.ToJson(lines));
Assert.Equal(lines, restored);
Assert.Empty(BumperRenderedText.FromJson("{не json}"));
Assert.Empty(BumperRenderedText.FromJson(null));
}
}