|
| 1 | +using MarkupString.Ansi; |
| 2 | +using MarkupString.Layout; |
| 3 | + |
| 4 | +namespace MarkupString.Tests; |
| 5 | + |
| 6 | +public class AsciiFoldTests |
| 7 | +{ |
| 8 | + private static readonly AnsiMarkup Red = AnsiMarkup.Create(foreground: new AnsiColor.Rgb(255, 0, 0)); |
| 9 | + |
| 10 | + [Test] |
| 11 | + [Arguments("Scene 3 · 21:04", "Scene 3 * 21:04")] |
| 12 | + [Arguments("one — two – three", "one - two - three")] |
| 13 | + [Arguments("“quoted” ‘single’", "\"quoted\" 'single'")] |
| 14 | + [Arguments("café naïve Ångström", "cafe naive Angstrom")] |
| 15 | + [Arguments("café", "cafe")] |
| 16 | + [Arguments("a b", "a b")] |
| 17 | + [Arguments("→ ← ×", "> < x")] |
| 18 | + [Arguments("Straße Øre", "Strase Ore")] |
| 19 | + [Arguments("┌─┐ ═ █", "+-+ = #")] |
| 20 | + [Arguments("plain ascii\ttab\n", "plain ascii\ttab\n")] |
| 21 | + public async Task Fold_ReplacesWithTheNearestAscii(string text, string expected) |
| 22 | + => await Assert.That(AsciiFold.Default.Fold(text)).IsEqualTo(expected); |
| 23 | + |
| 24 | + [Test] |
| 25 | + public async Task Fold_KeepsEachCharactersWidth() |
| 26 | + { |
| 27 | + // A wide character is two question marks, a fullwidth letter the letter and a space. |
| 28 | + await Assert.That(AsciiFold.Default.Fold("日本")).IsEqualTo("????"); |
| 29 | + await Assert.That(AsciiFold.Default.Fold("😀!")).IsEqualTo("??!"); |
| 30 | + await Assert.That(AsciiFold.Default.Fold("AB")).IsEqualTo("A B "); |
| 31 | + await Assert.That(AsciiFold.Default.Fold("👩👩👧")).IsEqualTo("??"); |
| 32 | + } |
| 33 | + |
| 34 | + [Test] |
| 35 | + public async Task Fold_ForLatin1_KeepsWhatLatin1Has() |
| 36 | + { |
| 37 | + var fold = new AsciiFold(latin1: true); |
| 38 | + await Assert.That(fold.Fold("· café — ½")).IsEqualTo("· café - ½"); |
| 39 | + await Assert.That(fold.Fold("café")).IsEqualTo("café"); |
| 40 | + } |
| 41 | + |
| 42 | + [Test] |
| 43 | + public async Task Translations_ComeBeforeTheBuiltInOnes() |
| 44 | + { |
| 45 | + var fold = new AsciiFold([new("·", " - "), new("…", "..."), new("❤️", "<3")]); |
| 46 | + await Assert.That(fold.Fold("a·b…")).IsEqualTo("a - b..."); |
| 47 | + await Assert.That(fold.Fold("I ❤️ it")).IsEqualTo("I <3 it"); |
| 48 | + await Assert.That(new AsciiFold([new("·", "+")], latin1: true).Fold("a·b")).IsEqualTo("a+b"); |
| 49 | + } |
| 50 | + |
| 51 | + [Test] |
| 52 | + [Arguments("ab", "x")] |
| 53 | + [Arguments("a", "x")] |
| 54 | + [Arguments("", "x")] |
| 55 | + [Arguments("·", "é")] |
| 56 | + [Arguments("·", "\n")] |
| 57 | + public async Task Translations_RejectAKeyOrValueThatCannotWork(string key, string value) |
| 58 | + => await Assert.That(() => new AsciiFold([new(key, value)])).Throws<ArgumentException>(); |
| 59 | + |
| 60 | + [Test] |
| 61 | + public async Task Fold_KeepsMarkupOnTheCharacterItReplaces() |
| 62 | + { |
| 63 | + var text = MarkupText.Concat([MarkupText.Plain("a"), MarkupText.Wrap(Red, "·…"), MarkupText.Plain("b")]); |
| 64 | + var folded = new AsciiFold([new("…", "...")]).Fold(text); |
| 65 | + |
| 66 | + await Assert.That(folded.Text).IsEqualTo("a*...b"); |
| 67 | + await Assert.That(folded.Runs.Length).IsEqualTo(1); |
| 68 | + await Assert.That(folded.Runs[0]).IsEqualTo(new Run(1, 4, MarkupSet.Of(Red))); |
| 69 | + } |
| 70 | + |
| 71 | + [Test] |
| 72 | + public async Task Fold_LeavesAsciiTextAsItIs() |
| 73 | + { |
| 74 | + var text = MarkupText.Wrap(Red, "nothing to do"); |
| 75 | + await Assert.That(AsciiFold.Default.Fold(text)).IsSameReferenceAs(text); |
| 76 | + } |
| 77 | + |
| 78 | + [Test] |
| 79 | + public async Task Fold_KeepsAPointsCarrier() |
| 80 | + { |
| 81 | + var text = MarkupText.Concat([MarkupText.Plain("·"), MarkupText.Sound("ping.wav")]); |
| 82 | + var folded = AsciiFold.Default.Fold(text); |
| 83 | + |
| 84 | + await Assert.That(folded.Text).IsEqualTo("*" + MarkupText.PointCarrier); |
| 85 | + await Assert.That(folded.Runs.Single().Start).IsEqualTo(1); |
| 86 | + } |
| 87 | + |
| 88 | + [Test] |
| 89 | + public async Task Layout_MeasuresTheFoldedText() |
| 90 | + { |
| 91 | + var table = new Table([new TableColumn(MarkupText.Plain("Scene")), new TableColumn(MarkupText.Plain("When"))], |
| 92 | + [ |
| 93 | + [MarkupText.Plain("Tea · Garden"), MarkupText.Plain("21:04")], |
| 94 | + [MarkupText.Plain("Duel…"), MarkupText.Plain("now")], |
| 95 | + ]); |
| 96 | + var block = new Stack([new Rule(MarkupText.Plain("Wren · Scenes")), table]); |
| 97 | + var context = new LayoutContext { AsciiOnly = true, Fold = new AsciiFold([new("·", " - "), new("…", "...")]) }; |
| 98 | + |
| 99 | + var lines = BlockLayout.Lines(block, 30, context).Select(line => line.ToPlainText()).ToArray(); |
| 100 | + |
| 101 | + await Assert.That(lines[0]).Contains("Wren - Scenes"); |
| 102 | + await Assert.That(lines.Any(line => line.Contains("Tea - Garden"))).IsTrue(); |
| 103 | + await Assert.That(lines.Any(line => line.Contains("Duel..."))).IsTrue(); |
| 104 | + await Assert.That(lines.All(line => System.Text.Ascii.IsValid(line))).IsTrue(); |
| 105 | + // The column after the folded text still starts at one place on every row. |
| 106 | + var when = lines.Where(line => line.Contains("21:04") || line.Contains("now")).Select(line => line.IndexOf(line.Contains("21:04") ? "21:04" : "now")).Distinct(); |
| 107 | + await Assert.That(when.Count()).IsEqualTo(1); |
| 108 | + } |
| 109 | + |
| 110 | + [Test] |
| 111 | + public async Task Relayout_KeepsTheTreeAsGiven() |
| 112 | + { |
| 113 | + var built = BlockLayout.Build(new Rule(MarkupText.Plain("a · b")), 20, fluid: true); |
| 114 | + var relaid = BlockLayout.Relayout(built, 20, new LayoutContext { AsciiOnly = true, Fold = AsciiFold.Default }); |
| 115 | + |
| 116 | + await Assert.That(relaid.ToPlainText()).Contains("a * b"); |
| 117 | + var layout = relaid.Runs.SelectMany(run => run.Markups).OfType<LayoutMarkup>().Single(); |
| 118 | + await Assert.That(((Rule)layout.Root).Title!.ToPlainText()).IsEqualTo("a · b"); |
| 119 | + } |
| 120 | +} |
0 commit comments