// Copyright (c) 2015 SIL International // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) using System; using System.Collections.Generic; using System.Windows.Forms; using System.Xml; using NUnit.Framework; using SIL.LCModel.Core.KernelInterfaces; using SIL.FieldWorks.Common.ViewsInterfaces; using SIL.LCModel; using SIL.LCModel.Utils; using XCore; namespace SIL.FieldWorks.XWorks { /// <summary> /// Test the functionality of the MacroListener class. A few minor methods are not tested, because testing would /// require setting up major objects like XWindows or major architectural changes so these objects make more use of interfaces /// to support mocking. /// </summary> [TestFixture] public class MacroListenerTests : MemoryOnlyBackendProviderRestoredForEachTestTestBase { private Mediator m_mediator; private PropertyTable m_propertyTable; public override void TestTearDown() { if (m_mediator != null) { m_mediator.Dispose(); m_mediator = null; } if (m_propertyTable != null) { m_propertyTable.Dispose(); m_propertyTable = null; } base.TestTearDown(); } [Test] public void InitMacro_PutsItemAtProperPosition() { var ml = new MacroListener(); var macroImplementors = new List<IFlexMacro>(new IFlexMacro[] {new MacroF4()}); var macros = ml.AssignMacrosToSlots(macroImplementors); Assert.That(macros[0], Is.Null); Assert.That(macros[2], Is.EqualTo(macroImplementors[0])); } [Test] public void InitMacro_PutsConflictAtFirstAvailableSpot() { var ml = new MacroListener(); var macroImplementors = new List<IFlexMacro>(new IFlexMacro[] { new MacroF4(), new MacroF4(), new MacroF2() }); var macros = ml.AssignMacrosToSlots(macroImplementors); Assert.That(macros[0], Is.EqualTo(macroImplementors[2])); // the only one that wants to be at F2 Assert.That(macros[2], Is.EqualTo(macroImplementors[0])); // first one that wants to be a F4 Assert.That(macros[1], Is.EqualTo(macroImplementors[1])); // can't put where it wants to be, put it in first free slot. } /// <summary> /// Makes sure nothing too drastic happens if someone installs too many macro DLLs. /// </summary> [Test] public void InitMacro_SurvivesTooManyMacros() { var ml = new MacroListener(); var macroImplementors = new List<IFlexMacro>(); for (int i = 0; i < 20; i++) macroImplementors.Add(new MacroF4()); var macros = ml.AssignMacrosToSlots(macroImplementors); Assert.That(macros[0], Is.EqualTo(macroImplementors[1])); // first free slot gets the second one Assert.That(macros[2], Is.EqualTo(macroImplementors[0])); // first one that wants to be a F4 Assert.That(macros[1], Is.EqualTo(macroImplementors[2])); // can't put where it wants to be, put it in next free slot. Assert.That(macros[3], Is.EqualTo(macroImplementors[3])); // from here on they line up. } [Test] public void DoMacro_NormalPathSucceeds() { var ml = MakeMacroListenerWithCache(); SetupF4Implementation(ml); var entry = Cache.ServiceLocator.GetInstance<ILexEntryFactory>().Create(); m_actionHandler.EndUndoTask(); // running macro wants to make a new one. var sel = GetValidMockSelection(entry); using (var command = GetF4CommandObject()) { ml.DoMacro(command, sel); Assert.That(entry.Restrictions.AnalysisDefaultWritingSystem.Text, Is.EqualTo("test succeeded")); Assert.That(m_actionHandler.GetUndoText(), Is.EqualTo("Undo F4test")); } } private MacroListener MakeMacroListenerWithCache() { m_mediator = new Mediator(); m_propertyTable = new PropertyTable(m_mediator); m_propertyTable.SetProperty("cache", Cache, true); var ml = new MacroListener(); ml.Init(m_mediator, m_propertyTable, null); return ml; } private static MockSelection GetValidMockSelection(ILexEntry entry) { var sel = new MockSelection(); sel.TypeToReturn = VwSelType.kstText; sel.EndHvo = sel.AnchorHvo = entry.Hvo; sel.EndTag = sel.AnchorTag = LexEntryTags.kflidRestrictions; // arbitrary in this case sel.EndIch = 2; sel.AnchorIch = 5; return sel; } /// <summary> /// Initialize the listener into a state where there is a MacroF4 mock available to implement that command. /// </summary> /// <param name="ml"></param> /// <returns></returns> private static MacroF4 SetupF4Implementation(MacroListener ml) { var macroF4 = new MacroF4(); var macroImplementors = new List<IFlexMacro>(new IFlexMacro[] {macroF4}); ml.Macros = ml.AssignMacrosToSlots(macroImplementors); macroF4.BeEnabled = true; return macroF4; } /// <summary> /// Get a command object with the required XML to indicate the F4 key. /// </summary> /// <returns></returns> private static Command GetF4CommandObject() { var doc = new XmlDocument(); doc.LoadXml(@"<command><params key='4'/>"); var command = new Command(null, doc.DocumentElement); return command; } [Test] public void SafeToDoMacro_WithNoSelection_ReturnsFalse() { var ml = new MacroListener(); int ichA, hvoA, flid, ws, ichE, start, length; ICmObject obj; Assert.That(ml.SafeToDoMacro(null, out obj, out flid, out ws, out start, out length), Is.False); } [Test] public void SafeToDoMacro_WithUnsuitableSelection_ReturnsFalse() { var ml = new MacroListener(); var sel = new MockSelection(); sel.EndHvo = sel.AnchorHvo = 317; sel.EndTag = sel.AnchorTag = LexEntryTags.kflidRestrictions; // arbitrary in this case sel.EndIch = 2; sel.AnchorIch = 5; int ichA, hvoA, flid, ws, ichE, start, length; ICmObject obj; Assert.That(ml.SafeToDoMacro(sel, out obj, out flid, out ws, out start, out length), Is.False); // wrong type of selection sel.TypeToReturn = VwSelType.kstText; sel.EndHvo = 316; Assert.That(ml.SafeToDoMacro(sel, out obj, out flid, out ws, out start, out length), Is.False); // different objects sel.EndHvo = sel.AnchorHvo; sel.EndTag = 3; Assert.That(ml.SafeToDoMacro(sel, out obj, out flid, out ws, out start, out length), Is.False); // different tags } [Test] public void DoDisplayMacro_NoMacro_HidesCommand() { var props = new UIItemDisplayProperties(null, "SomeMacro", true, null, false); var ml = new MacroListener(); using (var command = GetF4CommandObject()) { ml.DoDisplayMacro(command, props, null); Assert.That(props.Visible, Is.False); // no implementation of F4, hide it altogether. } } [Test] public void DoDisplayMacro_NoSelection_ShowsDisabledCommand() { var props = new UIItemDisplayProperties(null, "SomeMacro", true, null, false); var ml = new MacroListener(); SetupF4Implementation(ml); using (var command = GetF4CommandObject()) { ml.DoDisplayMacro(command, props, null); Assert.That(props.Visible, Is.True); Assert.That(props.Enabled, Is.False); // can't do it without a selection Assert.That(props.Text, Is.EqualTo("F4test")); } } [Test] public void DoDisplayMacro_WithSafeToDo_ResultDependsOnMacro() { var props = new UIItemDisplayProperties(null, "SomeMacro", true, null, false); var ml = MakeMacroListenerWithCache(); var macro = SetupF4Implementation(ml); var entry = Cache.ServiceLocator.GetInstance<ILexEntryFactory>().Create(); var sel = GetValidMockSelection(entry); using (var command = GetF4CommandObject()) { ml.DoDisplayMacro(command, props, sel); Assert.That(props.Visible, Is.True); Assert.That(props.Enabled, Is.True); Assert.That(props.Text, Is.EqualTo("F4test")); props = new UIItemDisplayProperties(null, "SomeMacro", true, null, false); macro.BeEnabled = false; ml.DoDisplayMacro(command, props, sel); Assert.That(props.Visible, Is.True); Assert.That(props.Enabled, Is.False); // can't do it if the macro says no good. Assert.That(props.Text, Is.EqualTo("F4test")); } } } /// <summary> /// Mock macro for F2 key. In this one we only use the PreferredFunctionKey. /// </summary> class MacroF2 : IFlexMacro { public string CommandName { get { throw new NotImplementedException(); } } public bool BeEnabled; public bool Enabled(ICmObject target, int targetField, int wsId, int start, int length) { return BeEnabled; } public void RunMacro(ICmObject target, int targetField, int wsId, int startOffset, int length) { throw new NotImplementedException(); } public Keys PreferredFunctionKey { get { return Keys.F2; } } } /// <summary> /// A more complete mock for the F4 key. RunMacro sets a string property. This verifies that we are getting the unit of work /// into the required state. /// </summary> class MacroF4 : IFlexMacro { public string CommandName { get { return "F4test"; } } public bool BeEnabled; public bool Enabled(ICmObject target, int targetField, int wsId, int start, int length) { return BeEnabled; } public void RunMacro(ICmObject target, int targetField, int wsId, int startOffset, int length) { ((ILexEntry)target).Restrictions.set_String(target.Cache.DefaultAnalWs,"test succeeded"); } public Keys PreferredFunctionKey { get { return Keys.F4; } } } /// <summary> /// This class mocks the (unfortunately huge) IVwSelection. Only the first couple of methods are actually used by MacroListener, /// others are left unimplemented. Enhance JohnT: this would be better done with some sort of dynamic mock, so that changes to /// parts of the interface we don't care about won't have to be made here. But we haven't settled on a mock framework for FLEx. /// </summary> class MockSelection : IVwSelection { public VwSelType TypeToReturn; public VwSelType SelType { get { return TypeToReturn; } } public int EndHvo; public int AnchorHvo; public int EndTag; public int AnchorTag; public int EndWs; public int AnchorWs; public int EndIch; public int AnchorIch; public void TextSelInfo(bool fEndPoint, out ITsString ptss, out int ich, out bool fAssocPrev, out int hvoObj, out int tag, out int ws) { ptss = null; fAssocPrev = false; if (fEndPoint) { ich = EndIch; hvoObj = EndHvo; tag = EndTag; ws = EndWs; } else { ich = AnchorIch; hvoObj = AnchorHvo; tag = AnchorTag; ws = AnchorWs; } } public void GetSelectionProps(int cttpMax, ArrayPtr _rgpttp, ArrayPtr _rgpvps, out int _cttp) { throw new NotImplementedException(); } public void GetHardAndSoftCharProps(int cttpMax, ArrayPtr _rgpttpSel, ArrayPtr _rgpvpsSoft, out int _cttp) { throw new NotImplementedException(); } public void GetParaProps(int cttpMax, ArrayPtr _rgpvps, out int _cttp) { throw new NotImplementedException(); } public void GetHardAndSoftParaProps(int cttpMax, ITsTextProps[] _rgpttpPara, ArrayPtr _rgpttpHard, ArrayPtr _rgpvpsSoft, out int _cttp) { throw new NotImplementedException(); } public void SetSelectionProps(int cttp, ITsTextProps[] _rgpttp) { throw new NotImplementedException(); } public int CLevels(bool fEndPoint) { throw new NotImplementedException(); } public void PropInfo(bool fEndPoint, int ilev, out int _hvoObj, out int _tag, out int _ihvo, out int _cpropPrevious, out IVwPropertyStore _pvps) { throw new NotImplementedException(); } public void AllTextSelInfo(out int _ihvoRoot, int cvlsi, ArrayPtr _rgvsli, out int _tagTextProp, out int _cpropPrevious, out int _ichAnchor, out int _ichEnd, out int _ws, out bool _fAssocPrev, out int _ihvoEnd, out ITsTextProps _pttp) { throw new NotImplementedException(); } public void AllSelEndInfo(bool fEndPoint, out int _ihvoRoot, int cvlsi, ArrayPtr _rgvsli, out int _tagTextProp, out int _cpropPrevious, out int _ich, out int _ws, out bool _fAssocPrev, out ITsTextProps _pttp) { throw new NotImplementedException(); } public bool CompleteEdits(out VwChangeInfo _ci) { throw new NotImplementedException(); } public void ExtendToStringBoundaries() { throw new NotImplementedException(); } public void Location(IVwGraphics _vg, Rect rcSrc, Rect rcDst, out Rect _rdPrimary, out Rect _rdSecondary, out bool _fSplit, out bool _fEndBeforeAnchor) { throw new NotImplementedException(); } public void GetParaLocation(out Rect _rdLoc) { throw new NotImplementedException(); } public void ReplaceWithTsString(ITsString _tss) { throw new NotImplementedException(); } public void GetSelectionString(out ITsString _ptss, string bstrSep) { throw new NotImplementedException(); } public void GetFirstParaString(out ITsString _ptss, string bstrSep, out bool _fGotItAll) { throw new NotImplementedException(); } public void SetIPLocation(bool fTopLine, int xdPos) { throw new NotImplementedException(); } public void Install() { throw new NotImplementedException(); } public bool get_Follows(IVwSelection _sel) { throw new NotImplementedException(); } public int get_ParagraphOffset(bool fEndPoint) { throw new NotImplementedException(); } public IVwSelection GrowToWord() { throw new NotImplementedException(); } public IVwSelection EndPoint(bool fEndPoint) { throw new NotImplementedException(); } public void SetTypingProps(ITsTextProps _ttp) { throw new NotImplementedException(); } public int get_BoxDepth(bool fEndPoint) { throw new NotImplementedException(); } public int get_BoxIndex(bool fEndPoint, int iLevel) { throw new NotImplementedException(); } public int get_BoxCount(bool fEndPoint, int iLevel) { throw new NotImplementedException(); } public VwBoxType get_BoxType(bool fEndPoint, int iLevel) { throw new NotImplementedException(); } public bool IsRange { get { throw new NotImplementedException(); } } public bool EndBeforeAnchor { get { throw new NotImplementedException(); } } public bool CanFormatPara { get { throw new NotImplementedException(); } } public bool CanFormatChar { get { throw new NotImplementedException(); } } public bool CanFormatOverlay { get { throw new NotImplementedException(); } } public bool IsValid { get { throw new NotImplementedException(); } } public bool AssocPrev { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public IVwRootBox RootBox { get { throw new NotImplementedException(); } } public bool IsEditable { get { throw new NotImplementedException(); } } public bool IsEnabled { get { throw new NotImplementedException(); } } } }