using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; using NUnit.Framework; using StardewModdingAPI.Utilities; namespace StardewModdingAPI.Tests { /// Unit tests for . [TestFixture] internal class SDateTests { /********* ** Properties *********/ /// All valid seasons. private static string[] ValidSeasons = { "spring", "summer", "fall", "winter" }; /// All valid days of a month. private static int[] ValidDays = Enumerable.Range(1, 28).ToArray(); /********* ** Unit tests *********/ /**** ** Constructor ****/ [Test(Description = "Assert that the constructor sets the expected values for all valid dates.")] public void Constructor_SetsExpectedValues([ValueSource(nameof(SDateTests.ValidSeasons))] string season, [ValueSource(nameof(SDateTests.ValidDays))] int day, [Values(1, 2, 100)] int year) { // act SDate date = new SDate(day, season, year); // assert Assert.AreEqual(day, date.Day); Assert.AreEqual(season, date.Season); Assert.AreEqual(year, date.Year); } [Test(Description = "Assert that the constructor throws an exception if the values are invalid.")] [TestCase(01, "Spring", 1)] // seasons are case-sensitive [TestCase(01, "springs", 1)] // invalid season name [TestCase(-1, "spring", 1)] // day < 0 [TestCase(29, "spring", 1)] // day > 28 [TestCase(01, "spring", -1)] // year < 1 [TestCase(01, "spring", 0)] // year < 1 [SuppressMessage("ReSharper", "AssignmentIsFullyDiscarded", Justification = "Deliberate for unit test.")] public void Constructor_RejectsInvalidValues(int day, string season, int year) { // act & assert Assert.Throws(() => _ = new SDate(day, season, year), "Constructing the invalid date didn't throw the expected exception."); } /**** ** ToString ****/ [Test(Description = "Assert that ToString returns the expected string.")] [TestCase("14 spring Y1", ExpectedResult = "14 spring Y1")] [TestCase("01 summer Y16", ExpectedResult = "01 summer Y16")] [TestCase("28 fall Y10", ExpectedResult = "28 fall Y10")] [TestCase("01 winter Y1", ExpectedResult = "01 winter Y1")] public string ToString(string dateStr) { return this.ParseDate(dateStr).ToString(); } /**** ** AddDays ****/ [Test(Description = "Assert that AddDays returns the expected date.")] [TestCase("01 spring Y1", 15, ExpectedResult = "16 spring Y1")] // day transition [TestCase("01 spring Y1", 28, ExpectedResult = "01 summer Y1")] // season transition [TestCase("01 spring Y1", 28 * 4, ExpectedResult = "01 spring Y2")] // year transition [TestCase("01 spring Y1", 28 * 7 + 17, ExpectedResult = "18 winter Y2")] // year transition [TestCase("15 spring Y1", -14, ExpectedResult = "01 spring Y1")] // negative day transition [TestCase("15 summer Y1", -28, ExpectedResult = "15 spring Y1")] // negative season transition [TestCase("15 summer Y2", -28 * 4, ExpectedResult = "15 summer Y1")] // negative year transition [TestCase("01 spring Y3", -(28 * 7 + 17), ExpectedResult = "12 spring Y1")] // negative year transition public string AddDays(string dateStr, int addDays) { return this.ParseDate(dateStr).AddDays(addDays).ToString(); } /********* ** Private methods *********/ /// Convert a string date into a game date, to make unit tests easier to read. /// The date string like "dd MMMM yy". private SDate ParseDate(string dateStr) { void Fail(string reason) => throw new AssertionException($"Couldn't parse date '{dateStr}' because {reason}."); // parse Match match = Regex.Match(dateStr, @"^(?\d+) (?\w+) Y(?\d+)$"); if (!match.Success) Fail("it doesn't match expected pattern (should be like 28 spring Y1)"); // extract parts string season = match.Groups["season"].Value; if (!int.TryParse(match.Groups["day"].Value, out int day)) Fail($"'{match.Groups["day"].Value}' couldn't be parsed as a day."); if (!int.TryParse(match.Groups["year"].Value, out int year)) Fail($"'{match.Groups["year"].Value}' couldn't be parsed as a year."); // build date return new SDate(day, season, year); } } }