using System; using System.Collections.Generic; using Amphora; using Glass.Editing; using UnitTestSharp; namespace Glass.UnitTests.Editing { public class SplicedTextTests : TestFixture { public static readonly string LongLines = new string('a', 700) + "\n" + new string('b', 300); public static readonly string[] Insertions = { "", "x", "ab\ncd", "\n", "\r", "\r\n", PagedTextTests.Pair, "q" + new string('q', 599) + "\r\n" + new string('r', 300), }; /// /// A splice and the string it should read as. /// public class Case { public SplicedText Spliced; public StringSource Expected; public string ExpectedText; public string Description; } public static Case Splice(ITextSource text, string whole, int start, int end, string inserted) { var spliced = new SplicedText(); spliced.Set(text, start, end, inserted); string expected = whole.Substring(0, start) + inserted + whole.Substring(end); return new Case { Spliced = spliced, Expected = new StringSource(expected), ExpectedText = expected, Description = $"\"{whole.Length} chars, {start}..{end} for {inserted.Length} chars\"", }; } /// /// Splices at the start, the end, and inside, over a paged text and a string source. /// public static List Cases(bool includeLongLines = false, bool pagedOnly = false, int longestInsertion = int.MaxValue) { var returnMe = new List(); var wholes = new List(StringSourceTests.Samples); if (includeLongLines) { wholes = new List { LongLines }; } foreach (string whole in wholes) { var ranges = new List { new[] { 0, 0 }, new[] { 0, whole.Length }, new[] { whole.Length, whole.Length }, new[] { whole.Length / 2, whole.Length / 2 }, new[] { whole.Length / 3, whole.Length * 2 / 3 }, }; foreach (int[] range in ranges) { foreach (string inserted in Insertions) { if (includeLongLines && inserted.Length > 0 && inserted.Length < 6) { continue; } if (inserted.Length > longestInsertion) { continue; } returnMe.Add(Splice(PagedTextTests.Paged(whole), whole, range[0], range[1], inserted)); if (!pagedOnly) { returnMe.Add(Splice(new StringSource(whole), whole, range[0], range[1], inserted)); } } } } return returnMe; } public class SetTests : TestFixture { public void NullText_Throws() { CheckThrow(typeof(ArgumentNullException)); new SplicedText().Set(null, 0, 0, ""); } public void NullReplacement_Throws() { CheckThrow(typeof(ArgumentNullException)); new SplicedText().Set(new StringSource("abc"), 0, 0, null); } public void StartPastEnd_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SplicedText().Set(new StringSource("abc"), 2, 1, ""); } public void NegativeStart_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SplicedText().Set(new StringSource("abc"), -1, 1, ""); } public void EndPastTheText_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SplicedText().Set(new StringSource("abc"), 1, 4, ""); } } public class UnderlyingTests : TestFixture { public void IsTheTextTheRangeWasSwappedIn() { var text = new StringSource("abcdef"); var spliced = new SplicedText(); spliced.Set(text, 1, 3, "x"); CheckTrue(ReferenceEquals(text, spliced.Underlying)); } } public class ReplacedStartTests : TestFixture { public void IsWhereTheSwappedRangeStarts() { var spliced = new SplicedText(); spliced.Set(new StringSource("abcdef"), 1, 3, "x"); CheckEqual(1, spliced.ReplacedStart); } } public class ReplacedEndTests : TestFixture { public void IsWhereTheSwappedRangeEndsInTheTextUnderneath() { var spliced = new SplicedText(); spliced.Set(new StringSource("abcdef"), 1, 3, "x"); CheckEqual(3, spliced.ReplacedEnd); } } public class ReplacementTests : TestFixture { public void IsTheStringTheRangeWasSwappedFor() { var spliced = new SplicedText(); spliced.Set(new StringSource("abcdef"), 1, 3, "xyz"); CheckEqual("xyz", spliced.Replacement); } } public class VersionTests : TestFixture { public void SettingTheSameSplice_KeepsIt() { var text = new StringSource("abc"); var spliced = new SplicedText(); spliced.Set(text, 1, 2, "xy"); int version = spliced.Version; spliced.Set(text, 1, 2, "xy"); CheckEqual(version, spliced.Version); } public void SettingTheSameCharsInANewString_KeepsIt() { var text = new StringSource("abc"); var spliced = new SplicedText(); spliced.Set(text, 1, 2, "xy"); int version = spliced.Version; spliced.Set(text, 1, 2, new string(new[] { 'x', 'y' })); CheckEqual(version, spliced.Version); } public void ADifferentReplacement_ChangesIt() { var text = new StringSource("abc"); var spliced = new SplicedText(); spliced.Set(text, 1, 2, "xy"); int version = spliced.Version; spliced.Set(text, 1, 2, "xz"); Check(version != spliced.Version); } public void ADifferentRange_ChangesIt() { var text = new StringSource("abc"); var spliced = new SplicedText(); spliced.Set(text, 1, 2, "xy"); int version = spliced.Version; spliced.Set(text, 0, 2, "xy"); Check(version != spliced.Version); } public void ADifferentText_ChangesIt() { var spliced = new SplicedText(); spliced.Set(new StringSource("abc"), 1, 2, "xy"); int version = spliced.Version; spliced.Set(new StringSource("abc"), 1, 2, "xy"); Check(version != spliced.Version); } public void TheTextChangingUnderneath_ChangesIt() { var paged = PagedTextTests.Paged("abcdef"); var spliced = new SplicedText(); spliced.Set(paged, 1, 2, "xy"); int version = spliced.Version; paged.Replace(5, 0, "!"); spliced.Set(paged, 1, 2, "xy"); Check(version != spliced.Version); } } public class LengthTests : TestFixture { public void IsTheLengthOfTheSplicedString() { foreach (Case splice in Cases()) { CheckEqual(splice.ExpectedText.Length, splice.Spliced.Length); } } public void OfANewSplice_IsEmpty() { CheckEqual(0, new SplicedText().Length); } } public class IndexerTests : TestFixture { public void ReadsTheSplicedChars() { foreach (Case splice in Cases()) { for (int i = 0; i < splice.ExpectedText.Length; ++i) { CheckEqual(splice.ExpectedText[i], splice.Spliced[i]); } } } public void NegativeIndex_Throws() { var spliced = new SplicedText(); spliced.Set(new StringSource("abc"), 0, 0, ""); CheckThrow(typeof(ArgumentOutOfRangeException)); char ignored = spliced[-1]; } public void IndexAtTheEnd_Throws() { var spliced = new SplicedText(); spliced.Set(new StringSource("abc"), 1, 2, "xy"); CheckThrow(typeof(ArgumentOutOfRangeException)); char ignored = spliced[4]; } } public class CopyToTests : TestFixture { public void CopiesEveryRange() { foreach (Case splice in Cases()) { int length = splice.ExpectedText.Length; int step = Math.Max(1, length / 7); for (int start = 0; start <= length; start += step) { for (int count = 0; start + count <= length; count += step) { var copy = new char[count + 2]; splice.Spliced.CopyTo(start, count, copy, 1); CheckEqual(splice.ExpectedText.Substring(start, count), new string(copy, 1, count)); } } } } public void ARangePastTheEnd_Throws() { var spliced = new SplicedText(); spliced.Set(new StringSource("abc"), 1, 2, "xy"); CheckThrow(typeof(ArgumentOutOfRangeException)); spliced.CopyTo(3, 2, new char[4], 0); } } public class SliceTests : TestFixture { public void ViewsEveryRange() { foreach (Case splice in Cases()) { int length = splice.ExpectedText.Length; int step = Math.Max(1, length / 7); char[] scratch = null; for (int start = 0; start <= length; start += step) { for (int count = 0; start + count <= length; count += step) { StringView view = splice.Spliced.Slice(start, count, ref scratch); CheckEqual(splice.ExpectedText.Substring(start, count), view.ToString()); } } } } public void ARangeInTheChangedPart_CopiesNothing() { var spliced = new SplicedText(); spliced.Set(new StringSource("abcdef"), 2, 4, "XYZ"); char[] scratch = null; StringView view = spliced.Slice(3, 2, ref scratch); CheckEqual("YZ", view.ToString()); CheckNull(scratch); } public void ARangeBeforeOrAfterTheChangedPart_CopiesNothing() { var spliced = new SplicedText(); spliced.Set(new StringSource("abcdef"), 2, 4, "XYZ"); char[] scratch = null; CheckEqual("ab", spliced.Slice(0, 2, ref scratch).ToString()); CheckEqual("ef", spliced.Slice(5, 2, ref scratch).ToString()); CheckNull(scratch); } public void ARangeAcrossTheChangedPart_FillsTheScratch() { var spliced = new SplicedText(); spliced.Set(new StringSource("abcdef"), 2, 4, "XYZ"); char[] scratch = null; CheckEqual("bXYZe", spliced.Slice(1, 5, ref scratch).ToString()); CheckEqual(5, scratch.Length); } public void ARangeAcrossTheChangedPart_KeepsAScratchThatIsBigEnough() { var spliced = new SplicedText(); spliced.Set(new StringSource("abcdef"), 2, 4, "XYZ"); var scratch = new char[20]; spliced.Slice(1, 5, ref scratch); CheckEqual(20, scratch.Length); } public void ARangePastTheEnd_Throws() { var spliced = new SplicedText(); spliced.Set(new StringSource("abc"), 1, 2, "xy"); char[] scratch = null; CheckThrow(typeof(ArgumentOutOfRangeException)); spliced.Slice(3, 2, ref scratch); } } // The string source is the reference, since its own tests check it against the paged text. public class AgreesWithAStringSourceTests : TestFixture { public void OnTheLineQueriesAtEveryIndex() { foreach (Case splice in Cases(pagedOnly: true, longestInsertion: 100)) { int step = 1; if (splice.ExpectedText.Length > 120) { step = 31; } for (int index = 0; index <= splice.ExpectedText.Length; index += step) { CheckEqual(splice.Expected.StartOfLine(index), splice.Spliced.StartOfLine(index)); CheckEqual(splice.Expected.EndOfLine(index), splice.Spliced.EndOfLine(index)); CheckEqual(splice.Expected.LengthAt(index), splice.Spliced.LengthAt(index)); CheckEqual(splice.Expected.LengthBefore(index), splice.Spliced.LengthBefore(index)); CheckEqual(splice.Expected.LineNumberOf(index), splice.Spliced.LineNumberOf(index)); } } } public void OnTheLineQueriesInLongLines() { foreach (Case splice in Cases(includeLongLines: true)) { int step = 37; for (int index = 0; index <= splice.ExpectedText.Length; index += step) { CheckEqual(splice.Expected.StartOfLine(index), splice.Spliced.StartOfLine(index)); CheckEqual(splice.Expected.EndOfLine(index), splice.Spliced.EndOfLine(index)); CheckEqual(splice.Expected.LengthAt(index), splice.Spliced.LengthAt(index)); CheckEqual(splice.Expected.LengthBefore(index), splice.Spliced.LengthBefore(index)); } } } public void OnTheLineCountAndTheStartOfEachLine() { foreach (Case splice in Cases()) { CheckEqual(splice.Expected.HardLineCount, splice.Spliced.HardLineCount); for (int line = 0; line < splice.Expected.HardLineCount; ++line) { CheckEqual(splice.Expected.StartOfLineNumber(line), splice.Spliced.StartOfLineNumber(line)); } } } public static readonly string[] BreakyTexts = { "a\r\nb\r\n\nc\r", "\r\n", "\n\r", "\r\r\n\n", "ab", "", }; public static readonly string[] BreakyInsertions = { "", "x", "\r", "\n", "\r\n", "\n\r", "a\nb" }; public void OnTheLineCountAndNumbersAtEverySpliceAmongBreaks() { foreach (string whole in BreakyTexts) { for (int start = 0; start <= whole.Length; ++start) { for (int end = start; end <= whole.Length; ++end) { foreach (string inserted in BreakyInsertions) { Case splice = Splice(PagedTextTests.Paged(whole), whole, start, end, inserted); CheckEqual(splice.Expected.HardLineCount, splice.Spliced.HardLineCount); for (int index = 0; index <= splice.ExpectedText.Length; ++index) { CheckEqual(splice.Expected.LineNumberOf(index), splice.Spliced.LineNumberOf(index)); } for (int line = 0; line < splice.Expected.HardLineCount; ++line) { CheckEqual(splice.Expected.StartOfLineNumber(line), splice.Spliced.StartOfLineNumber(line)); } } } } } } [Thorough] public void OnTheLineCountAndNumbersInATextOfManyLines() { string whole = string.Concat(System.Linq.Enumerable.Repeat("ab\r\ncd\nef\r", 40)); foreach (int start in new[] { 0, 7, 100, 199, 200, 201, 399, 400 }) { foreach (string inserted in BreakyInsertions) { Case splice = Splice(PagedTextTests.Paged(whole), whole, start, Math.Min(400, start + 9), inserted); CheckEqual(splice.Expected.HardLineCount, splice.Spliced.HardLineCount); for (int index = 0; index <= splice.ExpectedText.Length; index += 3) { CheckEqual(splice.Expected.LineNumberOf(index), splice.Spliced.LineNumberOf(index)); } for (int line = 0; line < splice.Expected.HardLineCount; line += 3) { CheckEqual(splice.Expected.StartOfLineNumber(line), splice.Spliced.StartOfLineNumber(line)); } } } } public void AfterTheSpliceIsSetAgain() { var spliced = new SplicedText(); var text = new StringSource("one\ntwo"); spliced.Set(text, 0, 3, "1\n2\n3"); CheckEqual(4, spliced.HardLineCount); spliced.Set(text, 0, 3, "1"); CheckEqual(2, spliced.HardLineCount); } } public class StartOfLineTests : TestFixture { public void AnIndexOutside_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SplicedText().StartOfLine(1); } } public class EndOfLineTests : TestFixture { public void AnIndexOutside_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SplicedText().EndOfLine(-1); } } public class LengthAtTests : TestFixture { public void AnIndexOutside_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SplicedText().LengthAt(1); } } public class LengthBeforeTests : TestFixture { public void AnIndexOutside_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SplicedText().LengthBefore(1); } } public class LineNumberOfTests : TestFixture { public void AnIndexOutside_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SplicedText().LineNumberOf(1); } } public class StartOfLineNumberTests : TestFixture { public void PastTheLastLine_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SplicedText().StartOfLineNumber(1); } public void Negative_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new SplicedText().StartOfLineNumber(-1); } } } }