Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 219 additions & 2 deletions CSharpMath.Core.Tests/Editor/KeyPressTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,128 @@ namespace CSharpMath.Core.EditorTests {
using Editor;
public class KeyPressTests {
private static readonly TypesettingContext<TestFont, TGlyph> context = TestTypesettingContext.Instance;
static void Test(string latex, K[] inputs) {
static void Test(string latex, params K[] inputs) {
var keyboard = new MathKeyboard<TestFont, TGlyph>(context, new TestFont());
keyboard.KeyPress(inputs);
Assert.Equal(latex, keyboard.LaTeX);
}
static void Test(string latex, TestFont font, params K[] inputs) {
var keyboard = new MathKeyboard<TestFont, TGlyph>(context, font);
keyboard.KeyPress(inputs);
Assert.Equal(latex, keyboard.LaTeX);
}
static void TestVisual(MathKeyboardHorizontalNavigationMode mode, string latex, params K[] inputs) {
var keyboard = VisualKeyboard(mode);
keyboard.KeyPress(inputs);
Assert.Equal(latex, keyboard.LaTeX);
}
static MathKeyboard<TestFont, TGlyph> VisualKeyboard(MathKeyboardHorizontalNavigationMode mode) =>
new(context, new TestFont(10)) { HorizontalNavigationMode = mode };

[Fact]
public void HorizontalNavigationDefaultsToExhaustive() {
var keyboard = new MathKeyboard<TestFont, TGlyph>(context, new TestFont());
Assert.Equal(MathKeyboardHorizontalNavigationMode.Exhaustive, keyboard.HorizontalNavigationMode);
}

[Fact]
public void VisualUpperFractionLeavesEitherRowAndReentersNumerator() =>
TestVisual(MathKeyboardHorizontalNavigationMode.VisualUpper, @"\frac{13}{2}",
K.D1, K.Slash, K.D2, K.Right, K.Left, K.D3);

[Fact]
public void VisualLowerFractionLeavesEitherRowAndReentersDenominator() =>
TestVisual(MathKeyboardHorizontalNavigationMode.VisualLower, @"\frac{1}{23}",
K.D1, K.Slash, K.D2, K.Right, K.Left, K.D3);

[Theory]
[InlineData(MathKeyboardHorizontalNavigationMode.VisualUpper, MathListSubIndexType.Numerator)]
[InlineData(MathKeyboardHorizontalNavigationMode.VisualLower, MathListSubIndexType.Denominator)]
public void VisualFractionEntryAndExitAreDirectional(
MathKeyboardHorizontalNavigationMode mode, MathListSubIndexType preferredBranch) {
var keyboard = VisualKeyboard(mode);
keyboard.KeyPress(K.D1, K.Slash, K.D2);
var fraction = Assert.IsType<CSharpMath.Atom.Atoms.Fraction>(Assert.Single(keyboard.MathList));
var branchCount = preferredBranch == MathListSubIndexType.Numerator
? fraction.Numerator.Count : fraction.Denominator.Count;

keyboard.InsertionIndex = new(0);
keyboard.KeyPress(K.Right);
Assert.Equal(new MathListIndex(0).LevelUpWithSubIndex(preferredBranch, 0), keyboard.InsertionIndex);
keyboard.KeyPress(K.Left);
Assert.Equal(new MathListIndex(0), keyboard.InsertionIndex);

keyboard.InsertionIndex = new(1);
keyboard.KeyPress(K.Left);
Assert.Equal(new MathListIndex(0).LevelUpWithSubIndex(preferredBranch, branchCount), keyboard.InsertionIndex);
keyboard.KeyPress(K.Right);
Assert.Equal(new MathListIndex(1), keyboard.InsertionIndex);
}

[Theory]
[InlineData(MathKeyboardHorizontalNavigationMode.VisualUpper, MathListSubIndexType.Numerator, MathListSubIndexType.Denominator, K.Down)]
[InlineData(MathKeyboardHorizontalNavigationMode.VisualLower, MathListSubIndexType.Denominator, MathListSubIndexType.Numerator, K.Up)]
public void VisualFractionOtherRowRemainsReachableVertically(
MathKeyboardHorizontalNavigationMode mode, MathListSubIndexType preferredBranch,
MathListSubIndexType otherBranch, K verticalInput) {
var keyboard = VisualKeyboard(mode);
keyboard.KeyPress(K.D1, K.Slash, K.D2);
keyboard.InsertionIndex = new MathListIndex(0).LevelUpWithSubIndex(preferredBranch, 0);

keyboard.KeyPress(verticalInput);

Assert.Equal(otherBranch, keyboard.InsertionIndex.FinalSubIndexType);
keyboard.InsertionIndex = new MathListIndex(0).LevelUpWithSubIndex(otherBranch, 0);
keyboard.KeyPress(K.Left);
Assert.Equal(new MathListIndex(0), keyboard.InsertionIndex);
}

[Theory]
[InlineData(MathKeyboardHorizontalNavigationMode.VisualUpper, MathListSubIndexType.Superscript)]
[InlineData(MathKeyboardHorizontalNavigationMode.VisualLower, MathListSubIndexType.Subscript)]
public void VisualScriptPolicyChoosesPreferredScript(
MathKeyboardHorizontalNavigationMode mode, MathListSubIndexType preferredBranch) {
var keyboard = VisualKeyboard(mode);
keyboard.KeyPress(K.SmallX, K.Subscript, K.D2, K.Right, K.Power, K.D3);

keyboard.InsertionIndex = new(1);
keyboard.KeyPress(K.Left);

Assert.Equal(preferredBranch, keyboard.InsertionIndex.FinalSubIndexType);
}

[Theory]
[InlineData(MathKeyboardHorizontalNavigationMode.VisualUpper, K.Subscript, MathListSubIndexType.Subscript)]
[InlineData(MathKeyboardHorizontalNavigationMode.VisualLower, K.Power, MathListSubIndexType.Superscript)]
public void VisualScriptPolicyFallsBackToOnlyScript(
MathKeyboardHorizontalNavigationMode mode, K scriptInput, MathListSubIndexType availableBranch) {
var keyboard = VisualKeyboard(mode);
keyboard.KeyPress(K.SmallX, scriptInput, K.D2);
keyboard.InsertionIndex = new(1);

keyboard.KeyPress(K.Left);

Assert.Equal(availableBranch, keyboard.InsertionIndex.FinalSubIndexType);
}

[Theory]
[InlineData(MathKeyboardHorizontalNavigationMode.VisualUpper, MathListSubIndexType.Numerator, MathListSubIndexType.Superscript)]
[InlineData(MathKeyboardHorizontalNavigationMode.VisualLower, MathListSubIndexType.Denominator, MathListSubIndexType.Subscript)]
public void VisualCompoundRetainsIntrinsicAndScriptStages(
MathKeyboardHorizontalNavigationMode mode, MathListSubIndexType intrinsicBranch,
MathListSubIndexType scriptBranch) {
var keyboard = VisualKeyboard(mode);
keyboard.KeyPress(K.D1, K.Slash, K.D2, K.Right, K.Power, K.D3, K.Right, K.Subscript, K.D4);
var fraction = Assert.IsType<CSharpMath.Atom.Atoms.Fraction>(Assert.Single(keyboard.MathList));
var intrinsicCount = intrinsicBranch == MathListSubIndexType.Numerator
? fraction.Numerator.Count : fraction.Denominator.Count;
keyboard.InsertionIndex = new MathListIndex(0).LevelUpWithSubIndex(intrinsicBranch, intrinsicCount);

keyboard.KeyPress(K.Right);
Assert.Equal(MathListSubIndexType.BetweenBaseAndScripts, keyboard.InsertionIndex.FinalSubIndexType);
keyboard.KeyPress(K.Right);
Assert.Equal(scriptBranch, keyboard.InsertionIndex.FinalSubIndexType);
}
static void TestEvent(EventInteractor attach, EventInteractor detach, K[] inputs) {
var keyboard = new MathKeyboard<TestFont, TGlyph>(context, new TestFont());
Assert.Raises<EventArgs>(
Expand Down Expand Up @@ -402,5 +519,105 @@ public void Return(params K[] inputs) =>
T(@"\frac{\lim _{x\rightarrow 2}}{■}", K.LimitWithBase, K.SmallX, K.RightArrow, K.D2, K.Right, K.Slash),
]
public void Slash(string latex, params K[] inputs) => Test(latex, inputs);

[Theory,
T(@"", K.Up, K.Up, K.Up, K.Down, K.Down, K.Down),
T(@"1", K.Up, K.D1), T(@"1", K.Down, K.D1),
T(@"\frac{\square }{■}", K.Fraction, K.Down),
T(@"\frac{\square }{1}", K.Fraction, K.Down, K.D1),
T(@"\frac{■}{\square }", K.Fraction, K.Down, K.Up),
T(@"\frac{1}{\square }", K.Fraction, K.Down, K.Up, K.D1),
T(@"\frac{\square }{■}", K.Fraction, K.Down, K.Up, K.Down),
T(@"\frac{\square }{1}", K.Fraction, K.Down, K.Up, K.Down, K.D1),
T(@"\frac{123x456}{y}", K.Fraction, K.D1, K.D2, K.D3, K.D4, K.D5, K.D6, K.Down, K.Up, K.SmallX, K.Down, K.SmallY),
T(@"\frac{y}{123x456}", K.Fraction, K.Down, K.D1, K.D2, K.D3, K.D4, K.D5, K.D6, K.Up, K.Down, K.SmallX, K.Up, K.SmallY),
T(@"\frac{1234z56}{xy}", K.Fraction, K.D1, K.D2, K.D3, K.D4, K.D5, K.D6, K.Down, K.SmallX, K.SmallY, K.Up, K.SmallZ),
T(@"\frac{xy}{1234z56}", K.Fraction, K.Down, K.D1, K.D2, K.D3, K.D4, K.D5, K.D6, K.Up, K.SmallX, K.SmallY, K.Down, K.SmallZ),
T(@"\frac{\sqrt{2}}{■}", K.Fraction, K.SquareRoot, K.D2, K.Down),
T(@"\frac{■}{\sqrt{2}}", K.Fraction, K.Down, K.SquareRoot, K.D2, K.Up),
T(@"\frac{\sqrt{789}}{\square }", K.Fraction, K.SquareRoot, K.D8, K.D9, K.Down, K.Up, K.D7),
T(@"\frac{\square }{\sqrt{789}}", K.Fraction, K.Down, K.SquareRoot, K.D8, K.D9, K.Up, K.Down, K.D7),
T(@"\frac{\left( a\right) }{\left( b\right) }", K.Fraction, K.BothRoundBrackets, K.Down, K.BothRoundBrackets, K.Up, K.SmallA, K.Down, K.SmallB),
T(@"\frac{\left( b\right) }{\left( a\right) }", K.Fraction, K.Down, K.BothRoundBrackets, K.Up, K.BothRoundBrackets, K.Down, K.SmallA, K.Up, K.SmallB),
T(@"2^{ab}c", K.D2, K.Power, K.SmallA, K.Up, K.SmallB, K.Down, K.SmallC),
T(@"2_{ab}c", K.D2, K.Subscript, K.SmallA, K.Down, K.SmallB, K.Up, K.SmallC),
T(@"\square _1^3", K.Subscript, K.Up, K.Power, K.Down, K.D1, K.Up, K.D3),
T(@"123^■", K.D1, K.Power, K.Down, K.D2, K.D3, K.Up),
T(@"123_■", K.D1, K.Subscript, K.Up, K.D2, K.D3, K.Down),
T(@"1^{\square ^{\square ^{4^{\square }}}}", K.Power, K.Power, K.Power, K.Power, K.Down, K.Down, K.Down, K.Down, K.D1, K.Up, K.Up, K.Up, K.D4),
T(@"1_{\square _{\square _{4_{\square }}}}", K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Up, K.Up, K.Up, K.Up, K.D1, K.Down, K.Down, K.Down, K.D4),
T(@"2_{cab}^{\square }", K.D2, K.Subscript, K.SmallA, K.SmallB, K.Up, K.Power, K.Down, K.SmallC),
T(@"2c_{\square }^{ab}", K.D2, K.Power, K.SmallA, K.SmallB, K.Down, K.Subscript, K.Up, K.SmallC),
T(@"\square ^{\square }2", K.Power, K.Down, K.D2),
T(@"\square _{\square }2", K.Subscript, K.Up, K.D2),
T(@"1^{\square ^{\square ^{\square ^5}}}", K.Power, K.Power, K.Power, K.Power, K.Down, K.Down, K.Down, K.Down, K.D1, K.Up, K.Up, K.Up, K.Up, K.D5),
T(@"1_{\square _{\square _{\square _5}}}", K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Up, K.Up, K.Up, K.Up, K.D1, K.Down, K.Down, K.Down, K.Down, K.D5),
T(@"\frac{1^{\square ^{\square ^{4^{\square }}}}}{\square }", K.Fraction, K.Power, K.Power, K.Power, K.Power, K.Down, K.Down, K.Down, K.Down, K.D1, K.Up, K.Up, K.Up, K.D4),
T(@"\frac{1_{\square _{\square _{4_{\square }}}}}{\square }", K.Fraction, K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Up, K.Up, K.Up, K.Up, K.D1, K.Down, K.Down, K.Down, K.D4),
T(@"\frac{\square }{1^{\square ^{\square ^{4^{\square }}}}}", K.Fraction, K.Down, K.Power, K.Power, K.Power, K.Power, K.Down, K.Down, K.Down, K.Down, K.D1, K.Up, K.Up, K.Up, K.D4),
T(@"\frac{\square }{1_{\square _{\square _{4_{\square }}}}}", K.Fraction, K.Down, K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Up, K.Up, K.Up, K.Up, K.D1, K.Down, K.Down, K.Down, K.D4),
T(@"\frac{1^{\square ^{\square ^{\square ^5}}}}{\square }", K.Fraction, K.Power, K.Power, K.Power, K.Power, K.Down, K.Down, K.Down, K.Down, K.D1, K.Up, K.Up, K.Up, K.Up, K.D5),
T(@"\frac{1_{\square _{\square _{\square _5}}}}{\square }", K.Fraction, K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Up, K.Up, K.Up, K.Up, K.D1, K.Down, K.Down, K.Down, K.Down, K.D5),
T(@"\frac{\square }{1^{\square ^{\square ^{\square ^5}}}}", K.Fraction, K.Down, K.Power, K.Power, K.Power, K.Power, K.Down, K.Down, K.Down, K.Down, K.D1, K.Up, K.Up, K.Up, K.Up, K.D5),
T(@"\frac{\square }{1_{\square _{\square _{\square _5}}}}", K.Fraction, K.Down, K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Up, K.Up, K.Up, K.Up, K.D1, K.Down, K.Down, K.Down, K.Down, K.D5)]
public void UpDownNavigation(string latex, params K[] inputs) => Test(latex, new TestFont(10), inputs);

[Theory,
T(@"\square ^21", K.Power, K.D2, K.Left, K.Down, K.D1),
T(@"\square _21", K.Subscript, K.D2, K.Left, K.Up, K.D1),
T(@"01^2", K.D0, K.Power, K.D2, K.Left, K.Down, K.D1),
T(@"01_2", K.D0, K.Subscript, K.D2, K.Left, K.Up, K.D1),
T(@"1^{23}", K.Power, K.D2, K.D3, K.Left, K.Left, K.Down, K.D1),
T(@"1_{23}", K.Subscript, K.D2, K.D3, K.Left, K.Left, K.Up, K.D1),
T(@"x^{zy}", K.SmallX, K.Power, K.SmallY, K.Left, K.Left, K.Up, K.SmallZ),
T(@"x_{zy}", K.SmallX, K.Subscript, K.SmallY, K.Left, K.Left, K.Down, K.SmallZ),
T(@"1^23", K.D1, K.Power, K.Right, K.D3, K.Left, K.Left, K.Up, K.D2),
T(@"1_23", K.D1, K.Subscript, K.Right, K.D3, K.Left, K.Left, K.Down, K.D2),
T(@"1^{\left( 2\right) }3", K.D1, K.Power, K.BothRoundBrackets, K.Right, K.Right, K.D3, K.Left, K.Up, K.Left, K.D2),
T(@"1_{\left( 2\right) }3", K.D1, K.Subscript, K.BothRoundBrackets, K.Right, K.Right, K.D3, K.Left, K.Down, K.Left, K.D2),
T(@"\frac{a_{123x456}}{b^{6543y21}}", K.Fraction, K.SmallA, K.Subscript, K.D1, K.D2, K.D3, K.D4, K.D5, K.D6, K.Down, K.Down,
K.SmallB, K.Power, K.D6, K.D5, K.D4, K.D3, K.D2, K.D1, K.Left, K.Left, K.Left, K.Up, K.SmallX, K.Down, K.SmallY),
T(@"\frac{a^{1e23456}b}{cd_{654321}}", K.Fraction, K.SmallA, K.Power, K.D1, K.D2, K.D3, K.D4, K.D5, K.D6, K.Down, K.SmallB, K.Down,
K.SmallC, K.Subscript, K.D6, K.D5, K.D4, K.D3, K.D2, K.D1, K.Left, K.Left, K.Left, K.Up, K.SmallD, K.Up, K.SmallE),
T(@"1^{\square ^{\square ^{4^{\square }}}}", K.Power, K.Power, K.Power, K.Power, K.Left, K.Left, K.Left, K.Left, K.D1, K.Up, K.Up, K.Up, K.D4),
T(@"1_{\square _{\square _{4_{\square }}}}", K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Left, K.Left, K.Left, K.Left, K.D1, K.Down, K.Down, K.Down, K.D4),
T(@"\frac{1^{\square ^{\square ^{4^{\square }}}}}{\square }", K.Fraction, K.Power, K.Power, K.Power, K.Power, K.Left, K.Left, K.Left, K.Left, K.D1, K.Up, K.Up, K.Up, K.D4),
T(@"\frac{1_{\square _{\square _{4_{\square }}}}}{\square }", K.Fraction, K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Left, K.Left, K.Left, K.Left, K.D1, K.Down, K.Down, K.Down, K.D4),
T(@"\frac{\square }{1^{\square ^{\square ^{4^{\square }}}}}", K.Fraction, K.Down, K.Power, K.Power, K.Power, K.Power, K.Left, K.Left, K.Left, K.Left, K.D1, K.Up, K.Up, K.Up, K.D4),
T(@"\frac{\square }{1_{\square _{\square _{4_{\square }}}}}", K.Fraction, K.Down, K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Left, K.Left, K.Left, K.Left, K.D1, K.Down, K.Down, K.Down, K.D4),
T(@"1^{\square ^{\square ^{\square ^{\square }}7}8}9", K.D1, K.Power, K.Power, K.Power, K.Power, K.Right, K.Down, K.Down, K.Down, K.D9, K.Left, K.Up, K.Up, K.D7, K.Down, K.D8),
T(@"1_{\square _{\square _{\square _{\square }}7}8}9", K.D1, K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Right, K.Up, K.Up, K.Up, K.D9, K.Left, K.Down, K.Down, K.D7, K.Up, K.D8),
T(@"\frac{1^{\square ^{\square ^{\square ^{\square }}7}8}9}{\square }", K.Fraction, K.D1, K.Power, K.Power, K.Power, K.Power, K.Right, K.Down, K.Down, K.Down, K.D9, K.Left, K.Up, K.Up, K.D7, K.Down, K.D8),
T(@"\frac{1_{\square _{\square _{\square _{\square }}7}8}9}{\square }", K.Fraction, K.D1, K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Right, K.Up, K.Up, K.Up, K.D9, K.Left, K.Down, K.Down, K.D7, K.Up, K.D8),
T(@"\frac{\square }{1^{\square ^{\square ^{\square ^{\square }}7}8}9}", K.Fraction, K.Down, K.D1, K.Power, K.Power, K.Power, K.Power, K.Right, K.Down, K.Down, K.Down, K.D9, K.Left, K.Up, K.Up, K.D7, K.Down, K.D8),
T(@"\frac{\square }{1_{\square _{\square _{\square _{\square }}7}8}9}", K.Fraction, K.Down, K.D1, K.Subscript, K.Subscript, K.Subscript, K.Subscript, K.Right, K.Up, K.Up, K.Up, K.D9, K.Left, K.Down, K.Down, K.D7, K.Up, K.D8),
T(@"\frac{ef^{gh}}{\square }", K.Fraction, K.SmallE, K.Power, K.SmallH, K.Left, K.Down, K.SmallF, K.Up, K.SmallG),
T(@"\frac{ef_{gh}}{\square }", K.Fraction, K.SmallE, K.Subscript, K.SmallH, K.Left, K.Up, K.SmallF, K.Down, K.SmallG),
T(@"\frac{\square }{ef^{gh}}", K.Fraction, K.Down, K.SmallE, K.Power, K.SmallH, K.Left, K.Down, K.SmallF, K.Up, K.SmallG),
T(@"\frac{\square }{ef_{gh}}", K.Fraction, K.Down, K.SmallE, K.Subscript, K.SmallH, K.Left, K.Up, K.SmallF, K.Down, K.SmallG),
T(@"\frac{a^b}{c}", K.Fraction, K.SmallA, K.Power, K.SmallB, K.Left, K.Down, K.Down, K.SmallC),
T(@"\frac{a_{tb}}{c}", K.Fraction, K.SmallA, K.Subscript, K.SmallB, K.Left, K.Up, K.Down, K.SmallT, K.Down, K.SmallC),
T(@"\frac{c}{a^{tb}}", K.Fraction, K.Down, K.SmallA, K.Power, K.SmallB, K.Left, K.Down, K.Up, K.SmallT, K.Up, K.SmallC),
T(@"\frac{c}{a_b}", K.Fraction, K.Down, K.SmallA, K.Subscript, K.SmallB, K.Left, K.Up, K.Up, K.SmallC)]
public void FourDirectionalNavigation(string latex, params K[] inputs) => Test(latex, new TestFont(10), inputs);

[Fact]
public void NavigationUsesNonzeroWidthGeometryAndStaysInBranch() =>
Test(@"\frac{a^b}{c_d}", new TestFont(10), K.Fraction, K.SmallA, K.Power, K.SmallB,
K.Left, K.Down, K.Down, K.SmallC, K.Subscript, K.SmallD);

[Fact]
public void NavigationSupportsZeroWidthFont() =>
Test(@"\frac{\square }{1}", K.Fraction, K.Down, K.D1);

[Fact]
public void AssigningInsertionIndexClearsVerticalNavigationState() {
var keyboard = new MathKeyboard<TestFont, TGlyph>(context, new TestFont(10));
keyboard.KeyPress(K.Power, K.Down);
keyboard.InsertionIndex = new(0);
keyboard.KeyPress(K.D1, K.Left);
Assert.Equal("1^■", keyboard.LaTeX);
}

}
}
}
Loading
Loading