using System;
using Amphora;
using Annulus;
using Glass.Editing;
using Glass.Typesetting;
using UnitTestSharp;
using Glass.UnitTests.Editing;
namespace Glass.UnitTests.Typesetting
{
public class TextViewportTests : TestFixture
{
// Seven hard lines, starting at 0, 4, 11, 14, 19, 25 and 33.
public const string SevenLines = "abc\nabcdef\nab\nabcd\nabcde\nabcdefg\nab";
public static readonly TextMaterialStyle Style = new TextMaterialStyle
{
Font = LineWrapperTests.FontWithTenPixelLines,
};
public static TextViewport NewViewport(bool multiline, int width = 100, int? height = null)
{
var viewport = new TextViewport();
viewport.Configure(Style, multiline, width, height);
return viewport;
}
// Three hard lines starting at 0, 3 and 24, the middle one 200 wide.
public const string WideLines = "ab\nabcdefghijklmnopqrst\nabc";
public static TextViewport NewSideways(Alignment.Horizontal alignment = Alignment.Horizontal.Left,
int width = 100)
{
var viewport = new TextViewport();
viewport.Configure(Style with { HorizontalAlignment = alignment }, multiline: true, fitWidth: width,
fitHeight: 30, wrap: false, caretWidth: 2);
return viewport;
}
public static BandsMaterial NewBands()
{
return new BandsMaterial(new SolidColorMaterial());
}
// Hard lines of several shapes among short ones, each past the long line length of NewChunked.
public static readonly string[] LongLineTexts =
{
VisualLineIndexTests.Repeat("abcd efgh ", 36),
"ab\n" + VisualLineIndexTests.Repeat("abcd efgh ", 20) + "\ncd\n"
+ VisualLineIndexTests.Repeat("ij kl mn ", 24) + "\nef",
VisualLineIndexTests.Repeat("abcdefghij", 30),
VisualLineIndexTests.Repeat("ab cd ", 24),
VisualLineIndexTests.Repeat("abcd-efgh ", 30),
"x\n" + VisualLineIndexTests.Repeat("abcd efgh ", 20) + "\n\n"
+ VisualLineIndexTests.Repeat("abcd efgh ", 20),
VisualLineIndexTests.Repeat("abcd efgh ", 8) + "\nab\ncd\nef\ngh\n"
+ VisualLineIndexTests.Repeat("abcd efgh ", 8) + "\nij",
};
// Lays long lines out a few chars at a time, to be checked against NewViewport, which lays out the whole line.
// A limit small enough that a line of a few thousand chars is left partly uncounted.
public const int UncountedSynchronousChars = 3000;
public static TextViewport NewChunked(int width, int height,
int synchronousChars = VisualLineIndex.DefaultSynchronousChars)
{
var viewport = new TextViewport(blockChars: 16, longLineChars: 48, stretchChars: 24,
synchronousChars: synchronousChars);
viewport.Configure(Style, multiline: true, width, height);
return viewport;
}
// Letters a million pixels wide, so a line of a few thousand is wider than an int.
public static readonly TextMaterialStyle WideStyle = new TextMaterialStyle
{
Font = VisualLineIndexTests.WidthChunkedLinesTests.WideFont,
};
// Lays a wide line out around the view only, to be checked against NewSideways, which lays the whole line out.
public static TextViewport NewSidewaysChunked(int width, TextMaterialStyle style = null,
int synchronousChars = VisualLineIndex.DefaultSynchronousChars)
{
var viewport = new TextViewport(blockChars: 16, longLineChars: 48, stretchChars: 24,
synchronousChars: synchronousChars);
viewport.Configure(style ?? Style, multiline: true, fitWidth: width, fitHeight: 30, wrap: false,
caretWidth: 2);
return viewport;
}
public static TextViewport NewAlignedChunked(Alignment.Horizontal alignment, int width,
TextMaterialStyle style = null, int synchronousChars = VisualLineIndex.DefaultSynchronousChars)
{
return NewSidewaysChunked(width, (style ?? Style) with { HorizontalAlignment = alignment },
synchronousChars);
}
// What a sideways window shows, as the view sees it whether or not the window places rows by origin.
public static string SidewaysViewOf(TextViewport viewport, ITextSource text, int caret, bool withWidth = true)
{
CaretPosition at = viewport.CaretAt(caret, atLineEnd: false);
long shift = viewport.HasRowOrigins ? 0 : viewport.ScrollX;
var returnMe = new System.Text.StringBuilder();
returnMe.Append($"first {viewport.FirstVisible}, scroll {viewport.ScrollX}, ");
if (withWidth)
{
returnMe.Append($"width {viewport.ContentWidth(text)}, ");
}
// A caret outside the view is not drawn, and a window lays out only a little past the view.
long across = at.X - shift;
string where = across < 0 || across > viewport.VisibleWidth ? "off" : across.ToString();
returnMe.Append($"top {viewport.TopLine(text)}, caret line {at.Line} at {where},{at.Y}, ");
returnMe.Append($"in {viewport.IsInWindow(caret, false)}, ");
foreach (int x in new[] { -5, 0, 20, 60, 99, 120 })
{
int index = viewport.IndexAt(x, 10, out bool atLineEnd, out int pastEdge);
returnMe.Append($"at {x}: {index} {pastEdge}, ");
}
BandsMaterial bands = NewBands();
viewport.AddBands(bands, caret, Math.Min(text.Length, caret + 40));
// A band reaches only as far as the part of the line laid out, so only the view's part is compared.
for (int i = 0; i < bands.Count; ++i)
{
Rect band = bands[i];
long left = Math.Max(0, (int)band.Left - shift);
long right = Math.Min(viewport.VisibleWidth, (int)band.Right - shift);
if (right > left)
{
returnMe.Append($"band {left}..{right} at {(int)band.Top}, ");
}
}
return returnMe.ToString();
}
// Compares what a sideways window shows when it lays out a wide line around the view with what it shows laying
// out the whole line, after each of several scrolls to a caret.
public static string SidewaysScrollToCaretDifferences(string text, int width,
Alignment.Horizontal alignment = Alignment.Horizontal.Left)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewAlignedChunked(alignment, width);
TextViewport whole = NewSideways(alignment, width);
CountAll(chunked, paged);
for (int step = 0; step <= 2 * text.Length; step += 13)
{
int caret = step;
if (step > text.Length)
{
caret = 2 * text.Length - step;
}
chunked.ScrollToCaret(paged, caret, caretAtLineEnd: false);
whole.ScrollToCaret(paged, caret, caretAtLineEnd: false);
string expected = SidewaysViewOf(whole, paged, caret);
string actual = SidewaysViewOf(chunked, paged, caret);
if (expected != actual)
{
return $"caret {caret}: {actual} not {expected}";
}
}
return "";
}
// The same comparison after scrolling to a series of offsets across the text.
public static string SidewaysScrollToXDifferences(string text, int width,
Alignment.Horizontal alignment = Alignment.Horizontal.Left)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewAlignedChunked(alignment, width);
TextViewport whole = NewSideways(alignment, width);
CountAll(chunked, paged);
long widest = whole.ContentWidth(paged);
for (long offset = 0; offset <= widest + 50; offset += 41)
{
chunked.ScrollToX(paged, offset);
whole.ScrollToX(paged, offset);
int caret = Math.Min(text.Length, (int)(offset / 9));
string expected = SidewaysViewOf(whole, paged, caret);
string actual = SidewaysViewOf(chunked, paged, caret);
if (expected != actual)
{
return $"offset {offset}: {actual} not {expected}";
}
}
return "";
}
// The same comparison for moves of the caret by lines, pages and to the edges of a line.
public static string SidewaysMoveDifferences(string text, int width,
Alignment.Horizontal alignment = Alignment.Horizontal.Left)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewAlignedChunked(alignment, width);
TextViewport whole = NewSideways(alignment, width);
CountAll(chunked, paged);
for (int caret = 0; caret <= text.Length; caret += 17)
{
foreach (bool forward in new[] { false, true })
{
foreach (int lines in new[] { 1, 2 })
{
int expected = whole.LineTarget(paged, caret, false, lines, forward, version: 1, out bool ea);
int actual = chunked.LineTarget(paged, caret, false, lines, forward, version: 1, out bool aa);
if (expected != actual || ea != aa)
{
return $"line target {caret} {forward} {lines}: {actual} {aa} not {expected} {ea}";
}
}
int expectedPage = whole.PageTarget(paged, caret, false, forward, version: 1, out bool epa);
int actualPage = chunked.PageTarget(paged, caret, false, forward, version: 1, out bool apa);
if (expectedPage != actualPage || epa != apa)
{
return $"page target {caret} {forward}: {actualPage} {apa} not {expectedPage} {epa}";
}
string expectedView = SidewaysViewOf(whole, paged, expectedPage);
string actualView = SidewaysViewOf(chunked, paged, actualPage);
if (expectedView != actualView)
{
return $"after the page target {caret} {forward}: {actualView} not {expectedView}";
}
int expectedEdge = whole.LineEdge(paged, caret, false, forward, out bool eea);
int actualEdge = chunked.LineEdge(paged, caret, false, forward, out bool aea);
if (expectedEdge != actualEdge || eea != aea)
{
return $"line edge {caret} {forward}: {actualEdge} {aea} not {expectedEdge} {eea}";
}
}
}
return "";
}
// The same comparison after scrolling by lines and to lines, and keeping the window valid.
public static string SidewaysLineScrollDifferences(string text, int width,
Alignment.Horizontal alignment = Alignment.Horizontal.Left)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewAlignedChunked(alignment, width);
TextViewport whole = NewSideways(alignment, width);
CountAll(chunked, paged);
int lines = chunked.TotalLines(paged);
for (int step = 0; step < 3 * lines; ++step)
{
string what;
if (step % 3 == 0)
{
what = $"to line {step / 3}";
chunked.ScrollToLine(paged, step / 3);
whole.ScrollToLine(paged, step / 3);
}
else if (step % 3 == 1)
{
what = "by one line";
chunked.ScrollByLines(paged, 1);
whole.ScrollByLines(paged, 1);
}
else
{
what = "revalidated";
chunked.ScrollToX(paged, 60 * step);
whole.ScrollToX(paged, 60 * step);
chunked.Revalidate(paged);
whole.Revalidate(paged);
}
string expected = SidewaysViewOf(whole, paged, whole.FirstVisible);
string actual = SidewaysViewOf(chunked, paged, chunked.FirstVisible);
if (expected != actual)
{
return $"{what}: {actual} not {expected}";
}
}
return "";
}
// Compares a sideways window that lays a wide line out around the view, under a composition spliced into the
// text, with one that lays out the whole text with the composition in it.
public static string SidewaysComposedDifferences(string text, int start, int end, string replacement,
int width = 100, Alignment.Horizontal alignment = Alignment.Horizontal.Left,
TextMaterialStyle style = null, bool referenceIsChunked = false)
{
PagedText paged = PagedTextTests.Paged(text);
var spliced = new SplicedText();
spliced.Set(paged, start, end, replacement);
string composed = text.Remove(start, end - start).Insert(start, replacement);
PagedText reference = PagedTextTests.Paged(composed);
TextMaterialStyle aligned = (style ?? Style) with { HorizontalAlignment = alignment };
TextViewport chunked = NewSidewaysChunked(width, aligned);
TextViewport whole;
if (referenceIsChunked)
{
whole = NewSidewaysChunked(width, aligned);
CountAll(whole, reference);
}
else
{
whole = new TextViewport();
whole.Configure(aligned, multiline: true, width, 30, wrap: false, caretWidth: 2);
}
CountAll(chunked, spliced);
for (int caret = 0; caret <= composed.Length; caret += Math.Max(11, composed.Length / 40))
{
chunked.ScrollToCaret(spliced, caret, caretAtLineEnd: false);
whole.ScrollToCaret(reference, caret, caretAtLineEnd: false);
string expected = SidewaysViewOf(whole, reference, caret);
string actual = SidewaysViewOf(chunked, spliced, caret);
if (expected != actual)
{
return $"caret {caret}: {actual} not {expected}";
}
}
long widest = whole.ContentWidth(reference);
for (long offset = 0; offset <= widest + 50; offset += Math.Max(53, widest / 25))
{
chunked.ScrollToX(spliced, offset);
whole.ScrollToX(reference, offset);
int caret = (int)Math.Min(composed.Length, offset * composed.Length / Math.Max(1, widest));
string expected = SidewaysViewOf(whole, reference, caret);
string actual = SidewaysViewOf(chunked, spliced, caret);
if (expected != actual)
{
return $"offset {offset}: {actual} not {expected}";
}
}
for (int caret = 0; caret <= composed.Length; caret += Math.Max(17, composed.Length / 30))
{
foreach (bool forward in new[] { false, true })
{
int expectedMove = whole.LineTarget(reference, caret, false, 1, forward, 1, out bool ea);
int actualMove = chunked.LineTarget(spliced, caret, false, 1, forward, 1, out bool aa);
int expectedPage = whole.PageTarget(reference, caret, false, forward, 1, out bool epa);
int actualPage = chunked.PageTarget(spliced, caret, false, forward, 1, out bool apa);
int expectedEdge = whole.LineEdge(reference, caret, false, forward, out bool eea);
int actualEdge = chunked.LineEdge(spliced, caret, false, forward, out bool aea);
if (expectedMove != actualMove || ea != aa || expectedPage != actualPage || epa != apa
|| expectedEdge != actualEdge || eea != aea)
{
return $"moves from {caret} {forward}: {actualMove} {actualPage} {actualEdge} "
+ $"not {expectedMove} {expectedPage} {expectedEdge}";
}
}
}
return "";
}
public static string ViewOf(TextViewport viewport, ITextSource text, int caret, bool atLineEnd)
{
int index = viewport.IndexAt(20, 10, out bool indexAtLineEnd, out int pastEdge);
return $"first {viewport.FirstVisible}, top {viewport.TopLine(text)}, "
+ $"caret {viewport.CaretAt(caret, atLineEnd)}, in window {viewport.IsInWindow(caret, atLineEnd)}, "
+ $"index at {index} {indexAtLineEnd} {pastEdge}";
}
public static string ScrollToCaretDifferences(string text, int width, int height = 30)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewChunked(width, height);
TextViewport whole = NewViewport(multiline: true, width, height);
for (int step = 0; step <= 2 * text.Length; step += 9)
{
int caret = step;
if (step > text.Length)
{
caret = 2 * text.Length - step;
}
foreach (bool atLineEnd in new[] { false, true })
{
chunked.ScrollToCaret(paged, caret, atLineEnd);
whole.ScrollToCaret(paged, caret, atLineEnd);
string expected = ViewOf(whole, paged, caret, atLineEnd);
string actual = ViewOf(chunked, paged, caret, atLineEnd);
if (expected != actual)
{
return $"caret {caret} {atLineEnd}: {actual} not {expected}";
}
}
}
return "";
}
public static string LineEdgeDifferences(string text, int width)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewChunked(width, height: 30);
TextViewport whole = NewViewport(multiline: true, width, height: 30);
for (int caret = 0; caret <= text.Length; caret += 5)
{
foreach (bool atLineEnd in new[] { false, true })
{
foreach (bool end in new[] { false, true })
{
int expected = whole.LineEdge(paged, caret, atLineEnd, end, out bool expectedAtLineEnd);
int actual = chunked.LineEdge(paged, caret, atLineEnd, end, out bool actualAtLineEnd);
if (expected != actual || expectedAtLineEnd != actualAtLineEnd)
{
return $"caret {caret} {atLineEnd} end {end}: {actual} {actualAtLineEnd} "
+ $"not {expected} {expectedAtLineEnd}";
}
}
}
}
return "";
}
public static string LineTargetDifferences(string text, int width)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewChunked(width, height: 30);
TextViewport whole = NewViewport(multiline: true, width, height: 30);
for (int caret = 0; caret <= text.Length; caret += 13)
{
foreach (int lines in new[] { 1, 3, 9, 1000 })
{
foreach (bool forward in new[] { false, true })
{
int expected = whole.LineTarget(paged, caret, false, lines, forward, 1, out bool expectedAt);
int actual = chunked.LineTarget(paged, caret, false, lines, forward, 1, out bool actualAt);
if (expected != actual || expectedAt != actualAt)
{
return $"caret {caret} lines {lines} {forward}: {actual} {actualAt} "
+ $"not {expected} {expectedAt}";
}
}
}
}
return "";
}
public static string LineTargetRunDifferences(string text, int width)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewChunked(width, height: 30);
TextViewport whole = NewViewport(multiline: true, width, height: 30);
foreach (bool forward in new[] { true, false })
{
int expectedCaret = 0;
int actualCaret = 0;
bool expectedAt = false;
bool actualAt = false;
if (!forward)
{
expectedCaret = text.Length;
actualCaret = text.Length;
}
for (int move = 0; move < 40; ++move)
{
expectedCaret = whole.LineTarget(paged, expectedCaret, expectedAt, 1, forward, 1, out expectedAt);
actualCaret = chunked.LineTarget(paged, actualCaret, actualAt, 1, forward, 1, out actualAt);
whole.KeepGoalColumn(1);
chunked.KeepGoalColumn(1);
if (expectedCaret != actualCaret || expectedAt != actualAt)
{
return $"move {move} {forward}: {actualCaret} {actualAt} not {expectedCaret} {expectedAt}";
}
}
}
return "";
}
public static string PageTargetDifferences(string text, int width)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewChunked(width, height: 30);
TextViewport whole = NewViewport(multiline: true, width, height: 30);
chunked.ScrollToCaret(paged, 0, false);
whole.ScrollToCaret(paged, 0, false);
int caret = 0;
bool atLineEnd = false;
for (int move = 0; move < 24; ++move)
{
bool forward = move < 12;
int expected = whole.PageTarget(paged, caret, atLineEnd, forward, 1, out bool expectedAt);
int actual = chunked.PageTarget(paged, caret, atLineEnd, forward, 1, out bool actualAt);
string expectedView = $"{expected} {expectedAt} first {whole.FirstVisible} top {whole.TopLine(paged)}";
string actualView = $"{actual} {actualAt} first {chunked.FirstVisible} top {chunked.TopLine(paged)}";
if (expectedView != actualView)
{
return $"move {move} from {caret}: {actualView} not {expectedView}";
}
caret = expected;
atLineEnd = expectedAt;
whole.KeepGoalColumn(1);
chunked.KeepGoalColumn(1);
}
return "";
}
public static string ScrollToLineDifferences(string text, int width)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewChunked(width, height: 30);
TextViewport whole = NewViewport(multiline: true, width, height: 30);
if (whole.TotalLines(paged) != chunked.TotalLines(paged))
{
return $"total {chunked.TotalLines(paged)} not {whole.TotalLines(paged)}";
}
for (int step = 0; step <= 2 * whole.TotalLines(paged) + 4; ++step)
{
int line = step;
if (step > whole.TotalLines(paged) + 2)
{
line = 2 * whole.TotalLines(paged) + 4 - step;
}
chunked.ScrollToLine(paged, line);
whole.ScrollToLine(paged, line);
string expected = $"first {whole.FirstVisible}, top {whole.TopLine(paged)}";
string actual = $"first {chunked.FirstVisible}, top {chunked.TopLine(paged)}";
if (expected != actual)
{
return $"line {line}: {actual} not {expected}";
}
}
return "";
}
public static string RevalidateDifferences(string text, int width)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewChunked(width, height: 30);
TextViewport whole = NewViewport(multiline: true, width, height: 30);
chunked.ScrollToCaret(paged, text.Length, false);
whole.ScrollToCaret(paged, text.Length, false);
int removed = text.Length / 2;
paged.Replace(text.Length - removed, removed, "");
chunked.NoteEdit(paged, text.Length - removed, removed, 0);
whole.NoteEdit(paged, text.Length - removed, removed, 0);
chunked.Revalidate(paged);
whole.Revalidate(paged);
string expected = $"first {whole.FirstVisible}, top {whole.TopLine(paged)}";
string actual = $"first {chunked.FirstVisible}, top {chunked.TopLine(paged)}";
return expected == actual ? "" : $"{actual} not {expected}";
}
public class CtorTests : TestFixture
{
public void AStretchOfNoChars_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
new TextViewport(stretchChars: 0);
}
}
public class CharsMeasuredTests : TestFixture
{
public static readonly string HugeLine = VisualLineIndexTests.Repeat("abcd efgh ", 15000);
public void ScrollingAboutInABoxOfOneLine_MeasuresAWindowNotTheText()
{
PagedText paged = PagedTextTests.Paged(HugeLine);
TextViewport viewport = NewOneLine(Alignment.Horizontal.Left, width: 100, reach: 128);
foreach (int caret in new[] { 0, HugeLine.Length, HugeLine.Length / 2, 5, HugeLine.Length - 3 })
{
viewport.ScrollToCaret(paged, caret, caretAtLineEnd: false);
}
CheckTrue(viewport.CharsMeasured < 6000);
}
public void TypingInABoxOfOneLine_MeasuresAWindowNotTheText()
{
PagedText paged = PagedTextTests.Paged(HugeLine);
TextViewport viewport = NewOneLine(Alignment.Horizontal.Left, width: 100, reach: 128);
viewport.ScrollToCaret(paged, HugeLine.Length / 2, caretAtLineEnd: false);
long before = viewport.CharsMeasured;
paged.Replace(HugeLine.Length / 2, 0, "x");
viewport.ScrollToCaret(paged, HugeLine.Length / 2 + 1, caretAtLineEnd: false);
CheckTrue(viewport.CharsMeasured - before < 1500);
}
public const int Bound = 1500;
// Long enough that a viewport with UncountedSynchronousChars leaves the end of it uncounted.
public static readonly string UncountedLine = VisualLineIndexTests.Repeat("abcd efgh ", 2000);
public static readonly string OneLine = VisualLineIndexTests.Repeat("abcd efgh ", 4000);
public static TextViewport NewCounted(PagedText paged)
{
TextViewport returnMe = NewChunked(width: 100, height: 30);
CountAll(returnMe, paged);
return returnMe;
}
public void StartsAtNothing()
{
CheckEqual(0L, new TextViewport().CharsMeasured);
}
public void ScrollingToACaretFarIntoALongLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewCounted(paged);
viewport.ScrollToCaret(paged, 30000, caretAtLineEnd: false);
CheckTrue(viewport.CharsMeasured < Bound);
}
public void ScrollingToACaretNearAComposition_InALongLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewCounted(paged);
var spliced = new SplicedText();
spliced.Set(paged, 20000, 20003, "xyz");
foreach (int caret in new[] { 20003, 20050, 5, 30000, 20000 })
{
viewport.ScrollToCaret(spliced, caret, caretAtLineEnd: false);
}
CheckTrue(viewport.CharsMeasured < 5 * Bound);
}
public void MovingByLinesNearAComposition_InALongLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewCounted(paged);
var spliced = new SplicedText();
spliced.Set(paged, 20000, 20003, "xyz");
long before = viewport.CharsMeasured;
viewport.LineTarget(spliced, 20003, false, lines: 5, forward: true, version: 1, out _);
viewport.LineTarget(spliced, 20003, false, lines: 5, forward: false, version: 1, out _);
CheckTrue(viewport.CharsMeasured - before < Bound);
}
public void MovingByLinesFarIntoALongLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewCounted(paged);
long before = viewport.CharsMeasured;
viewport.LineTarget(paged, 30000, false, lines: 5, forward: true, version: 1, out _);
viewport.LineTarget(paged, 30000, false, lines: 5, forward: false, version: 1, out _);
CheckTrue(viewport.CharsMeasured - before < Bound);
}
public void MovingByPagesFarIntoALongLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewCounted(paged);
viewport.ScrollToCaret(paged, 30000, caretAtLineEnd: false);
long before = viewport.CharsMeasured;
viewport.PageTarget(paged, 30000, false, forward: true, version: 1, out _);
viewport.PageTarget(paged, 30000, false, forward: false, version: 1, out _);
CheckTrue(viewport.CharsMeasured - before < Bound);
}
public void FindingTheEdgesOfALineFarIntoALongLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewCounted(paged);
long before = viewport.CharsMeasured;
viewport.LineEdge(paged, 30000, false, end: false, out _);
viewport.LineEdge(paged, 30000, false, end: true, out _);
CheckTrue(viewport.CharsMeasured - before < Bound);
}
public void ScrollingToALineFarIntoALongLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewCounted(paged);
long before = viewport.CharsMeasured;
viewport.ScrollToLine(paged, viewport.TotalLines(paged) / 2);
CheckTrue(viewport.CharsMeasured - before < Bound);
}
public static TextViewport NewWide()
{
return NewSidewaysChunked(100);
}
public void ScrollingAboutAWideLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewWide();
CountAll(viewport, paged);
long before = viewport.CharsMeasured;
foreach (int caret in new[] { 30000, 3, OneLine.Length, OneLine.Length / 2 })
{
viewport.ScrollToCaret(paged, caret, caretAtLineEnd: false);
}
viewport.ScrollToX(paged, 140000);
viewport.ScrollToX(paged, 5);
CheckTrue(viewport.CharsMeasured - before < Bound);
}
public void ScrollingAboutAWideLineUnderComposition_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewWide();
CountAll(viewport, paged);
var spliced = new SplicedText();
spliced.Set(paged, 20000, 20003, "xyz");
long before = viewport.CharsMeasured;
foreach (int caret in new[] { 20003, 30000, 3, OneLine.Length, 20000 })
{
viewport.ScrollToCaret(spliced, caret, caretAtLineEnd: false);
}
viewport.ScrollToX(spliced, 140000);
viewport.ScrollToX(spliced, 5);
CheckTrue(viewport.CharsMeasured - before < 2 * Bound);
}
public void MovingAboutAWideLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine + "\n" + OneLine);
TextViewport viewport = NewWide();
CountAll(viewport, paged);
viewport.ScrollToCaret(paged, 30000, caretAtLineEnd: false);
long before = viewport.CharsMeasured;
int target = viewport.LineTarget(paged, 30000, false, lines: 1, forward: true, version: 1, out _);
viewport.LineEdge(paged, target, false, end: true, out _);
viewport.PageTarget(paged, target, false, forward: false, version: 1, out _);
CheckTrue(viewport.CharsMeasured - before < Bound);
}
public void TypingInAWideLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewWide();
CountAll(viewport, paged);
viewport.ScrollToCaret(paged, 20000, caretAtLineEnd: false);
long before = viewport.CharsMeasured;
paged.Replace(20000, 0, "x");
viewport.NoteEdit(paged, 20000, 0, 1);
viewport.ScrollToCaret(paged, 20001, caretAtLineEnd: false);
CheckTrue(viewport.CharsMeasured - before < Bound);
}
public void TypingInALongLine_MeasuresAWindowNotTheLine()
{
PagedText paged = PagedTextTests.Paged(OneLine);
TextViewport viewport = NewCounted(paged);
viewport.ScrollToCaret(paged, 20000, caretAtLineEnd: false);
long before = viewport.CharsMeasured;
paged.Replace(20000, 0, "x");
viewport.NoteEdit(paged, 20000, 0, 1);
viewport.ScrollToCaret(paged, 20001, caretAtLineEnd: false);
CheckTrue(viewport.CharsMeasured - before < Bound);
}
}
public static string ScrollByLinesDifferences(string text, int width)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewChunked(width, height: 30);
TextViewport whole = NewViewport(multiline: true, width, height: 30);
foreach (int lines in new[] { 1, 3, 7, -2, 20, -9, 1, 60, -1, 400, -33, -1000, 5, 1000 })
{
chunked.ScrollByLines(paged, lines);
whole.ScrollByLines(paged, lines);
string expected = $"first {whole.FirstVisible}, top {whole.TopLine(paged)}";
string actual = $"first {chunked.FirstVisible}, top {chunked.TopLine(paged)}";
if (expected != actual)
{
return $"by {lines}: {actual} not {expected}";
}
}
return "";
}
public class ScrollByLinesTests : TestFixture
{
public void MovesDownByThatManyLines()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollByLines(SevenLines, 2);
CheckEqual(11, viewport.FirstVisible);
CheckEqual(2, viewport.TopLine(SevenLines));
}
public void MovesUpByThatManyLines()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollByLines(SevenLines, 3);
viewport.ScrollByLines(SevenLines, -2);
CheckEqual(1, viewport.TopLine(SevenLines));
}
public void ByNothing_StaysPut()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollByLines(SevenLines, 2);
viewport.ScrollByLines(SevenLines, 0);
CheckEqual(2, viewport.TopLine(SevenLines));
}
public void PastTheEnd_StopsWhereTheLastLinesFillTheBox()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollByLines(SevenLines, 100);
CheckEqual(4, viewport.TopLine(SevenLines));
}
public void PastTheStart_StopsAtTheFirstLine()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollByLines(SevenLines, 3);
viewport.ScrollByLines(SevenLines, -100);
CheckEqual(0, viewport.FirstVisible);
}
public void ByTheWholeRangeOfAnInt_Stops()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollByLines(SevenLines, int.MaxValue);
CheckEqual(4, viewport.TopLine(SevenLines));
viewport.ScrollByLines(SevenLines, int.MinValue);
CheckEqual(0, viewport.FirstVisible);
}
public void MeasuresTheWindowItEndsIn()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollByLines(SevenLines, 1);
CheckEqual(4, viewport.FirstVisible);
CheckEqual(18, viewport.WindowEnd);
}
public void AWindowThatShowsAllLines_DoesNothing()
{
TextViewport viewport = NewViewport(multiline: true);
viewport.ScrollByLines(SevenLines, 3);
CheckEqual(0, viewport.FirstVisible);
}
public void AWindowOfOneLine_DoesNothing()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollByLines(SevenLines, 3);
CheckEqual(0, viewport.FirstVisible);
}
public void WithoutAFont_DoesNothing()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 30);
viewport.ScrollByLines(SevenLines, 3);
CheckEqual(0, viewport.FirstVisible);
}
[Thorough]
public void OnLongLines_MovesAsLayingOutTheWholeLineDoes()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", ScrollByLinesDifferences(text, width: 100));
CheckEqual("", ScrollByLinesDifferences(text, width: 35));
}
}
public void WhileTheLongLineIsNotCounted_MovesByExactlyThatManyLines()
{
PagedText paged = PagedTextTests.Paged(CharsMeasuredTests.UncountedLine);
VisualLineIndex index = VisualLineIndexTests.NewIndex(blockChars: 16, longLineChars: 48);
VisualLineIndexTests.CountAll(index, paged);
TextViewport viewport = NewChunked(width: 100, height: 30, UncountedSynchronousChars);
viewport.ScrollByLines(paged, 5);
int afterFive = viewport.FirstVisible;
viewport.ScrollByLines(paged, 1000);
CheckEqual(index.IndexOfRow(paged, 5), afterFive);
CheckEqual(index.IndexOfRow(paged, 1005), viewport.FirstVisible);
CheckFalse(viewport.IsCounted(paged));
}
public void OnALongLine_MeasuresRowsNotTheLine()
{
PagedText paged = PagedTextTests.Paged(CharsMeasuredTests.OneLine);
TextViewport viewport = CharsMeasuredTests.NewCounted(paged);
viewport.ScrollToLine(paged, viewport.TotalLines(paged) / 2);
long before = viewport.CharsMeasured;
viewport.ScrollByLines(paged, 3);
viewport.ScrollByLines(paged, -3);
CheckTrue(viewport.CharsMeasured - before < CharsMeasuredTests.Bound);
}
}
// Lines of one hard line in several shapes, each far wider than the view.
public static readonly string[] SingleLineTexts =
{
VisualLineIndexTests.Repeat("abcd efgh ", 40),
VisualLineIndexTests.Repeat("abcdefghij", 40),
VisualLineIndexTests.Repeat("ab cd ", 30),
VisualLineIndexTests.Repeat("iiiWWWii mm ", 30),
VisualLineIndexTests.Repeat("ab" + char.ConvertFromUtf32(0x20BB7) + " ", 40),
VisualLineIndexTests.Repeat("a" + (char)0x301 + "bc d" + (char)0x301 + " ", 40),
VisualLineIndexTests.Repeat("" + (char)0x4F60 + (char)0x597D, 80),
};
public static TextViewport NewOneLine(Alignment.Horizontal alignment, int width, int reach)
{
var viewport = new TextViewport(stretchChars: reach);
viewport.Configure(Style with { HorizontalAlignment = alignment }, multiline: false, width,
fitHeight: null);
return viewport;
}
public static string OneLineViewOf(TextViewport viewport, int caret, bool atLineEnd)
{
var returnMe = new System.Text.StringBuilder();
returnMe.Append($"first {viewport.FirstVisible}, caret {viewport.CaretAt(caret, atLineEnd)}");
foreach (int x in new[] { 0, 20, 50, 99 })
{
int index = viewport.IndexAt(x, 0, out bool indexAtLineEnd, out int pastEdge);
returnMe.Append($", {index} {indexAtLineEnd} {pastEdge}");
}
return returnMe.ToString();
}
public static string OneLineDifferences(string text, Alignment.Horizontal alignment, int width)
{
PagedText paged = PagedTextTests.Paged(text);
TextViewport bounded = NewOneLine(alignment, width, reach: 8);
TextViewport whole = NewOneLine(alignment, width, reach: TextViewport.DefaultStretchChars);
for (int step = 0; step <= 2 * text.Length; step += 7)
{
int caret = step;
if (step > text.Length)
{
caret = 2 * text.Length - step;
}
if (caret > 0 && caret < text.Length && char.IsLowSurrogate(text[caret]))
{
continue;
}
foreach (bool atLineEnd in new[] { false, true })
{
bounded.ScrollToCaret(paged, caret, atLineEnd);
whole.ScrollToCaret(paged, caret, atLineEnd);
string expected = OneLineViewOf(whole, caret, atLineEnd);
string actual = OneLineViewOf(bounded, caret, atLineEnd);
if (expected != actual)
{
return $"caret {caret} {atLineEnd}: {actual} not {expected}";
}
}
}
return "";
}
public class ConfigureTests : TestFixture
{
public void RejectsNoStyle()
{
CheckThrow(typeof(System.ArgumentNullException));
new TextViewport().Configure(null, multiline: true, fitWidth: 100, fitHeight: null);
}
public void WithoutAFont_HasNoLineHeightAndOneVisibleLine()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 100);
CheckEqual(0, viewport.LineHeight);
CheckEqual(1, viewport.VisibleLines);
}
public void TakesLineHeightFromTheFont()
{
CheckEqual(10, NewViewport(multiline: true).LineHeight);
}
public void SingleLine_ShowsOneLineHoweverTallTheBox()
{
CheckEqual(1, NewViewport(multiline: false, height: 100).VisibleLines);
}
public void Multiline_ShowsTheLinesThatFit()
{
CheckEqual(3, NewViewport(multiline: true, height: 35).VisibleLines);
}
public void Multiline_ShowsOneLineInABoxShorterThanOne()
{
CheckEqual(1, NewViewport(multiline: true, height: 4).VisibleLines);
}
public void Multiline_WithNoHeightLimit_ShowsEveryLine()
{
CheckEqual(int.MaxValue, NewViewport(multiline: true).VisibleLines);
}
}
public class ResetTests : TestFixture
{
public void ScrollsBackToTheStart()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
viewport.Reset();
CheckEqual(0, viewport.FirstVisible);
}
}
public class LimitToTests : TestFixture
{
public void ScrollsBackToAnIndexBeforeTheWindow()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
viewport.LimitTo(index: 6);
CheckEqual(6, viewport.FirstVisible);
}
public void LeavesAWindowThatStartsBeforeTheIndex()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
viewport.LimitTo(index: 30);
CheckEqual(19, viewport.FirstVisible);
}
}
public class ScrollToCaretTests : TestFixture
{
[Thorough]
public void InABoxOfOneLine_ShowsTheSameAsMeasuringTheWholeText()
{
foreach (string text in SingleLineTexts)
{
CheckEqual("", OneLineDifferences(text, Alignment.Horizontal.Left, width: 100));
}
}
[Thorough]
public void InABoxOfOneLine_ShowsTheSameAsMeasuringTheWholeText_AtNarrowWidth()
{
foreach (string text in SingleLineTexts)
{
CheckEqual("", OneLineDifferences(text, Alignment.Horizontal.Left, width: 35));
}
}
public void InABoxOfOneLine_ShowsTheSameAsMeasuringTheWholeText_Centred()
{
foreach (string text in SingleLineTexts)
{
CheckEqual("", OneLineDifferences(text, Alignment.Horizontal.Center, width: 100));
}
}
[Thorough]
public void InABoxOfOneLine_ShowsTheSameAsMeasuringTheWholeText_RightAligned()
{
foreach (string text in SingleLineTexts)
{
CheckEqual("", OneLineDifferences(text, Alignment.Horizontal.Right, width: 100));
}
}
public void OnLongLines_ShowTheSameLinesAsLayingOutTheWholeLine_WhenARowIsLongerThanAStretch()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", ScrollToCaretDifferences(text, width: 600));
}
}
[Thorough]
public void OnLongLines_ShowTheSameLinesAsLayingOutTheWholeLine_InATallBox()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", ScrollToCaretDifferences(text, width: 100, height: 90));
}
}
public void OnLongLines_ShowTheSameLinesAsLayingOutTheWholeLine_InATallBoxAtNarrowWidth()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", ScrollToCaretDifferences(text, width: 35, height: 150));
}
}
public static readonly string ComposedLine = WideLineText;
public static readonly TextMaterialStyle KernedStyle = new TextMaterialStyle
{
Font = VisualLineIndexTests.WidthChunkedLinesTests.Kerned,
};
[Thorough]
public void SidewaysOnAWideLineUnderComposition_InsertingInTheMiddle_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 100, 100, "xy"));
}
public void SidewaysOnAWideLineUnderComposition_ReplacingSomeChars_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 100, 104, "wxyz!"));
}
public void SidewaysOnAWideLineUnderComposition_ReplacingManyChars_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 50, 150, "q"));
}
public void SidewaysOnAWideLineUnderComposition_AtTheStartOfTheLine_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 3, 3, "xy"));
}
public static long MeasuredOnScrollingToTheStart(string text, int height)
{
TextViewport viewport = NewChunked(width: 100, height);
viewport.ScrollToCaret(PagedTextTests.Paged(text), caret: 0, caretAtLineEnd: false);
return viewport.CharsMeasured;
}
public void ALongLineThatLaysOutInOneStretch_UsesUpAsManyRowsOfTheBoxAsItHas()
{
string sixRows = VisualLineIndexTests.Repeat("abcd efgh ", 6) + "\n";
CheckEqual(MeasuredOnScrollingToTheStart(sixRows + VisualLineIndexTests.Repeat("abcdefghij\n", 18),
height: 240), MeasuredOnScrollingToTheStart(sixRows
+ VisualLineIndexTests.Repeat("abcdefghij\n", 100), height: 240));
}
public void SidewaysOnAWideLineUnderComposition_AtTheEndOfTheLine_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 203, 203, "xy"));
}
public void SidewaysOnAWideLineUnderComposition_AddingSpacesAtTheEnd_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 203, 203, " "));
}
[Thorough]
public void SidewaysOnAWideLineUnderComposition_WhenTheLineEndsInSpaces_ShowsWhatLayingOutTheWholeTextDoes()
{
string text = "ab\n" + VisualLineIndexTests.Repeat("abcd efgh ", 20) + " \ncd";
CheckEqual("", SidewaysComposedDifferences(text, 100, 100, "xy"));
CheckEqual("", SidewaysComposedDifferences(text, 203, 205, "q"));
}
[Thorough]
public void SidewaysOnAShortLineUnderComposition_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 1, 1, "xy"));
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 206, 206, "xy"));
}
public void SidewaysOnAWideLineUnderComposition_WithABreakInIt_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 100, 100, "x\ny"));
}
public void SidewaysOnAWideLineUnderComposition_ReplacingAcrossLines_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 1, 206, "zz"));
}
public void SidewaysOnAWideLineUnderComposition_WhenCentred_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 100, 100, "xy", alignment:
Alignment.Horizontal.Center));
}
public void SidewaysOnAWideLineUnderComposition_WhenRightAligned_ShowsWhatLayingOutTheWholeTextDoes()
{
CheckEqual("", SidewaysComposedDifferences(ComposedLine, 100, 103, "xy", alignment:
Alignment.Horizontal.Right));
}
public void SidewaysOnAWideLineUnderComposition_WithKerning_ShowsWhatLayingOutTheWholeTextDoes()
{
string text = "ab\n" + VisualLineIndexTests.Repeat("AVAWTa ", 40) + "\ncd";
foreach (int start in new[] { 3, 40, 41, 100, 283 })
{
CheckEqual("", SidewaysComposedDifferences(text, start, start, "AVT", style: KernedStyle));
CheckEqual("", SidewaysComposedDifferences(text, start, start + 2, "WA", style: KernedStyle));
}
}
public void SidewaysOnAWideLineUnderComposition_WiderThanAnInt_ShowsWhatCountingTheWholeTextDoes()
{
string text = "ab\n" + VisualLineIndexTests.Repeat("abcd efgh ", 250) + "\ncd";
CheckEqual("", SidewaysComposedDifferences(text, 1500, 1500, "xy", style: WideStyle,
referenceIsChunked: true));
CheckEqual("", SidewaysComposedDifferences(text, 1500, 1520, "x", style: WideStyle,
referenceIsChunked: true));
}
public void OnALongLineUnderComposition_ShowTheSameLinesAsLayingOutTheWholeLine()
{
string text = LongLineTexts[1];
PagedText paged = PagedTextTests.Paged(text);
var spliced = new SplicedText();
spliced.Set(paged, 20, 20, "xyz");
TextViewport chunked = NewChunked(width: 100, height: 30);
TextViewport whole = NewViewport(multiline: true, width: 100, height: 30);
for (int caret = 0; caret <= spliced.Length; caret += 17)
{
chunked.ScrollToCaret(spliced, caret, caretAtLineEnd: false);
whole.ScrollToCaret(spliced, caret, caretAtLineEnd: false);
CheckEqual(ViewOf(whole, spliced, caret, atLineEnd: false),
ViewOf(chunked, spliced, caret, atLineEnd: false));
}
}
public void OnALongLineUnderComposition_ShowTheSameLinesAsLayingOutTheWholeLine_WhereTheSpliceIs()
{
string text = LongLineTexts[1];
PagedText paged = PagedTextTests.Paged(text);
var spliced = new SplicedText();
spliced.Set(paged, 200, 203, "xyz");
TextViewport chunked = NewChunked(width: 100, height: 30);
TextViewport whole = NewViewport(multiline: true, width: 100, height: 30);
for (int caret = 0; caret <= 203; caret += 7)
{
chunked.ScrollToCaret(spliced, caret, caretAtLineEnd: false);
whole.ScrollToCaret(spliced, caret, caretAtLineEnd: false);
CheckEqual(ViewOf(whole, spliced, caret, atLineEnd: false),
ViewOf(chunked, spliced, caret, atLineEnd: false));
}
}
public void PastTheCountedPartOfALongLine_CountsUpToTheCaretAndShowsWhatCountedTextDoes()
{
PagedText paged = PagedTextTests.Paged(CharsMeasuredTests.UncountedLine);
TextViewport counted = NewChunked(width: 100, height: 30);
CountAll(counted, paged);
TextViewport uncounted = NewChunked(width: 100, height: 30, UncountedSynchronousChars);
counted.ScrollToCaret(paged, 10000, caretAtLineEnd: false);
uncounted.ScrollToCaret(paged, 10000, caretAtLineEnd: false);
CheckEqual(ViewOf(counted, paged, 10000, atLineEnd: false),
ViewOf(uncounted, paged, 10000, atLineEnd: false));
CheckFalse(uncounted.IsCounted(paged));
}
[Thorough]
public void OnLongLines_ShowTheSameLinesAsLayingOutTheWholeLine()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", ScrollToCaretDifferences(text, width: 100));
}
}
[Thorough]
public void OnLongLines_ShowTheSameLinesAsLayingOutTheWholeLine_AtNarrowWidth()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", ScrollToCaretDifferences(text, width: 35));
}
}
public void WithoutAFont_StaysAtTheStart()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: false, fitWidth: 100, fitHeight: null);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
CheckEqual(0, viewport.FirstVisible);
}
public static void CheckWideTexts(int first, int last, int width,
Alignment.Horizontal alignment = Alignment.Horizontal.Left)
{
for (int i = first; i <= last; ++i)
{
string difference = SidewaysScrollToCaretDifferences(LongLineTexts[i], width, alignment);
CheckStatic(difference.Length == 0, $"Text {i}: {difference}");
}
}
public static void CheckStatic(bool condition, string message)
{
if (!condition)
{
throw new InvalidOperationException(message);
}
}
[Thorough]
public void WideLines_ShowWhatALayoutOfTheWholeLineDoes_RightAligned_1()
{
CheckWideTexts(0, 2, width: 100, Alignment.Horizontal.Right);
}
[Thorough]
public void WideLines_ShowWhatALayoutOfTheWholeLineDoes_RightAligned_2()
{
CheckWideTexts(3, 6, width: 100, Alignment.Horizontal.Right);
}
[Thorough]
public void WideLines_ShowWhatALayoutOfTheWholeLineDoes_Centred_1()
{
CheckWideTexts(0, 2, width: 100, Alignment.Horizontal.Center);
}
[Thorough]
public void WideLines_ShowWhatALayoutOfTheWholeLineDoes_Centred_2()
{
CheckWideTexts(3, 6, width: 100, Alignment.Horizontal.Center);
}
[Thorough]
public void WideLines_ShowWhatALayoutOfTheWholeLineDoes_First()
{
CheckWideTexts(0, 2, width: 100);
}
[Thorough]
public void WideLines_ShowWhatALayoutOfTheWholeLineDoes_Second()
{
CheckWideTexts(3, 4, width: 100);
}
public void WideLines_ShowWhatALayoutOfTheWholeLineDoes_Third()
{
CheckWideTexts(5, 6, width: 100);
}
[Thorough]
public void WideLines_ShowWhatALayoutOfTheWholeLineDoes_InANarrowView()
{
CheckWideTexts(0, 1, width: 33);
}
[Thorough]
public void WideLines_ShowWhatALayoutOfTheWholeLineDoes_InAWideView()
{
CheckWideTexts(0, 1, width: 237);
}
public void InAComposedText_ScrollsSidewaysToTheCaret()
{
TextViewport viewport = NewSideways();
var spliced = new SplicedText();
spliced.Set(PagedTextTests.Paged(WideLines), 5, 5, "xy");
viewport.ScrollToCaret(spliced, 20, caretAtLineEnd: false);
CheckGreater(viewport.ScrollX, 0L);
CheckTrue(viewport.IsInWindow(20, atLineEnd: false));
}
public void ALineWiderThanAnInt_ScrollsToAndShowsACaretFarAlong()
{
string text = VisualLineIndexTests.Repeat("ab", 3000);
PagedText paged = PagedTextTests.Paged(text);
TextViewport viewport = NewSidewaysChunked(100, WideStyle);
CountAll(viewport, paged);
viewport.ScrollToCaret(paged, 5000, caretAtLineEnd: false);
CheckEqual(5000L * 1000000 + 2 - 100, viewport.ScrollX);
CheckTrue(viewport.HasRowOrigins);
CheckEqual(98, viewport.CaretAt(5000, atLineEnd: false).X);
CheckTrue(viewport.IsInWindow(5000, atLineEnd: false));
CheckFalse(viewport.IsInWindow(4990, atLineEnd: false));
CheckEqual(5000, viewport.IndexAt(50, 5, out _, out int pastEdge));
CheckEqual(0, pastEdge);
}
public void ALineWiderThanAnInt_ScrollsBackToTheStart()
{
string text = VisualLineIndexTests.Repeat("ab", 3000);
PagedText paged = PagedTextTests.Paged(text);
TextViewport viewport = NewSidewaysChunked(100, WideStyle);
viewport.ScrollToCaret(paged, 5000, caretAtLineEnd: false);
viewport.ScrollToCaret(paged, 0, caretAtLineEnd: false);
CheckEqual(0L, viewport.ScrollX);
CheckEqual(0, viewport.CaretAt(0, atLineEnd: false).X);
}
public void SingleLine_ScrollsSidewaysToShowACaretPastTheEnd()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
CheckEqual(6, viewport.FirstVisible);
}
public void SingleLine_ScrollsBackToShowACaretBeforeTheWindow()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 2, caretAtLineEnd: false);
CheckEqual(2, viewport.FirstVisible);
}
public void SingleLine_ScrollsBackToFillTheBoxWhenTheTextShrinks()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
viewport.ScrollToCaret("abcdefghijklm", caret: 13, caretAtLineEnd: false);
CheckEqual(3, viewport.FirstVisible);
}
public void Multiline_ScrollsDownToShowACaretBelowTheWindow()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
CheckEqual(19, viewport.FirstVisible);
}
public void Multiline_ScrollsUpToTheStartOfTheLineOfACaretAboveTheWindow()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
viewport.ScrollToCaret(SevenLines, caret: 16, caretAtLineEnd: false);
CheckEqual(14, viewport.FirstVisible);
}
public void Multiline_LeavesTheWindowWhenTheCaretIsInIt()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
viewport.ScrollToCaret(SevenLines, caret: 25, caretAtLineEnd: false);
CheckEqual(19, viewport.FirstVisible);
}
public void Multiline_ScrollsBackToFillTheWindowWhenTheTextShrinks()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
viewport.ScrollToCaret(SevenLines.Substring(0, 24), caret: 24, caretAtLineEnd: false);
CheckEqual(11, viewport.FirstVisible);
}
public void Sideways_ScrollsToShowACaretPastTheRightEdge()
{
TextViewport viewport = NewSideways();
viewport.ScrollToCaret(WideLines, caret: 23, caretAtLineEnd: false);
CheckEqual(102, viewport.ScrollX);
}
public void Sideways_ScrollsOnlyFarEnoughToShowACaretInTheMiddle()
{
TextViewport viewport = NewSideways();
viewport.ScrollToCaret(WideLines, caret: 15, caretAtLineEnd: false);
CheckEqual(22, viewport.ScrollX);
}
public void Sideways_ScrollsBackToShowACaretLeftOfTheView()
{
TextViewport viewport = NewSideways();
viewport.ScrollToCaret(WideLines, caret: 23, caretAtLineEnd: false);
viewport.ScrollToCaret(WideLines, caret: 5, caretAtLineEnd: false);
CheckEqual(20, viewport.ScrollX);
}
public void Sideways_LeavesTheOffsetWhenTheCaretIsInView()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 40);
viewport.ScrollToCaret(WideLines, caret: 9, caretAtLineEnd: false);
CheckEqual(40, viewport.ScrollX);
}
public void Sideways_KeepsTheLinesUnwrapped()
{
TextViewport viewport = NewSideways();
CheckEqual(3, viewport.TotalLines(WideLines));
}
public void Multiline_ShowsTheStartOfAWrappedLineWhoseEndHoldsTheCaret()
{
TextViewport viewport = NewViewport(multiline: true, height: 10);
viewport.ScrollToCaret("aaaa bbbb cccc dddd", caret: 19, caretAtLineEnd: false);
CheckEqual(10, viewport.FirstVisible);
}
}
public class CaretAtTests : TestFixture
{
public void SingleLine_FindsTheCaretRelativeToTheScrolledWindow()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
CaretPosition caret = viewport.CaretAt(16, atLineEnd: false);
CheckEqual(new CaretPosition(Line: 0, X: 100, Y: 0), caret);
}
public void Multiline_FindsTheLineWithinTheScrolledWindow()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
CaretPosition caret = viewport.CaretAt(SevenLines.Length, atLineEnd: false);
CheckEqual(new CaretPosition(Line: 2, X: 20, Y: 20), caret);
}
public void AnIndexBeforeTheWindow_FindsTheWindowsStart()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
CaretPosition caret = viewport.CaretAt(0, atLineEnd: false);
CheckEqual(new CaretPosition(Line: 0, X: 0, Y: 0), caret);
}
}
public class AddBandsTests : TestFixture
{
public void AddsABandForEachLineTheRangeCrosses()
{
TextViewport viewport = NewViewport(multiline: true, height: 100);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
BandsMaterial bands = NewBands();
viewport.AddBands(bands, start: 2, end: 12);
CheckEqual(3, bands.Count);
CheckEqual(10, bands[1].TopLeft.Y);
}
public void AnEmptyRange_AddsNoBands()
{
TextViewport viewport = NewViewport(multiline: true, height: 100);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
BandsMaterial bands = NewBands();
viewport.AddBands(bands, start: 5, end: 5);
CheckEqual(0, bands.Count);
}
}
public class IndexAtTests : TestFixture
{
public void SingleLine_AddsTheWindowsStartToTheIndexInIt()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
int index = viewport.IndexAt(x: 31, y: 0, out _, out int pastEdge);
CheckEqual(9, index);
CheckEqual(0, pastEdge);
}
public void SingleLine_ReportsHowFarLeftOfTheWindowThePointIs()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
int index = viewport.IndexAt(x: -5, y: 0, out _, out int pastEdge);
CheckEqual(6, index);
CheckEqual(-5, pastEdge);
}
public void SingleLine_ReportsHowFarRightOfTheWindowThePointIs()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
int index = viewport.IndexAt(x: 130, y: 0, out _, out int pastEdge);
CheckEqual(16, index);
CheckEqual(30, pastEdge);
}
public void Multiline_ReportsNoDistancePastAnEdge()
{
TextViewport viewport = NewViewport(multiline: true, height: 100);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
viewport.IndexAt(x: -5, y: 0, out _, out int pastLeft);
viewport.IndexAt(x: 130, y: 0, out _, out int pastRight);
CheckEqual(0, pastLeft);
CheckEqual(0, pastRight);
}
public void Multiline_FindsTheIndexOnTheLineAtY()
{
TextViewport viewport = NewViewport(multiline: true, height: 100);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
CheckEqual(13, viewport.IndexAt(x: 25, y: 22, out _, out _));
}
public void Sideways_AddsTheOffsetToThePoint()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 50);
CheckEqual(9, viewport.IndexAt(x: 10, y: 10, out _, out int pastEdge));
CheckEqual(0, pastEdge);
}
public void Sideways_ReportsHowFarOutsideTheViewThePointIs()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 50);
viewport.IndexAt(x: -5, y: 10, out _, out int pastLeft);
viewport.IndexAt(x: 150, y: 10, out _, out int pastRight);
CheckEqual(-5, pastLeft);
CheckEqual(50, pastRight);
}
public void WithoutAFont_FindsIndexZeroAndStillReportsThePastEdge()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: false, fitWidth: 100, fitHeight: null);
int index = viewport.IndexAt(x: 130, y: 0, out bool atLineEnd, out int pastEdge);
CheckEqual(0, index);
CheckFalse(atLineEnd);
CheckEqual(30, pastEdge);
}
}
public class ScrollsSidewaysTests : TestFixture
{
public void MultilineThatDoesNotWrap_Is()
{
Check(NewSideways().ScrollsSideways);
}
public void MultilineThatWraps_IsNot()
{
CheckFalse(NewViewport(multiline: true).ScrollsSideways);
}
public void SingleLine_IsNot()
{
var viewport = new TextViewport();
viewport.Configure(Style, multiline: false, fitWidth: 100, fitHeight: null, wrap: false);
CheckFalse(viewport.ScrollsSideways);
}
}
public class ColumnTests : TestFixture
{
public void IsHalfTheFontsEm()
{
CheckEqual(5, NewViewport(multiline: true).Column);
}
public void WithoutAFont_IsOne()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: null);
CheckEqual(1, viewport.Column);
}
}
public class LayoutWidthTests : TestFixture
{
public void WhenWrapping_IsNull()
{
TextViewport viewport = NewViewport(multiline: true);
viewport.TotalLines(WideLines);
CheckNull(viewport.LayoutWidth);
}
public void WhenNotWrapping_IsTheWidestLine()
{
TextViewport viewport = NewSideways();
viewport.TotalLines(WideLines);
CheckEqual(200, viewport.LayoutWidth);
}
public void WhenNotWrapping_IsAtLeastTheViewsWidth()
{
TextViewport viewport = NewSideways();
viewport.TotalLines("ab");
CheckEqual(100, viewport.LayoutWidth);
}
public void FollowsTheText()
{
TextViewport viewport = NewSideways();
viewport.TotalLines(WideLines);
viewport.TotalLines("abcdefghijklmnopqrstuvwxyz");
CheckEqual(260, viewport.LayoutWidth);
}
}
public class ContentWidthTests : TestFixture
{
public void LeavesRoomForTheCaretAfterTheWidestLine()
{
CheckEqual(202, NewSideways().ContentWidth(WideLines));
}
public static readonly string LongLine = VisualLineIndexTests.Repeat("abcd efgh ", 2000);
// Each ten chars are 88 wide, and the last space hangs.
public const long LongLineWidth = 2000L * 88 - 4 + 2;
public void OfAWideLineNotYetCounted_IsReckonedFromWhatIsCounted()
{
PagedText paged = PagedTextTests.Paged(LongLine);
TextViewport viewport = NewSidewaysChunked(100, synchronousChars: UncountedSynchronousChars);
viewport.CountMore(paged, 7000);
long reckoned = viewport.ContentWidth(paged);
CheckFalse(viewport.IsCounted(paged));
CheckGreater(reckoned, LongLineWidth / 10 * 9);
CheckGreater(LongLineWidth / 10 * 11, reckoned);
}
public void OfAWideLineNotYetCounted_IsReckonedFromItsFirstBlock()
{
PagedText paged = PagedTextTests.Paged(LongLine);
TextViewport viewport = NewSidewaysChunked(100, synchronousChars: UncountedSynchronousChars);
long reckoned = viewport.ContentWidth(paged);
CheckGreater(reckoned, LongLineWidth / 10 * 9);
CheckGreater(LongLineWidth / 10 * 11, reckoned);
}
public void OfAWideLine_IsExactOnceCounted()
{
PagedText paged = PagedTextTests.Paged(LongLine);
TextViewport viewport = NewSidewaysChunked(100);
CountAll(viewport, paged);
CheckEqual(LongLineWidth, viewport.ContentWidth(paged));
}
public void ReckoningMore_LeavesTheViewWhereItIs()
{
PagedText paged = PagedTextTests.Paged(LongLine);
TextViewport viewport = NewSidewaysChunked(100, synchronousChars: UncountedSynchronousChars);
viewport.ScrollToX(paged, 12000);
for (int step = 0; step < 6; ++step)
{
viewport.CountMore(paged, 4000);
viewport.ContentWidth(paged);
CheckEqual(12000L, viewport.ScrollX);
}
}
public void OfALineWiderThanAnInt_IsALong()
{
PagedText paged = PagedTextTests.Paged(VisualLineIndexTests.Repeat("ab", 3000));
TextViewport viewport = NewSidewaysChunked(100, WideStyle);
CountAll(viewport, paged);
CheckEqual(6000L * 1000000 + 2, viewport.ContentWidth(paged));
}
public void TextNarrowerThanTheView_IsTheViewsWidth()
{
CheckEqual(100, NewSideways().ContentWidth("ab"));
}
public void WhenWrapping_IsTheViewsWidth()
{
CheckEqual(100, NewViewport(multiline: true).ContentWidth(WideLines));
}
}
public class WindowEndTests : TestFixture
{
public void InABoxOfOneLine_ReachesPastTheViewButNotToTheEnd()
{
string text = VisualLineIndexTests.Repeat("abcd efgh ", 400);
PagedText paged = PagedTextTests.Paged(text);
TextViewport viewport = NewOneLine(Alignment.Horizontal.Left, width: 100, reach: 8);
viewport.ScrollToCaret(paged, 0, caretAtLineEnd: false);
CheckTrue(viewport.WindowEnd > 10 && viewport.WindowEnd < 200);
}
public void InABoxOfOneLine_ThatFits_IsTheEndOfTheText()
{
PagedText paged = PagedTextTests.Paged("abc");
TextViewport viewport = NewOneLine(Alignment.Horizontal.Left, width: 100, reach: 8);
viewport.ScrollToCaret(paged, 0, caretAtLineEnd: false);
CheckEqual(3, viewport.WindowEnd);
}
public void OnALongLine_IsInsideIt()
{
string text = VisualLineIndexTests.Repeat("abcd efgh ", 60);
PagedText paged = PagedTextTests.Paged(text);
TextViewport viewport = NewChunked(width: 100, height: 30);
viewport.ScrollToCaret(paged, 0, caretAtLineEnd: false);
CheckTrue(viewport.WindowEnd > 0 && viewport.WindowEnd < text.Length);
}
public void OnALongLineThatEndsInTheBox_ReachesTheLinesAfterIt()
{
PagedText paged = PagedTextTests.Paged(LongLineTexts[6]);
TextViewport viewport = NewChunked(width: 100, height: 400);
viewport.ScrollToCaret(paged, 0, caretAtLineEnd: false);
CheckTrue(viewport.WindowEnd > paged.EndOfLine(0) + 1);
}
public void HoldsAsManyHardLinesAsTheBoxShows()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
CheckEqual(13, viewport.WindowEnd);
}
public void FollowsTheScroll()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 2);
CheckEqual(24, viewport.WindowEnd);
}
public void NearTheEndOfTheText_IsTheEndOfTheText()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 4);
CheckEqual(SevenLines.Length, viewport.WindowEnd);
}
public void AHardLineThatWraps_StaysWhole()
{
TextViewport viewport = NewViewport(multiline: true, width: 100, height: 10);
viewport.ScrollToCaret("aaaa bbbb cccc dddd\nxx\nyy\nzz", caret: 0, caretAtLineEnd: false);
CheckEqual(19, viewport.WindowEnd);
}
public void WithNoLimitOnTheHeight_IsTheEndOfTheText()
{
TextViewport viewport = NewViewport(multiline: true);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
CheckEqual(SevenLines.Length, viewport.WindowEnd);
}
public void SingleLine_IsTheEndOfTheText()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
CheckEqual(16, viewport.WindowEnd);
}
public void WithoutAFont_IsTheEndOfTheText()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 30);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
CheckEqual(SevenLines.Length, viewport.WindowEnd);
}
public void CaretAtClampsToTheEndOfTheWindow()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
CheckEqual(new CaretPosition(Line: 2, X: 20, Y: 20), viewport.CaretAt(30, atLineEnd: false));
}
public void AddBandsClampsARangePastTheWindow()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
BandsMaterial bands = NewBands();
viewport.AddBands(bands, start: 2, end: 30);
CheckEqual(3, bands.Count);
}
public void ACaretPastTheWindow_IsOutside()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
Check(viewport.IsInWindow(13, atLineEnd: false));
Check(!viewport.IsInWindow(14, atLineEnd: false));
}
public void AFarJump_PutsTheCaretsLineAtTheBottomThroughWrappedLines()
{
const string wrapped = "aaaa bbbb cccc dddd eeee\nxx\nyy\nzz";
TextViewport viewport = NewViewport(multiline: true, width: 100, height: 50);
viewport.ScrollToCaret(wrapped, caret: wrapped.Length, caretAtLineEnd: false);
CheckEqual(10, viewport.FirstVisible);
}
public void AFarJump_PutsTheCaretsLineAtTheBottomThroughHardLines()
{
const string wrapped = "aaaa bbbb cccc dddd eeee\nxx\nyy\nzz";
TextViewport viewport = NewViewport(multiline: true, width: 100, height: 20);
viewport.ScrollToCaret(wrapped, caret: wrapped.Length, caretAtLineEnd: false);
CheckEqual(wrapped.IndexOf("yy"), viewport.FirstVisible);
}
public void AFarJump_CountsEmptyLines()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret("a\n\n\n\nb", caret: 6, caretAtLineEnd: false);
CheckEqual(3, viewport.FirstVisible);
}
public void AFarJump_IntoALongTextLaysOutOnlyTheLinesItNeeds()
{
var longText = new System.Text.StringBuilder();
for (int i = 0; i < 20000; ++i)
{
longText.Append("ab\n");
}
longText.Append("end");
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(longText.ToString(), caret: longText.Length, caretAtLineEnd: false);
CheckEqual(longText.Length - 9, viewport.FirstVisible);
}
public void AFarJump_ToTheStartOfTheTextFromTheEnd()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
CheckEqual(0, viewport.FirstVisible);
}
}
public class ScrollToXTests : TestFixture
{
public void SetsTheOffset()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 50);
CheckEqual(50, viewport.ScrollX);
}
public void PastTheEnd_StopsWhereTheCaretFitsAfterTheWidestLine()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 500);
CheckEqual(102, viewport.ScrollX);
}
public void Negative_ScrollsToTheStart()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 50);
viewport.ScrollToX(WideLines, -5);
CheckEqual(0, viewport.ScrollX);
}
public void WhenWrapping_DoesNothing()
{
TextViewport viewport = NewViewport(multiline: true);
viewport.ScrollToX(WideLines, 50);
CheckEqual(0, viewport.ScrollX);
}
public void CaretAtStaysInTheTextsOwnUnits()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 50);
CheckEqual(120, viewport.CaretAt(15, atLineEnd: false).X);
}
public static void CheckWideTexts(int first, int last, int width,
Alignment.Horizontal alignment = Alignment.Horizontal.Left)
{
for (int i = first; i <= last; ++i)
{
string difference = SidewaysScrollToXDifferences(LongLineTexts[i], width, alignment);
if (difference.Length > 0)
{
throw new InvalidOperationException($"Text {i}: {difference}");
}
}
}
public void OnWideLines_ShowWhatALayoutOfTheWholeLineDoes_RightAligned_1()
{
CheckWideTexts(0, 2, width: 100, Alignment.Horizontal.Right);
}
public void OnWideLines_ShowWhatALayoutOfTheWholeLineDoes_RightAligned_2()
{
CheckWideTexts(3, 6, width: 100, Alignment.Horizontal.Right);
}
public void OnWideLines_ShowWhatALayoutOfTheWholeLineDoes_Centred_1()
{
CheckWideTexts(0, 2, width: 100, Alignment.Horizontal.Center);
}
public void OnWideLines_ShowWhatALayoutOfTheWholeLineDoes_Centred_2()
{
CheckWideTexts(3, 6, width: 100, Alignment.Horizontal.Center);
}
public void OnWideLines_ShowWhatALayoutOfTheWholeLineDoes_First()
{
CheckWideTexts(0, 2, width: 100);
}
public void OnWideLines_ShowWhatALayoutOfTheWholeLineDoes_Second()
{
CheckWideTexts(3, 6, width: 100);
}
public static readonly string[] SurrogateTexts =
{
VisualLineIndexTests.Repeat("ab" + VisualLineIndexTests.WidthChunkedLinesTests.Pair + "cd ", 30),
VisualLineIndexTests.Repeat("a" + VisualLineIndexTests.WidthChunkedLinesTests.Pair, 70),
VisualLineIndexTests.Repeat("ab" + (char)0xD800, 50),
VisualLineIndexTests.Repeat("ab" + (char)0xDC00 + "c", 50),
VisualLineIndexTests.Repeat(char.ConvertFromUtf32(0x1F1E6), 120),
VisualLineIndexTests.Repeat("a" + char.ConvertFromUtf32(0x1F1E6), 70),
};
public void OnWideLinesOfSurrogates_ShowWhatALayoutOfTheWholeLineDoes()
{
foreach (string text in SurrogateTexts)
{
string difference = SidewaysScrollToXDifferences(text, width: 100);
if (difference.Length > 0)
{
throw new InvalidOperationException(difference);
}
}
}
public void OnAWideLine_PlacesTheRowsByTheirOrigins()
{
TextViewport viewport = NewSidewaysChunked(100);
string text = "ab\n" + VisualLineIndexTests.Repeat("abcd efgh ", 20) + "\ncd";
viewport.ScrollToX(text, 500);
CheckTrue(viewport.HasRowOrigins);
CheckEqual(500L, viewport.ScrollX);
CheckEqual(-500L, viewport.RowOrigin(0));
}
public void FarIntoAnUncountedLine_CountsOnToTheOffset()
{
string text = VisualLineIndexTests.Repeat("abcd efgh ", 2000);
PagedText paged = PagedTextTests.Paged(text);
TextViewport viewport = NewSidewaysChunked(100, synchronousChars: UncountedSynchronousChars);
var words = new MeasuredWords(Style.Font, new StringView(text));
int expected = CaretLocator.IndexAt(words, 15000 + 33, y: 5, int.MaxValue, Alignment.Horizontal.Left,
out _);
viewport.ScrollToX(paged, 15000);
CheckFalse(viewport.IsCounted(paged));
CheckEqual(15000L, viewport.ScrollX);
CheckEqual(expected, viewport.IndexAt(33, 5, out _, out _));
}
public void PastTheEndOfAnUncountedLine_StopsWhereTheCaretFitsAfterIt()
{
string text = VisualLineIndexTests.Repeat("abcd efgh ", 2000);
PagedText paged = PagedTextTests.Paged(text);
TextViewport viewport = NewSidewaysChunked(100, synchronousChars: UncountedSynchronousChars);
TextViewport counted = NewSidewaysChunked(100, synchronousChars: UncountedSynchronousChars);
CountAll(counted, paged);
viewport.ScrollToX(paged, long.MaxValue);
CheckEqual(counted.ContentWidth(paged) - 100, viewport.ScrollX);
}
public void OnALineWiderThanAnInt_ScrollsBeyondIt()
{
string text = VisualLineIndexTests.Repeat("ab", 3000);
PagedText paged = PagedTextTests.Paged(text);
TextViewport viewport = NewSidewaysChunked(100, WideStyle);
CountAll(viewport, paged);
viewport.ScrollToX(paged, 3000000000L);
CheckEqual(3000000000L, viewport.ScrollX);
CheckEqual(3000, viewport.IndexAt(0, 5, out _, out _));
viewport.ScrollToX(paged, 6000000000L - 5);
CheckEqual(5999999902L, viewport.ScrollX);
}
}
public class ScrollXTests : TestFixture
{
public const string Longer = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
public void LeftAlignedText_StartsAtTheLeft()
{
TextViewport viewport = NewSideways(Alignment.Horizontal.Left);
viewport.ContentWidth(WideLines);
CheckEqual(0, viewport.ScrollX);
}
public void RightAlignedText_StartsAtTheRightEnd()
{
TextViewport viewport = NewSideways(Alignment.Horizontal.Right);
viewport.ContentWidth(WideLines);
CheckEqual(102, viewport.ScrollX);
}
public void CenteredText_StartsHalfWayAcross()
{
TextViewport viewport = NewSideways(Alignment.Horizontal.Center);
viewport.ContentWidth(WideLines);
CheckEqual(51, viewport.ScrollX);
}
public void RightAlignedText_KeepsItsPlaceAtTheRightEdgeAsItGrows()
{
TextViewport viewport = NewSideways(Alignment.Horizontal.Right);
viewport.ContentWidth(WideLines);
viewport.ContentWidth(Longer);
CheckEqual(422, viewport.ScrollX);
}
public void LeftAlignedText_KeepsItsOffsetAsItGrows()
{
TextViewport viewport = NewSideways(Alignment.Horizontal.Left);
viewport.ScrollToX(WideLines, 40);
viewport.ContentWidth(Longer);
CheckEqual(40, viewport.ScrollX);
}
public void ATextThatShrinks_ClampsTheOffset()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 102);
viewport.Revalidate("ab");
CheckEqual(0, viewport.ScrollX);
}
public void RightAlignedText_ThatFitsTheView_DoesNotScroll()
{
TextViewport viewport = NewSideways(Alignment.Horizontal.Right);
viewport.ContentWidth("ab");
CheckEqual(0, viewport.ScrollX);
}
}
public class PastVerticalTests : TestFixture
{
public void InsideTheLinesShown_IsZero()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
CheckEqual(0, viewport.PastVertical(0));
CheckEqual(0, viewport.PastVertical(29));
}
public void AboveTheFirstLine_IsHowFarAbove()
{
CheckEqual(-5, NewViewport(multiline: true, height: 30).PastVertical(-5));
}
public void BelowTheLastLine_IsHowFarBelow()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
CheckEqual(1, viewport.PastVertical(30));
CheckEqual(16, viewport.PastVertical(45));
}
public void ASliverUnderTheLastWholeLine_CountsAsBelow()
{
CheckEqual(1, NewViewport(multiline: true, height: 35).PastVertical(30));
}
public void SingleLine_IsZero()
{
CheckEqual(0, NewViewport(multiline: false, height: 30).PastVertical(100));
}
public void WithNoHeightLimit_IsZero()
{
CheckEqual(0, NewViewport(multiline: true).PastVertical(-100));
}
public void WithoutAFont_IsZero()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 30);
CheckEqual(0, viewport.PastVertical(100));
}
}
// A wrapped hard line on the second line of the text, wrapping at 10 chars into rows starting at 3 and 13.
public const string WrappedBetween = "ab\naaaa bbbb cccc dddd\ncd";
///
/// A text that counts the chars it is asked for, to show how much of a long text a move reads.
///
public class CountingSource : ITextSource
{
private readonly StringSource text;
public int CharsRead;
public CountingSource(string text)
{
this.text = new StringSource(text);
}
public int Length => text.Length;
public int HardLineCount => text.HardLineCount;
public int Version => text.Version;
public char this[int index]
{
get
{
++CharsRead;
return text[index];
}
}
public void CopyTo(int start, int count, char[] destination, int destinationIndex)
{
CharsRead += count;
text.CopyTo(start, count, destination, destinationIndex);
}
public StringView Slice(int start, int count, ref char[] scratch)
{
CharsRead += count;
return text.Slice(start, count, ref scratch);
}
public int StartOfLine(int index)
{
return text.StartOfLine(index);
}
public int EndOfLine(int index)
{
return text.EndOfLine(index);
}
public int LengthAt(int index)
{
return text.LengthAt(index);
}
public int LengthBefore(int index)
{
return text.LengthBefore(index);
}
public int LineNumberOf(int index)
{
return text.LineNumberOf(index);
}
public int StartOfLineNumber(int line)
{
return text.StartOfLineNumber(line);
}
}
public static string TwentyThousandShortLines()
{
return string.Concat(System.Linq.Enumerable.Repeat("ab\n", 20000)) + "end";
}
public class LineEdgeTests : TestFixture
{
public void OnLongLines_AreWhereLayingOutTheWholeLineFindsThem()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", LineEdgeDifferences(text, width: 100));
}
}
[Thorough]
public void OnLongLines_AreWhereLayingOutTheWholeLineFindsThem_AtNarrowWidth()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", LineEdgeDifferences(text, width: 35));
}
}
public void Start_OfALaterWrappedRow_LeadsToTheRowsStart()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineEdge(WrappedBetween, caret: 14, caretAtLineEnd: false, end: false,
out bool atLineEnd);
CheckEqual(13, index);
CheckFalse(atLineEnd);
}
public void Start_OfTheFirstRowOfALaterHardLine_LeadsToTheHardLinesStart()
{
TextViewport viewport = NewViewport(multiline: true);
CheckEqual(3, viewport.LineEdge(WrappedBetween, caret: 5, caretAtLineEnd: false, end: false, out _));
}
public void End_OfAnEarlierRowOfALaterHardLine_LeadsToTheWrapPoint()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineEdge(WrappedBetween, caret: 5, caretAtLineEnd: false, end: true,
out bool atLineEnd);
CheckEqual(13, index);
Check(atLineEnd);
}
public void End_OfTheLastRowOfALaterHardLine_LeadsToBeforeItsBreak()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineEdge(WrappedBetween, caret: 14, caretAtLineEnd: false, end: true,
out bool atLineEnd);
CheckEqual(22, index);
CheckFalse(atLineEnd);
}
public void ACaretShownAtTheEndOfAWrappedRow_IsOnThatRow()
{
TextViewport viewport = NewViewport(multiline: true);
CheckEqual(3, viewport.LineEdge(WrappedBetween, caret: 13, caretAtLineEnd: true, end: false, out _));
CheckEqual(13, viewport.LineEdge(WrappedBetween, caret: 13, caretAtLineEnd: false, end: false, out _));
}
public void AnEmptyLastLine_LeadsToTheEndOfTheText()
{
TextViewport viewport = NewViewport(multiline: true);
CheckEqual(3, viewport.LineEdge("ab\n", caret: 3, caretAtLineEnd: false, end: true, out _));
CheckEqual(3, viewport.LineEdge("ab\n", caret: 3, caretAtLineEnd: false, end: false, out _));
}
public void InALongText_ReadsOnlyTheLineTheCaretIsOn()
{
var source = new CountingSource(TwentyThousandShortLines());
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineEdge(source, caret: 30001, caretAtLineEnd: false, end: true, out _);
CheckEqual(30002, index);
Check(source.CharsRead < 100);
}
public void End_LeadsToTheEndOfTheHardLineBeforeItsBreak()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineEdge(SevenLines, caret: 6, caretAtLineEnd: false, end: true,
out bool atLineEnd);
CheckEqual(10, index);
CheckFalse(atLineEnd);
}
public void Start_LeadsToTheStartOfTheHardLine()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineEdge(SevenLines, caret: 6, caretAtLineEnd: false, end: false,
out bool atLineEnd);
CheckEqual(4, index);
CheckFalse(atLineEnd);
}
public void End_OfAWrappedLine_LeadsToTheWrapPointOnTheLineSide()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineEdge("aaaa bbbb cccc", caret: 0, caretAtLineEnd: false, end: true,
out bool atLineEnd);
CheckEqual(10, index);
Check(atLineEnd);
}
}
public class LineTargetTests : TestFixture
{
public static void CheckWideTexts(int first, int last, int width,
Alignment.Horizontal alignment = Alignment.Horizontal.Left)
{
for (int i = first; i <= last; ++i)
{
string difference = SidewaysMoveDifferences(LongLineTexts[i], width, alignment);
if (difference.Length > 0)
{
throw new InvalidOperationException($"Text {i}: {difference}");
}
}
}
public void OnWideLines_KeepTheColumnTheRunBeganIn()
{
string line = VisualLineIndexTests.Repeat("abcd efgh ", 20);
string text = line + "\nab\n" + line;
PagedText paged = PagedTextTests.Paged(text);
TextViewport chunked = NewSidewaysChunked(100);
TextViewport whole = NewSideways();
CountAll(chunked, paged);
var results = new int[2, 3];
int viewport = 0;
foreach (TextViewport run in new[] { chunked, whole })
{
int caret = 100;
for (int step = 0; step < 3; ++step)
{
caret = run.LineTarget(paged, caret, false, lines: 1, forward: step < 2, version: 7, out _);
run.KeepGoalColumn(7);
results[viewport, step] = caret;
}
++viewport;
}
for (int step = 0; step < 3; ++step)
{
CheckEqual(results[1, step], results[0, step]);
}
}
public void OnWideLines_LeadWhereALayoutOfTheWholeLineDoes_RightAligned_1()
{
CheckWideTexts(0, 2, width: 100, Alignment.Horizontal.Right);
}
public void OnWideLines_LeadWhereALayoutOfTheWholeLineDoes_RightAligned_2()
{
CheckWideTexts(3, 6, width: 100, Alignment.Horizontal.Right);
}
public void OnWideLines_LeadWhereALayoutOfTheWholeLineDoes_Centred_1()
{
CheckWideTexts(0, 2, width: 100, Alignment.Horizontal.Center);
}
public void OnWideLines_LeadWhereALayoutOfTheWholeLineDoes_Centred_2()
{
CheckWideTexts(3, 6, width: 100, Alignment.Horizontal.Center);
}
public void OnWideLines_LeadWhereALayoutOfTheWholeLineDoes_First()
{
CheckWideTexts(0, 2, width: 100);
}
public void OnWideLines_LeadWhereALayoutOfTheWholeLineDoes_Second()
{
CheckWideTexts(3, 6, width: 100);
}
public void OnALongLine_MoreRowsThanAStretchHoldsAway_LeadToThatRow()
{
PagedText paged = PagedTextTests.Paged(CharsMeasuredTests.OneLine);
TextViewport viewport = CharsMeasuredTests.NewCounted(paged);
VisualLineIndex index = VisualLineIndexTests.NewIndex(blockChars: 16, longLineChars: 48);
VisualLineIndexTests.CountAll(index, paged);
int down = viewport.LineTarget(paged, 0, false, lines: 3000, forward: true, version: 1,
out bool downAtLineEnd);
int up = viewport.LineTarget(paged, paged.Length, false, lines: 3000, forward: false, version: 1,
out bool upAtLineEnd);
CheckEqual(3000, index.RowOf(paged, down, downAtLineEnd));
CheckEqual(index.TotalRows(paged) - 1 - 3000, index.RowOf(paged, up, upAtLineEnd));
}
[Thorough]
public void OnLongLines_LeadWhereLayingOutTheWholeLineLeads()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", LineTargetDifferences(text, width: 100));
}
}
public void OnLongLines_LeadWhereLayingOutTheWholeLineLeads_AtNarrowWidth()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", LineTargetDifferences(text, width: 35));
}
}
[Thorough]
public void OnLongLines_KeepToTheGoalColumnOverARunOfMoves()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", LineTargetRunDifferences(text, width: 100));
}
}
public void Forward_IntoAWrappedHardLine_LeadsToItsFirstRow()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget(WrappedBetween, caret: 1, caretAtLineEnd: false, lines: 1,
forward: true, version: 1, out _);
CheckEqual(4, index);
}
public void Forward_CountsTheRowsOfAWrappedHardLine()
{
TextViewport viewport = NewViewport(multiline: true);
int second = viewport.LineTarget(WrappedBetween, caret: 1, caretAtLineEnd: false, lines: 2,
forward: true, version: 1, out _);
int past = viewport.LineTarget(WrappedBetween, caret: 1, caretAtLineEnd: false, lines: 3,
forward: true, version: 1, out _);
CheckEqual(14, second);
CheckEqual(24, past);
}
public void Forward_FromALaterRow_LeadsToTheNextHardLine()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget(WrappedBetween, caret: 14, caretAtLineEnd: false, lines: 1,
forward: true, version: 1, out _);
CheckEqual(24, index);
}
public void Backward_IntoAWrappedHardLine_LeadsToItsLastRow()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget(WrappedBetween, caret: 24, caretAtLineEnd: false, lines: 1,
forward: false, version: 1, out _);
CheckEqual(14, index);
}
public void Backward_CountsTheRowsOfAWrappedHardLine()
{
TextViewport viewport = NewViewport(multiline: true);
int first = viewport.LineTarget(WrappedBetween, caret: 24, caretAtLineEnd: false, lines: 2,
forward: false, version: 1, out _);
int before = viewport.LineTarget(WrappedBetween, caret: 24, caretAtLineEnd: false, lines: 3,
forward: false, version: 1, out _);
CheckEqual(4, first);
CheckEqual(1, before);
}
public void Backward_FromALaterRow_StaysInTheHardLine()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget(WrappedBetween, caret: 14, caretAtLineEnd: false, lines: 1,
forward: false, version: 1, out _);
CheckEqual(4, index);
}
public void ACaretShownAtTheEndOfAWrappedRow_MovesFromThatRow()
{
TextViewport viewport = NewViewport(multiline: true);
int up = viewport.LineTarget(WrappedBetween, caret: 13, caretAtLineEnd: true, lines: 1, forward: false,
version: 1, out _);
int down = viewport.LineTarget(WrappedBetween, caret: 13, caretAtLineEnd: false, lines: 1,
forward: false, version: 2, out _);
CheckEqual(2, up);
CheckEqual(3, down);
}
public void ReportsWhenItLandsAtTheEndOfAWrappedRow()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget("abcdefghij\naaaa bbbb cccc", caret: 10, caretAtLineEnd: false,
lines: 1, forward: true, version: 1, out bool atLineEnd);
CheckEqual(21, index);
Check(atLineEnd);
}
public void CountsEmptyLines()
{
TextViewport viewport = NewViewport(multiline: true);
int down = viewport.LineTarget("a\n\n\nb", caret: 0, caretAtLineEnd: false, lines: 3, forward: true,
version: 1, out _);
int up = viewport.LineTarget("a\n\n\nb", caret: 5, caretAtLineEnd: false, lines: 3, forward: false,
version: 1, out _);
CheckEqual(4, down);
CheckEqual(1, up);
}
public void CountsACrLfAsOneBreak()
{
TextViewport viewport = NewViewport(multiline: true);
int down = viewport.LineTarget("ab\r\ncd", caret: 1, caretAtLineEnd: false, lines: 1, forward: true,
version: 1, out _);
int up = viewport.LineTarget("ab\r\ncd", caret: 5, caretAtLineEnd: false, lines: 1, forward: false,
version: 1, out _);
CheckEqual(5, down);
CheckEqual(1, up);
}
public void ALineAfterATrailingBreak_IsReached()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget("ab\n", caret: 1, caretAtLineEnd: false, lines: 1, forward: true,
version: 1, out _);
CheckEqual(3, index);
}
public void InALongText_ReadsOnlyTheLinesItCrosses()
{
var source = new CountingSource(TwentyThousandShortLines());
TextViewport viewport = NewViewport(multiline: true);
int down = viewport.LineTarget(source, caret: 30001, caretAtLineEnd: false, lines: 5, forward: true,
version: 1, out _);
int up = viewport.LineTarget(source, caret: 30001, caretAtLineEnd: false, lines: 5, forward: false,
version: 1, out _);
CheckEqual(30016, down);
CheckEqual(29986, up);
Check(source.CharsRead < 100);
}
public void Forward_LeadsToTheSameColumnOnTheNextLine()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget(SevenLines, caret: 6, caretAtLineEnd: false, lines: 1, forward: true,
version: 1, out bool atLineEnd);
CheckEqual(13, index);
CheckFalse(atLineEnd);
}
public void Backward_LeadsToTheSameColumnOnThePreviousLine()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget(SevenLines, caret: 16, caretAtLineEnd: false, lines: 1,
forward: false, version: 1, out _);
CheckEqual(13, index);
}
public void PastTheFirstLine_LeadsToTheStartOfTheText()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget(SevenLines, caret: 6, caretAtLineEnd: false, lines: 2, forward: false,
version: 1, out _);
CheckEqual(0, index);
}
public void PastTheLastLine_LeadsToTheEndOfTheText()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget(SevenLines, caret: 6, caretAtLineEnd: false, lines: 10, forward: true,
version: 1, out _);
CheckEqual(SevenLines.Length, index);
}
public void WithAHugeLineCount_DoesNotOverflow()
{
TextViewport viewport = NewViewport(multiline: true);
int index = viewport.LineTarget(SevenLines, caret: 6, caretAtLineEnd: false, lines: int.MaxValue,
forward: true, version: 1, out _);
CheckEqual(SevenLines.Length, index);
}
public void StartsANewRunWhenNoRunWasKept()
{
TextViewport viewport = NewViewport(multiline: true);
int shorter = viewport.LineTarget(SevenLines, caret: 10, caretAtLineEnd: false, lines: 1,
forward: true, version: 1, out _);
int index = viewport.LineTarget(SevenLines, shorter, caretAtLineEnd: false, lines: 1, forward: true,
version: 1, out _);
CheckEqual(16, index);
}
}
public class PageTargetTests : TestFixture
{
[Thorough]
public void OnLongLines_LeadWhereLayingOutTheWholeLineLeads()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", PageTargetDifferences(text, width: 100));
}
}
[Thorough]
public void OnLongLines_LeadWhereLayingOutTheWholeLineLeads_AtNarrowWidth()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", PageTargetDifferences(text, width: 35));
}
}
public void CountsTheRowsOfWrappedLinesAsLines()
{
const string text = "aaaa bbbb cccc dddd\nxx\nyy\nzz\nww";
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(text, caret: 0, caretAtLineEnd: false);
int first = viewport.PageTarget(text, caret: 0, caretAtLineEnd: false, forward: true, version: 1,
out _);
int firstTop = viewport.FirstVisible;
int second = viewport.PageTarget(text, caret: first, caretAtLineEnd: false, forward: true, version: 1,
out _);
CheckEqual(23, first);
CheckEqual(23, firstTop);
CheckEqual(text.Length, second);
CheckEqual(23, viewport.FirstVisible);
}
public void Forward_FromAWindowStartingOnAWrappedRow_CountsFromThatRow()
{
const string text = "aaaa bbbb cccc dddd\nxx\nyy\nzz\nww";
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(text, 1);
int index = viewport.PageTarget(text, caret: 10, caretAtLineEnd: false, forward: true, version: 1,
out _);
CheckEqual(26, index);
CheckEqual(23, viewport.FirstVisible);
}
public void Backward_FromAWindowStartingOnAWrappedRow_CountsBackFromThatRow()
{
TextViewport viewport = NewViewport(multiline: true, height: 20);
viewport.ScrollToLine(WrappedBetween, line: 2);
CheckEqual(13, viewport.FirstVisible);
int index = viewport.PageTarget(WrappedBetween, caret: 15, caretAtLineEnd: false, forward: false,
version: 1, out _);
CheckEqual(0, viewport.FirstVisible);
CheckEqual(2, index);
}
public void TextShorterThanAPage_DoesNotScroll()
{
TextViewport viewport = NewViewport(multiline: true, height: 100);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
int down = viewport.PageTarget(SevenLines, caret: 6, caretAtLineEnd: false, forward: true, version: 1,
out _);
int up = viewport.PageTarget(SevenLines, caret: 6, caretAtLineEnd: false, forward: false, version: 1,
out _);
CheckEqual(SevenLines.Length, down);
CheckEqual(0, up);
CheckEqual(0, viewport.FirstVisible);
}
public void InALongText_ReadsOnlyThePagesItCrosses()
{
var source = new CountingSource(TwentyThousandShortLines());
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(source, caret: 30000, caretAtLineEnd: false);
source.CharsRead = 0;
int down = viewport.PageTarget(source, caret: 30000, caretAtLineEnd: false, forward: true, version: 1,
out _);
int up = viewport.PageTarget(source, caret: 30000, caretAtLineEnd: false, forward: false, version: 1,
out _);
CheckEqual(30009, down);
CheckEqual(29991, up);
Check(source.CharsRead < 500);
}
public void Forward_ScrollsAPageAndMovesTheCaretTheSameDistance()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
int index = viewport.PageTarget(SevenLines, caret: 4, caretAtLineEnd: false, forward: true,
version: 1, out _);
CheckEqual(14, viewport.FirstVisible);
CheckEqual(19, index);
}
public void Forward_StopsScrollingWhereTheLastLineFillsTheBottom()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
viewport.PageTarget(SevenLines, caret: 4, caretAtLineEnd: false, forward: true, version: 1, out _);
int index = viewport.PageTarget(SevenLines, caret: 19, caretAtLineEnd: false, forward: true,
version: 1, out _);
CheckEqual(19, viewport.FirstVisible);
CheckEqual(SevenLines.Length, index);
}
public void Backward_ScrollsAPageUpAndMovesTheCaretTheSameDistance()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
int index = viewport.PageTarget(SevenLines, caret: 25, caretAtLineEnd: false, forward: false,
version: 1, out _);
CheckEqual(4, viewport.FirstVisible);
CheckEqual(11, index);
}
public void Backward_StopsScrollingAtTheStart()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: 4, caretAtLineEnd: false);
int index = viewport.PageTarget(SevenLines, caret: 6, caretAtLineEnd: false, forward: false,
version: 1, out _);
CheckEqual(0, viewport.FirstVisible);
CheckEqual(0, index);
}
public void WithNoHeightLimit_MovesAPageOfEveryLineWithoutScrolling()
{
TextViewport viewport = NewViewport(multiline: true);
viewport.ScrollToCaret(SevenLines, caret: 0, caretAtLineEnd: false);
int index = viewport.PageTarget(SevenLines, caret: 6, caretAtLineEnd: false, forward: true,
version: 1, out _);
CheckEqual(0, viewport.FirstVisible);
CheckEqual(SevenLines.Length, index);
}
}
public class TotalLinesTests : TestFixture
{
public void CountsEachHardLine()
{
CheckEqual(7, NewViewport(multiline: true).TotalLines(SevenLines));
}
public void CountsTheLinesTextWrapsOnto()
{
CheckEqual(2, NewViewport(multiline: true, width: 100).TotalLines("aaaa bbbb cccc"));
}
public void EmptyText_IsOneLine()
{
CheckEqual(1, NewViewport(multiline: true).TotalLines(""));
}
public void CountsTheLineAfterATrailingBreak()
{
CheckEqual(2, NewViewport(multiline: true).TotalLines("ab\n"));
}
public void SingleLine_IsOneLine()
{
CheckEqual(1, NewViewport(multiline: false).TotalLines(SevenLines));
}
public void WithoutAFont_IsOneLine()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: null);
CheckEqual(1, viewport.TotalLines(SevenLines));
}
public void CountsDifferentTextAgain()
{
TextViewport viewport = NewViewport(multiline: true);
viewport.TotalLines("a\nb");
CheckEqual(3, viewport.TotalLines("a\nb\nc"));
}
public void CountsAgainWhenTheWidthChanges()
{
TextViewport viewport = NewViewport(multiline: true, width: 400);
CheckEqual(1, viewport.TotalLines("aaaa bbbb cccc"));
viewport.Configure(Style, multiline: true, fitWidth: 100, fitHeight: null);
CheckEqual(2, viewport.TotalLines("aaaa bbbb cccc"));
}
}
public class LinesForTests : TestFixture
{
public void CountsEachHardLine()
{
CheckEqual(3, NewViewport(multiline: true).LinesFor("a\nb\nc"));
}
public void EmptyText_IsOneLine()
{
CheckEqual(1, NewViewport(multiline: true).LinesFor(""));
}
public void CountsTheLineAfterATrailingBreak()
{
CheckEqual(2, NewViewport(multiline: true).LinesFor("ab\n"));
}
public void CountsTheLinesTextWrapsOntoAtTheViewsWidth()
{
CheckEqual(2, NewViewport(multiline: true, width: 100).LinesFor("aaaa bbbb cccc"));
CheckEqual(1, NewViewport(multiline: true, width: 400).LinesFor("aaaa bbbb cccc"));
}
public void TextThatDoesNotWrap_CountsOnlyHardLines()
{
CheckEqual(2, NewSideways().LinesFor("abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz\nab"));
}
public void SingleLine_IsOne()
{
CheckEqual(1, NewViewport(multiline: false).LinesFor("a\nb"));
}
public void WithoutAFont_IsOne()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 30);
CheckEqual(1, viewport.LinesFor("a\nb"));
}
public void LeavesWhatTheViewportHoldsLaidOutAlone()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 2);
viewport.LinesFor("x\ny");
CheckEqual(7, viewport.TotalLines(SevenLines));
CheckEqual(2, viewport.TopLine(SevenLines));
}
}
public class LinesUpToTests : TestFixture
{
public void ATextWithinTheCap_IsCountedExactly()
{
CheckEqual(7, NewViewport(multiline: true).LinesUpTo(new StringSource(SevenLines), cap: 10));
}
public void ATextAtTheCap_IsCountedExactly()
{
CheckEqual(7, NewViewport(multiline: true).LinesUpTo(new StringSource(SevenLines), cap: 7));
}
public void AWrappedTextWithinTheCap_CountsItsRows()
{
TextViewport viewport = NewViewport(multiline: true, width: 50);
CheckEqual(viewport.LinesFor(TwoRowLines(3)), viewport.LinesUpTo(LongPaged(3), cap: 100));
}
public void ALongTextWithinTheCap_IsCountedExactly()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CheckEqual(24001, viewport.LinesUpTo(paged, cap: 30000));
CheckTrue(viewport.IsCounted(paged));
}
public void ALongTextPastTheCap_StopsCountingOnceItPassesIt()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CheckGreater(viewport.LinesUpTo(paged, cap: 5), 5);
CheckFalse(viewport.IsCounted(paged));
}
public void ATextJustPastTheCap_IsAboveIt()
{
CheckGreater(NewViewport(multiline: true).LinesUpTo(new StringSource(SevenLines), cap: 6), 6);
}
public void SingleLine_IsOne()
{
CheckEqual(1, NewViewport(multiline: false).LinesUpTo(new StringSource(SevenLines), cap: 3));
}
public void WithoutAFont_IsOne()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 30);
CheckEqual(1, viewport.LinesUpTo(new StringSource(SevenLines), cap: 3));
}
}
public class LinesAddedTests : TestFixture
{
public static int ByLayingOutTheWholeText(TextViewport viewport, string text, int start, int length,
string replacement)
{
string edited = text.Substring(0, start) + replacement + text.Substring(start + length);
return viewport.LinesFor(edited) - viewport.LinesFor(text);
}
public void InsertingABreak_AddsALine()
{
CheckEqual(1, NewViewport(multiline: true).LinesAdded(new StringSource("ab\ncd"), 1, 0, "\n"));
}
public void DeletingABreak_TakesALineAway()
{
CheckEqual(-1, NewViewport(multiline: true).LinesAdded(new StringSource("ab\ncd"), 2, 1, ""));
}
public void ACharThatFitsTheLine_AddsNothing()
{
CheckEqual(0, NewViewport(multiline: true).LinesAdded(new StringSource("ab\ncd"), 1, 0, "x"));
}
public void ACharThatWrapsTheLine_AddsARow()
{
CheckEqual(1, NewViewport(multiline: true).LinesAdded(new StringSource("aaaa bbbb"), 9, 0, "cc"));
}
public void ReplacingTheSameChars_AddsNothing()
{
CheckEqual(0, NewViewport(multiline: true).LinesAdded(new StringSource("ab\ncd"), 0, 5, "ab\ncd"));
}
public void ReplacingSeveralLinesWithOne_TakesTheRestAway()
{
CheckEqual(-4, NewViewport(multiline: true).LinesAdded(new StringSource(SevenLines), 2, 20, "x"));
}
public void MatchesLayingOutTheWholeText()
{
string[] texts =
{
SevenLines,
"aaaa bbbb cccc dddd eeee ffff",
"ab\r\ncd\r\nef",
"ab\ncd\n\n\nef",
"",
};
string[] replacements = { "", "x", "\n", "\r", "\r\n", "aaaa bbbb ", "q\nr\ns", " " };
TextViewport viewport = NewViewport(multiline: true);
var mismatches = new System.Text.StringBuilder();
foreach (string text in texts)
{
var source = new StringSource(text);
for (int start = 0; start <= text.Length; ++start)
{
foreach (int length in new[] { 0, 1, 5 })
{
if (start + length > text.Length)
{
continue;
}
foreach (string replacement in replacements)
{
int expected = ByLayingOutTheWholeText(viewport, text, start, length, replacement);
int actual = viewport.LinesAdded(source, start, length, replacement);
if (expected != actual)
{
mismatches.AppendLine($"{text.Length}:{start}+{length}->{replacement.Length}:"
+ $" {expected} vs {actual} in '{text}' with '{replacement}'");
}
}
}
}
}
CheckEqual("", mismatches.ToString());
}
// Words of eight lengths, so the rows of a line cut at different places along it and an edit's effect on
// the wrapping dies out within a few rows.
public static string Prose(int cycles)
{
var returnMe = new System.Text.StringBuilder();
for (int cycle = 0; cycle < cycles; ++cycle)
{
foreach (int length in new[] { 3, 5, 2, 7, 4, 6, 1, 8 })
{
returnMe.Append((char)('a' + (cycle + length) % 26), length).Append(' ');
}
}
return returnMe.ToString();
}
public static string Mismatches(TextViewport viewport, TextViewport reference, string[] texts, int step)
{
string[] replacements = { "", "x", "\n", " ", "abcd efgh ", "q\nr" };
var returnMe = new System.Text.StringBuilder();
foreach (string text in texts)
{
var source = new StringSource(text);
for (int start = 0; start <= text.Length; start += step)
{
foreach (int length in new[] { 0, 1, 40 })
{
if (start + length > text.Length)
{
continue;
}
foreach (string replacement in replacements)
{
int expected = ByLayingOutTheWholeText(reference, text, start, length, replacement);
int actual = viewport.LinesAdded(source, start, length, replacement);
if (expected != actual)
{
returnMe.Append($"{text.Length}:{start}+{length}->{replacement.Length}:"
+ $" {expected} vs {actual}; ");
}
}
}
}
}
return returnMe.ToString();
}
public void OnProseOfSeveralChunks_MatchesLayingOutTheWholeText()
{
var viewport = new TextViewport(blockChars: 1024, longLineChars: 2048, stretchChars: 1024);
viewport.Configure(Style, multiline: true, fitWidth: 100, fitHeight: 30);
string[] texts = { Prose(55), "ab\n" + Prose(50) + "\ncd\n" + Prose(50) };
CheckEqual("", Mismatches(viewport, NewViewport(multiline: true), texts, step: 512));
}
public void OnLongLinesThatDoNotWrap_MatchesLayingOutTheWholeText()
{
CheckEqual("", Mismatches(NewSidewaysChunked(width: 100), NewSideways(), LongLineTexts, step: 13));
}
public void InTextThatIsNotCountedYet_IsExact()
{
PagedText paged = LongPaged(12000);
string text = TwoRowLines(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
var mismatches = new System.Text.StringBuilder();
foreach (int start in new[] { 0, 7000, 35003, 35006, 83999 })
{
foreach (string replacement in new[] { "", "\n", "x", "abc de\nabc de\n" })
{
int expected = ByLayingOutTheWholeText(viewport, text, start, 0, replacement);
int actual = viewport.LinesAdded(paged, start, 0, replacement);
if (expected != actual)
{
mismatches.AppendLine($"{start} '{replacement}': {expected} vs {actual}");
}
}
}
CheckEqual("", mismatches.ToString());
CheckFalse(viewport.IsCounted(paged));
}
public void InTextThatIsNotCountedYet_RemovingARangeIsExact()
{
PagedText paged = LongPaged(12000);
string text = TwoRowLines(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CheckEqual(ByLayingOutTheWholeText(viewport, text, 35006, 60, ""),
viewport.LinesAdded(paged, 35006, 60, ""));
}
public void LeavesWhatTheViewportHoldsLaidOutAlone()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 2);
viewport.LinesAdded(new StringSource(SevenLines), 0, 0, "x\ny");
CheckEqual(7, viewport.TotalLines(SevenLines));
CheckEqual(2, viewport.TopLine(SevenLines));
}
public void SingleLine_AddsNothing()
{
CheckEqual(0, NewViewport(multiline: false).LinesAdded(new StringSource("ab"), 0, 0, "x\ny"));
}
public void WithoutAFont_AddsNothing()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 30);
CheckEqual(0, viewport.LinesAdded(new StringSource("ab"), 0, 0, "x\ny"));
}
}
public class TopLineTests : TestFixture
{
public void AtTheBottomOfManyLines_ReachesTheTotalWhileTheTextIsNotCounted()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
viewport.ScrollByLines(paged, int.MaxValue);
CheckEqual(viewport.TotalLines(paged), viewport.TopLine(paged) + viewport.VisibleLines);
CheckFalse(viewport.IsCounted(paged));
}
public void AtTheBottomOfCountedText_IsTheTotalLessTheLinesShown()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, SevenLines.Length, caretAtLineEnd: false);
CheckEqual(4, viewport.TopLine(SevenLines));
}
public void StartsAtZero()
{
CheckEqual(0, NewViewport(multiline: true, height: 30).TopLine(SevenLines));
}
public void CountsTheLinesAboveTheWindow()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(SevenLines, caret: SevenLines.Length, caretAtLineEnd: false);
CheckEqual(4, viewport.TopLine(SevenLines));
}
public void CountsWrappedLinesAboveTheWindow()
{
TextViewport viewport = NewViewport(multiline: true, height: 10);
viewport.ScrollToCaret("aaaa bbbb cccc dddd", caret: 19, caretAtLineEnd: false);
CheckEqual(1, viewport.TopLine("aaaa bbbb cccc dddd"));
}
public void SingleLine_IsZeroHoweverFarItScrolls()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
CheckEqual(0, viewport.TopLine("abcdefghijklmnop"));
}
public void WithoutAFont_IsZero()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 30);
CheckEqual(0, viewport.TopLine(SevenLines));
}
}
public class ScrollToLineTests : TestFixture
{
public static void CheckWideTexts(Alignment.Horizontal alignment)
{
foreach (int i in new[] { 1, 5, 6 })
{
string difference = SidewaysLineScrollDifferences(LongLineTexts[i], width: 100, alignment);
if (difference.Length > 0)
{
throw new InvalidOperationException($"Text {i}: {difference}");
}
}
}
public void OnWideLines_ShowWhatALayoutOfTheWholeLineDoes()
{
CheckWideTexts(Alignment.Horizontal.Left);
}
public void OnWideLines_ShowWhatALayoutOfTheWholeLineDoes_RightAligned()
{
CheckWideTexts(Alignment.Horizontal.Right);
}
public void OnWideLines_ShowWhatALayoutOfTheWholeLineDoes_Centred()
{
CheckWideTexts(Alignment.Horizontal.Center);
}
public void OnLongLines_StartTheWindowWhereLayingOutTheWholeLineDoes()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", ScrollToLineDifferences(text, width: 100));
}
}
[Thorough]
public void OnLongLines_StartTheWindowWhereLayingOutTheWholeLineDoes_AtNarrowWidth()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", ScrollToLineDifferences(text, width: 35));
}
}
public void StartsTheWindowAtThatLine()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 2);
CheckEqual(11, viewport.FirstVisible);
CheckEqual(2, viewport.TopLine(SevenLines));
}
public void MeasuresTheWindow()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 2);
CheckEqual(new CaretPosition(Line: 1, X: 20, Y: 10), viewport.CaretAt(16, atLineEnd: false));
}
public void PastTheLastLineThatFillsTheWindow_StopsThere()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 100);
CheckEqual(4, viewport.TopLine(SevenLines));
}
public void NegativeLine_ScrollsToTheStart()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 3);
viewport.ScrollToLine(SevenLines, line: -3);
CheckEqual(0, viewport.FirstVisible);
}
public void TextShorterThanTheWindow_StaysAtTheStart()
{
TextViewport viewport = NewViewport(multiline: true, height: 100);
viewport.ScrollToLine(SevenLines, line: 3);
CheckEqual(0, viewport.FirstVisible);
}
public void WithNoHeightLimit_StaysAtTheStart()
{
TextViewport viewport = NewViewport(multiline: true);
viewport.ScrollToLine(SevenLines, line: int.MaxValue);
CheckEqual(0, viewport.FirstVisible);
}
public void SingleLine_DoesNothing()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
viewport.ScrollToLine("abcdefghijklmnop", line: 0);
CheckEqual(6, viewport.FirstVisible);
}
public void WithoutAFont_DoesNothing()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 30);
viewport.ScrollToLine(SevenLines, line: 3);
CheckEqual(0, viewport.FirstVisible);
}
}
public class RevalidateTests : TestFixture
{
[Thorough]
public void OnLongLines_FillTheWindowAsLayingOutTheWholeLineDoes()
{
foreach (string text in LongLineTexts)
{
CheckEqual("", RevalidateDifferences(text, width: 100));
CheckEqual("", RevalidateDifferences(text, width: 35));
}
}
public void ScrollsBackToFillAWindowThatGrew()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 4);
viewport.Configure(Style, multiline: true, fitWidth: 100, fitHeight: 50);
viewport.Revalidate(SevenLines);
CheckEqual(11, viewport.FirstVisible);
}
public void AWindowWithNoHeightLimitAnymore_ScrollsBackToTheStart()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 4);
viewport.Configure(Style, multiline: true, fitWidth: 100, fitHeight: null);
viewport.Revalidate(SevenLines);
CheckEqual(0, viewport.FirstVisible);
CheckEqual(SevenLines.Length, viewport.WindowEnd);
}
public void KeepsAWindowThatStillFills()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 3);
viewport.Revalidate(SevenLines);
CheckEqual(14, viewport.FirstVisible);
}
public void MovesAWindowStartingMidLineToTheLinesStart()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 4);
viewport.LimitTo(index: 6);
viewport.Revalidate(SevenLines);
CheckEqual(4, viewport.FirstVisible);
}
public void TextThatShrankPastTheWindow_ScrollsBackToFillIt()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 4);
viewport.Revalidate(SevenLines.Substring(0, 12));
CheckEqual(0, viewport.FirstVisible);
}
public void MeasuresTheWindow()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 4);
viewport.Configure(Style, multiline: true, fitWidth: 100, fitHeight: 50);
viewport.Revalidate(SevenLines);
CheckEqual(new CaretPosition(Line: 1, X: 20, Y: 10), viewport.CaretAt(16, atLineEnd: false));
}
public void SingleLine_KeepsTheWindow()
{
TextViewport viewport = NewViewport(multiline: false);
viewport.ScrollToCaret("abcdefghijklmnop", caret: 16, caretAtLineEnd: false);
viewport.Revalidate("abcdefghijklmnop");
CheckEqual(6, viewport.FirstVisible);
CheckEqual(100, viewport.CaretAt(16, atLineEnd: false).X);
}
public void WithoutAFont_ScrollsToTheStart()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 30);
viewport.LimitTo(index: 0);
viewport.Revalidate(SevenLines);
CheckEqual(0, viewport.FirstVisible);
}
}
public class IsInWindowTests : TestFixture
{
public static TextViewport ScrolledToLineTwo()
{
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToLine(SevenLines, line: 2);
return viewport;
}
public void AnIndexBeforeTheWindow_IsOutside()
{
Check(!ScrolledToLineTwo().IsInWindow(4, atLineEnd: false));
}
public void TheWindowsFirstIndex_IsInside()
{
Check(ScrolledToLineTwo().IsInWindow(11, atLineEnd: false));
}
public void TheEndOfTheLastLineShown_IsInside()
{
Check(ScrolledToLineTwo().IsInWindow(24, atLineEnd: false));
}
public void AnIndexBelowTheLastLineShown_IsOutside()
{
Check(!ScrolledToLineTwo().IsInWindow(25, atLineEnd: false));
}
public void AnIndexOnAWrappedRowBelowTheLastLineShown_IsOutside()
{
TextViewport viewport = NewViewport(multiline: true, height: 20);
viewport.ScrollToCaret(WrappedBetween, caret: 0, caretAtLineEnd: false);
Check(viewport.IsInWindow(5, atLineEnd: false));
Check(!viewport.IsInWindow(15, atLineEnd: false));
}
public void Sideways_ACaretLeftOfTheViewIsOutside()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 50);
Check(!viewport.IsInWindow(3, atLineEnd: false));
}
public void Sideways_ACaretInTheViewIsInside()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 50);
Check(viewport.IsInWindow(9, atLineEnd: false));
}
public void Sideways_ACaretRightOfTheViewIsOutside()
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(WideLines, 50);
Check(!viewport.IsInWindow(23, atLineEnd: false));
}
public void SingleLine_IsAlwaysInside()
{
Check(NewViewport(multiline: false).IsInWindow(5000, atLineEnd: false));
}
public void WithoutAFont_IsAlwaysInside()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 100, fitHeight: 30);
Check(viewport.IsInWindow(5000, atLineEnd: false));
}
}
///
/// Runs the same thing over a string source, a paged text and a text with a range spliced into it, which hold
/// the same chars, and checks all of them give the same answer.
///
public static void SameOnEverySource(string text, Func run)
{
string expected = run(new StringSource(text));
CheckEqualStatic(expected, run(PagedTextTests.Paged(text)));
int start = text.Length / 3;
int end = text.Length * 2 / 3;
var spliced = new SplicedText();
spliced.Set(PagedTextTests.Paged(text.Substring(0, start) + "XYZ" + text.Substring(end)), start, start + 3,
text.Substring(start, end - start));
CheckEqualStatic(expected, run(spliced));
}
private static void CheckEqualStatic(string expected, string actual)
{
if (expected != actual)
{
throw new InvalidOperationException($"Expected \"{expected}\" but was \"{actual}\".");
}
}
// Twenty copies of the seven lines, so the text runs over many of the tiny pages.
public static readonly string ManyLines = string.Concat(System.Linq.Enumerable.Repeat(SevenLines + "\n", 20));
public static string WindowAfter(TextViewport viewport)
{
return $"{viewport.FirstVisible},{viewport.WindowEnd},{viewport.ScrollX}";
}
public class ScrollToCaretSourceTests : TestFixture
{
public void LaysOutTheSameWindowWhereverTheCaretIs()
{
foreach (int caret in new[] { 0, 6, ManyLines.Length / 2, ManyLines.Length })
{
SameOnEverySource(ManyLines, source =>
{
TextViewport viewport = NewViewport(multiline: true, width: 30, height: 30);
viewport.ScrollToCaret(source, caret, caretAtLineEnd: false);
return WindowAfter(viewport);
});
}
}
public void ASingleLineWindow_ScrollsTheSame()
{
SameOnEverySource(new string('a', 40) + "bcdefg", source =>
{
TextViewport viewport = NewViewport(multiline: false, width: 100);
viewport.ScrollToCaret(source, source.Length, caretAtLineEnd: false);
return WindowAfter(viewport);
});
}
public void LaysOutAgainWhenThePagedTextChanges()
{
PagedText paged = PagedTextTests.Paged(SevenLines);
TextViewport viewport = NewViewport(multiline: true, height: 30);
viewport.ScrollToCaret(paged, SevenLines.Length, caretAtLineEnd: false);
CheckEqual(4, viewport.TopLine(paged));
paged.Replace(0, 0, "x\ny\nz\n");
viewport.ScrollToCaret(paged, paged.Length, caretAtLineEnd: false);
CheckEqual(7, viewport.TopLine(paged));
}
public void LaysOutAgainWhenTheSplicedTextIsSetAgain()
{
var spliced = new SplicedText();
var text = new StringSource("a\nb");
spliced.Set(text, 0, 1, "q");
TextViewport viewport = NewViewport(multiline: true, height: 30);
CheckEqual(2, viewport.TotalLines(spliced));
spliced.Set(text, 0, 1, "q\nr\ns");
CheckEqual(4, viewport.TotalLines(spliced));
}
}
public class SourceQueriesTests : TestFixture
{
public void TotalLinesAndTopLine_AreTheSame()
{
SameOnEverySource(ManyLines, source =>
{
TextViewport viewport = NewViewport(multiline: true, width: 30, height: 30);
viewport.ScrollToCaret(source, source.Length / 2, caretAtLineEnd: false);
return viewport.TotalLines(source) + "," + viewport.TopLine(source);
});
}
public void ScrollToLine_ScrollsTheSame()
{
SameOnEverySource(ManyLines, source =>
{
TextViewport viewport = NewViewport(multiline: true, width: 30, height: 30);
viewport.ScrollToLine(source, 17);
return WindowAfter(viewport);
});
}
public void Revalidate_KeepsTheSameWindow()
{
SameOnEverySource(ManyLines, source =>
{
TextViewport viewport = NewViewport(multiline: true, width: 30, height: 30);
viewport.ScrollToLine(source, 17);
viewport.Revalidate(source);
return WindowAfter(viewport);
});
}
public void LineEdge_LeadsToTheSameIndex()
{
SameOnEverySource(ManyLines, source =>
{
TextViewport viewport = NewViewport(multiline: true, width: 30, height: 30);
int caret = source.Length / 2;
int home = viewport.LineEdge(source, caret, false, end: false, out bool homeAtEnd);
int end = viewport.LineEdge(source, caret, false, end: true, out bool endAtEnd);
return $"{home},{homeAtEnd},{end},{endAtEnd}";
});
}
public void LineTarget_LeadsToTheSameIndex()
{
SameOnEverySource(ManyLines, source =>
{
TextViewport viewport = NewViewport(multiline: true, width: 30, height: 30);
int down = viewport.LineTarget(source, 70, false, lines: 3, forward: true, version: 1, out _);
int up = viewport.LineTarget(source, 70, false, lines: 3, forward: false, version: 1, out _);
return $"{down},{up}";
});
}
public void PageTarget_ScrollsAndLeadsToTheSameIndex()
{
SameOnEverySource(ManyLines, source =>
{
TextViewport viewport = NewViewport(multiline: true, width: 30, height: 30);
viewport.ScrollToCaret(source, 0, caretAtLineEnd: false);
int index = viewport.PageTarget(source, 4, false, forward: true, version: 1, out _);
return $"{index},{viewport.FirstVisible}";
});
}
public void ContentWidthAndScrollToX_AreTheSame()
{
SameOnEverySource(ManyLines + WideLines, source =>
{
TextViewport viewport = NewSideways();
viewport.ScrollToX(source, 50);
return viewport.ContentWidth(source) + "," + WindowAfter(viewport);
});
}
}
// Lines of two rows each at 50 wide, enough of them to take longer than a question's worth of counting.
public static string TwoRowLines(int lines)
{
return string.Concat(System.Linq.Enumerable.Repeat("abc de\n", lines));
}
public static PagedText LongPaged(int lines)
{
return PagedTextTests.Paged(TwoRowLines(lines), PagedText.DefaultPageSize);
}
public static void CountAll(TextViewport viewport, ITextSource text)
{
int guard = 0;
while (viewport.CountMore(text, 20000))
{
if (++guard > 1000)
{
throw new InvalidOperationException("Counting never finished.");
}
}
}
public class CountMoreTests : TestFixture
{
public void FinishesCountingALongText()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CheckEqual(12001, viewport.TotalLines(paged));
CountAll(viewport, paged);
CheckEqual(24001, viewport.TotalLines(paged));
}
public void ReportsWhetherItCounted()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CheckTrue(viewport.CountMore(paged, 20000));
CountAll(viewport, paged);
CheckFalse(viewport.CountMore(paged, 20000));
}
public void SingleLine_CountsNothing()
{
TextViewport viewport = NewViewport(multiline: false);
CheckFalse(viewport.CountMore(LongPaged(12000), 20000));
}
public void WithoutAFont_CountsNothing()
{
var viewport = new TextViewport();
viewport.Configure(new TextMaterialStyle(), multiline: true, fitWidth: 50, fitHeight: 30);
CheckFalse(viewport.CountMore(LongPaged(12000), 20000));
}
public void ACompositionIsCountedThroughTheTextUnderneath()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
var spliced = new SplicedText();
spliced.Set(paged, 35000, 35000, "q");
CountAll(viewport, spliced);
CheckEqual(24001, viewport.TotalLines(paged));
}
public void NeverMovesTheWindow()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
viewport.ScrollToCaret(paged, caret: 49000, caretAtLineEnd: false);
int first = viewport.FirstVisible;
int end = viewport.WindowEnd;
int topLineEstimated = viewport.TopLine(paged);
CountAll(viewport, paged);
CheckEqual(first, viewport.FirstVisible);
CheckEqual(end, viewport.WindowEnd);
CheckEqual(2 * topLineEstimated, viewport.TopLine(paged));
}
}
public class IsCountedTests : TestFixture
{
public void ALongText_IsNotUntilCounted()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CheckFalse(viewport.IsCounted(paged));
CountAll(viewport, paged);
CheckTrue(viewport.IsCounted(paged));
}
public void ASmallText_IsAtOnce()
{
CheckTrue(NewViewport(multiline: true).IsCounted(new StringSource(SevenLines)));
}
public void AWindowOfOneLine_HasNothingToCount()
{
CheckTrue(NewViewport(multiline: false).IsCounted(LongPaged(12000)));
}
public void ACompositionIsAskedOfTheTextUnderneath()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
var spliced = new SplicedText();
spliced.Set(paged, 35000, 35000, "q");
CheckFalse(viewport.IsCounted(spliced));
CountAll(viewport, paged);
CheckTrue(viewport.IsCounted(spliced));
}
}
public class NoteEditTests : TestFixture
{
public void KeepsWhatWasCounted()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CountAll(viewport, paged);
paged.Replace(35000, 0, "x");
viewport.NoteEdit(paged, 35000, 0, 1);
CheckEqual(24001, viewport.TotalLines(paged));
CheckFalse(viewport.CountMore(paged, 20000));
}
public void IsHowAnEditAddsLines()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CountAll(viewport, paged);
paged.Replace(35000, 0, "\n\n");
viewport.NoteEdit(paged, 35000, 0, 2);
CheckEqual(24003, viewport.TotalLines(paged));
CheckFalse(viewport.CountMore(paged, 20000));
}
public void WithoutIt_TheTextIsCountedAgain()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CountAll(viewport, paged);
paged.Replace(35000, 0, "x");
CheckTrue(viewport.CountMore(paged, 20000));
CountAll(viewport, paged);
CheckEqual(24001, viewport.TotalLines(paged));
}
}
public static readonly string WideLineText = "ab\n" + VisualLineIndexTests.Repeat("abcd efgh ", 20) + "\ncd";
public class HasRowOriginsTests : TestFixture
{
public void WithAWideLineInTheWindow_Is()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX(WideLineText, 0);
CheckTrue(viewport.HasRowOrigins);
}
public void WithOnlyShortLinesInTheWindow_Isnt()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX("ab\ncd\nef", 0);
CheckFalse(viewport.HasRowOrigins);
}
public void WithAWideLineBelowTheWindow_Isnt()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX("ab\ncd\nef\n" + VisualLineIndexTests.Repeat("abcd efgh ", 20), 0);
CheckFalse(viewport.HasRowOrigins);
}
public void WhenTheWindowHasNoHeightLimit_Isnt()
{
var viewport = new TextViewport(blockChars: 16, longLineChars: 48, stretchChars: 24);
viewport.Configure(Style, multiline: true, fitWidth: 100, fitHeight: null, wrap: false, caretWidth: 2);
viewport.ScrollToX(WideLineText, 0);
CheckFalse(viewport.HasRowOrigins);
}
public void WhenRightAligned_Is()
{
TextViewport viewport = NewAlignedChunked(Alignment.Horizontal.Right, 100);
viewport.ScrollToX(WideLineText, 0);
CheckTrue(viewport.HasRowOrigins);
}
public void WhenCentred_Is()
{
TextViewport viewport = NewAlignedChunked(Alignment.Horizontal.Center, 100);
viewport.ScrollToX(WideLineText, 0);
CheckTrue(viewport.HasRowOrigins);
}
public void WhenWrapping_Isnt()
{
TextViewport viewport = NewChunked(width: 100, height: 30);
viewport.ScrollToX(WideLineText, 0);
CheckFalse(viewport.HasRowOrigins);
}
public void InAComposedText_WithAWideLine_Is()
{
TextViewport viewport = NewSidewaysChunked(100);
var spliced = new SplicedText();
spliced.Set(PagedTextTests.Paged(WideLineText), 5, 5, "xy");
viewport.ScrollToX(spliced, 0);
CheckTrue(viewport.HasRowOrigins);
}
public void ForgetsAWideLineThatIsGone()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX(WideLineText, 0);
viewport.ScrollToX("ab\ncd", 0);
CheckFalse(viewport.HasRowOrigins);
}
}
public class RowOriginTests : TestFixture
{
public void OfAShortRow_IsTheScrollBackwards()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX(WideLineText, 70);
CheckEqual(-70L, viewport.RowOrigin(0));
}
public void OfAWideRow_PutsItsFirstCharJustBeforeTheView()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX(WideLineText, 500);
long origin = viewport.RowOrigin(1);
CheckTrue(origin <= 0);
CheckTrue(origin > -100);
CheckEqual(viewport.RowOriginCount, 3);
}
public void OfShortRowsRightAlignedBesideALineWiderThanAnInt_AreLongs()
{
string text = VisualLineIndexTests.Repeat("ab", 3000) + "\ncd\nef\ngh";
PagedText paged = PagedTextTests.Paged(text);
TextViewport viewport = NewAlignedChunked(Alignment.Horizontal.Right, 100, WideStyle);
CountAll(viewport, paged);
viewport.ScrollToLine(paged, 1);
CheckTrue(viewport.HasRowOrigins);
CheckEqual(6001, viewport.FirstVisible);
CheckEqual(6000L * 1000000 + 2 - 100, viewport.ScrollX);
CheckEqual(-2000000L + 98, viewport.RowOrigin(0));
CheckEqual(98, viewport.CaretAt(6003, atLineEnd: false).X);
}
public void OfShortRowsCentredBesideALineWiderThanAnInt_AreLongs()
{
string text = VisualLineIndexTests.Repeat("ab", 3000) + "\ncd\nef\ngh";
PagedText paged = PagedTextTests.Paged(text);
TextViewport viewport = NewAlignedChunked(Alignment.Horizontal.Center, 100, WideStyle);
CountAll(viewport, paged);
viewport.ScrollToLine(paged, 1);
long start = (6000L * 1000000 - 2000000) / 2 - viewport.ScrollX;
CheckEqual(start, viewport.RowOrigin(0));
}
public void OfTheWidestLine_StaysAtTheStartWhileTheLineIsReckoned()
{
string text = VisualLineIndexTests.Repeat("abcd efgh ", 2000);
PagedText paged = PagedTextTests.Paged(text);
foreach (Alignment.Horizontal alignment in
new[] { Alignment.Horizontal.Right, Alignment.Horizontal.Center })
{
TextViewport viewport = NewAlignedChunked(alignment, 100,
synchronousChars: UncountedSynchronousChars);
viewport.ScrollToX(paged, 12000);
long firstGlyphAt = viewport.RowOrigin(0) + viewport.ScrollX;
for (int step = 0; step < 5; ++step)
{
viewport.CountMore(paged, 4000);
viewport.ScrollToX(paged, 12000);
CheckEqual(12000L, viewport.ScrollX);
CheckEqual(firstGlyphAt, viewport.RowOrigin(0) + viewport.ScrollX);
}
}
}
public void OfAWindowOfManyRows_AnswersForEveryRow()
{
var viewport = new TextViewport(blockChars: 16, longLineChars: 48, stretchChars: 24);
viewport.Configure(Style, multiline: true, fitWidth: 100, fitHeight: 400, wrap: false, caretWidth: 2);
string text = VisualLineIndexTests.Repeat("ab\n", 20) + VisualLineIndexTests.Repeat("abcd efgh ", 20)
+ "\ncd";
viewport.ScrollToX(text, 300);
CheckEqual(22, viewport.RowOriginCount);
CheckEqual(-300L, viewport.RowOrigin(0));
CheckEqual(-300L, viewport.RowOrigin(19));
CheckTrue(viewport.RowOrigin(20) > -400 && viewport.RowOrigin(20) <= 0);
CheckEqual(-300L, viewport.RowOrigin(21));
}
public void WithoutOrigins_Throws()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX("ab", 0);
CheckThrow(typeof(ArgumentOutOfRangeException));
viewport.RowOrigin(0);
}
public void OfARowTheWindowLacks_Throws()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX(WideLineText, 0);
CheckThrow(typeof(ArgumentOutOfRangeException));
viewport.RowOrigin(3);
}
public void OfARowBeforeTheFirst_Throws()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX(WideLineText, 0);
CheckThrow(typeof(ArgumentOutOfRangeException));
viewport.RowOrigin(-1);
}
}
public class WindowTextTests : TestFixture
{
public void BeforeAnythingIsLaidOut_IsEmpty()
{
CheckTrue(new TextViewport().WindowText.IsDefault);
}
public void OfAWideLine_IsThePartAroundTheViewJoinedToTheLinesAroundIt()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX(WideLineText, 300);
string window = viewport.WindowText.ToString();
CheckTrue(window.StartsWith("ab\n"));
CheckTrue(window.EndsWith("\ncd"));
CheckGreater(WideLineText.Length - 10, window.Length);
CheckTrue(WideLineText.Contains(window.Substring(3, window.Length - 6)));
}
public void OfShortLines_IsTheLines()
{
TextViewport viewport = NewSidewaysChunked(100);
viewport.ScrollToX("ab\ncd\nef", 0);
CheckEqual("ab\ncd\nef", viewport.WindowText.ToString());
}
}
public class CompositionInALongTextTests : TestFixture
{
public void AddsTheLinesItBrings()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CountAll(viewport, paged);
var spliced = new SplicedText();
spliced.Set(paged, 35000, 35000, "\n\n");
CheckEqual(24003, viewport.TotalLines(spliced));
}
public void ReplacesTheLinesItStandsFor()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CountAll(viewport, paged);
var spliced = new SplicedText();
spliced.Set(paged, 35000, 35014, "x");
CheckEqual(24001 - 4, viewport.TotalLines(spliced));
}
public void ScrollsToALineBelowIt()
{
PagedText paged = LongPaged(12000);
TextViewport viewport = NewViewport(multiline: true, width: 50, height: 30);
CountAll(viewport, paged);
var spliced = new SplicedText();
spliced.Set(paged, 35000, 35000, "\n\n");
viewport.ScrollToLine(spliced, 10010);
CheckEqual(10010, viewport.TopLine(spliced));
}
}
public class KeepGoalColumnTests : TestFixture
{
public void IsCheckedAgainstTheVersionOfTheNextMove()
{
TextViewport viewport = NewViewport(multiline: true);
int shorter = viewport.LineTarget(SevenLines, caret: 10, caretAtLineEnd: false, lines: 1,
forward: true, version: 1, out _);
viewport.KeepGoalColumn(version: 7);
CheckEqual(18, viewport.LineTarget(SevenLines, shorter, caretAtLineEnd: false, lines: 1,
forward: true, version: 7, out _));
CheckEqual(16, viewport.LineTarget(SevenLines, shorter, caretAtLineEnd: false, lines: 1,
forward: true, version: 8, out _));
}
}
}
}