Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
// Copyright (c) 2026 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.Linq;
using System.Threading;
using NUnit.Framework;
using SIL.FieldWorks.Common.RootSites;
using SIL.LCModel;
using XCore;

namespace SIL.FieldWorks.XWorks.MorphologyEditor
{
/// <summary>
/// Tests the classification of the natural class under the cursor that decides whether the
/// "Set Phonological Features" rule-formula command is offered.
/// </summary>
[TestFixture]
[Apartment(ApartmentState.STA)]
public class RuleFormulaSetFeaturesTests : MemoryOnlyBackendProviderRestoredForEachTestTestBase
{
/// <summary>
/// Resolves the selection to a fixed object, the extension point concrete rule controls
/// implement, so the classification runs without a laid-out Views root box.
/// </summary>
private sealed class StubRuleFormulaControl : RuleFormulaControl
{
internal ICmObject ObjectUnderCursor { get; set; }

protected override ICmObject GetCmObject(SelectionHelper sel, SelectionHelper.SelLimitType limit)
{
return ObjectUnderCursor;
}
}

private StubRuleFormulaControl m_ruleFormulaControl;
private RuleFormulaSlice m_slice;

public override void TestSetup()
{
base.TestSetup();
m_ruleFormulaControl = new StubRuleFormulaControl();
m_slice = new RuleFormulaSlice { Control = m_ruleFormulaControl };
}

public override void TestTearDown()
{
if (m_slice != null)
{
m_slice.Dispose();
m_slice = null;
}
m_ruleFormulaControl = null;
base.TestTearDown();
}

private IPhSimpleContextNC MakeFeatureClassContext(IPhNCFeatures natClass)
{
var ctxt = Cache.ServiceLocator.GetInstance<IPhSimpleContextNCFactory>().Create();
Cache.LanguageProject.PhonologicalDataOA.ContextsOS.Add(ctxt);
ctxt.FeatureStructureRA = natClass;
return ctxt;
}

private IPhNCFeatures MakeFeatureClass(string name)
{
var natClass = Cache.ServiceLocator.GetInstance<IPhNCFeaturesFactory>().Create();
Cache.LanguageProject.PhonologicalDataOA.NaturalClassesOS.Add(natClass);
if (name != null)
natClass.Name.SetUserWritingSystem(name);
natClass.FeaturesOA = Cache.ServiceLocator.GetInstance<IFsFeatStrucFactory>().Create();
return natClass;
}

/// <summary>
/// Produces the natural class name that the rule formula controls write when the user sets
/// phonological features, so the tests stay tied to the shipped resource string.
/// </summary>
private static string AutoGeneratedName(string ruleName)
{
return string.Format(MEStrings.ksRuleNCFeatsName, ruleName);
}

private IPhSimpleContextNC MakeSegmentClassContext()
{
var natClass = Cache.ServiceLocator.GetInstance<IPhNCSegmentsFactory>().Create();
Cache.LanguageProject.PhonologicalDataOA.NaturalClassesOS.Add(natClass);
natClass.Name.SetUserWritingSystem("Vowels");

var ctxt = Cache.ServiceLocator.GetInstance<IPhSimpleContextNCFactory>().Create();
Cache.LanguageProject.PhonologicalDataOA.ContextsOS.Add(ctxt);
ctxt.FeatureStructureRA = natClass;
return ctxt;
}

private IPhSimpleContextSeg MakePhonemeContext()
{
var phonData = Cache.LanguageProject.PhonologicalDataOA;
if (phonData.PhonemeSetsOS.Count == 0)
phonData.PhonemeSetsOS.Add(Cache.ServiceLocator.GetInstance<IPhPhonemeSetFactory>().Create());
var phoneme = Cache.ServiceLocator.GetInstance<IPhPhonemeFactory>().Create();
phonData.PhonemeSetsOS[0].PhonemesOC.Add(phoneme);

var ctxt = Cache.ServiceLocator.GetInstance<IPhSimpleContextSegFactory>().Create();
phonData.ContextsOS.Add(ctxt);
ctxt.FeatureStructureRA = phoneme;
return ctxt;
}

private static UIItemDisplayProperties NewDisplayProperties()
{
return new UIItemDisplayProperties(null, "Set Phonological Features", true, null, true);
}

private UIItemDisplayProperties AskSliceToDisplaySetFeatures()
{
UIItemDisplayProperties display = NewDisplayProperties();
Assert.That(m_slice.OnDisplayContextSetFeatures(null, ref display), Is.True);
return display;
}

[Test]
public void UserNamedFeatureClassIsUserDefined()
{
m_ruleFormulaControl.ObjectUnderCursor = MakeFeatureClassContext(MakeFeatureClass("Voiced obstruents"));

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.True);
}

[Test]
public void FeatureClassGeneratedForARegularRuleIsNotUserDefined()
{
m_ruleFormulaControl.ObjectUnderCursor =
MakeFeatureClassContext(MakeFeatureClass(AutoGeneratedName("Intervocalic voicing")));

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.False);
}

[Test]
public void FeatureClassGeneratedForAnAffixRuleIsNotUserDefined()
{
// Affix rules substitute the affix form rather than a rule name, so only the fixed stub
// of the resource string is common to both kinds of generated name.
m_ruleFormulaControl.ObjectUnderCursor = MakeFeatureClassContext(MakeFeatureClass(AutoGeneratedName("-ed")));

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.False);
}

[Test]
public void UnnamedFeatureClassIsNotUserDefined()
{
m_ruleFormulaControl.ObjectUnderCursor = MakeFeatureClassContext(MakeFeatureClass(null));

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.False);
}

[Test]
public void FeatureClassWithAnEmptyNameIsNotUserDefined()
{
m_ruleFormulaControl.ObjectUnderCursor = MakeFeatureClassContext(MakeFeatureClass(string.Empty));

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.False);
}

[Test]
public void FeatureClassNamedOnlyInANonUserWritingSystemIsUserDefined()
{
// Generated names are always written in the user writing system, so a name that exists only
// elsewhere came from a person and the class must keep its features.
int otherWs = Cache.ServiceLocator.WritingSystems.AllWritingSystems
.Select(ws => ws.Handle).First(handle => handle != Cache.DefaultUserWs);
var natClass = MakeFeatureClass(null);
natClass.Name.set_String(otherWs, "Voiced obstruents");
m_ruleFormulaControl.ObjectUnderCursor = MakeFeatureClassContext(natClass);

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.True);
}

[Test]
public void FeatureClassNameMerelyResemblingTheGeneratedFormIsUserDefined()
{
m_ruleFormulaControl.ObjectUnderCursor =
MakeFeatureClassContext(MakeFeatureClass("Automatically created for the voicing rule"));

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.True);
}

[Test]
public void FeatureClassInsideAnIterationContextIsClassifiedByItsMember()
{
var ncCtxt = MakeFeatureClassContext(MakeFeatureClass("Voiced obstruents"));
var iterCtxt = Cache.ServiceLocator.GetInstance<IPhIterationContextFactory>().Create();
Cache.LanguageProject.PhonologicalDataOA.ContextsOS.Add(iterCtxt);
iterCtxt.MemberRA = ncCtxt;
m_ruleFormulaControl.ObjectUnderCursor = iterCtxt;

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.True);
}

[Test]
public void SegmentBasedNaturalClassIsNotReportedAsUserDefined()
{
m_ruleFormulaControl.ObjectUnderCursor = MakeSegmentClassContext();

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.False);
}

[Test]
public void PhonemeContextIsNotReportedAsUserDefined()
{
m_ruleFormulaControl.ObjectUnderCursor = MakePhonemeContext();

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.False);
}

[Test]
public void NaturalClassContextWithNoNaturalClassIsNotReportedAsUserDefined()
{
var ctxt = Cache.ServiceLocator.GetInstance<IPhSimpleContextNCFactory>().Create();
Cache.LanguageProject.PhonologicalDataOA.ContextsOS.Add(ctxt);
m_ruleFormulaControl.ObjectUnderCursor = ctxt;

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.False);
}

[Test]
public void EmptySelectionIsNotReportedAsUserDefined()
{
m_ruleFormulaControl.ObjectUnderCursor = null;

Assert.That(m_ruleFormulaControl.IsFeatureBasedNCNameUserDefined(), Is.False);
}

[Test]
public void SetFeaturesIsOfferedForAFeatureClassGeneratedForARule()
{
m_ruleFormulaControl.ObjectUnderCursor =
MakeFeatureClassContext(MakeFeatureClass(AutoGeneratedName("Intervocalic voicing")));

UIItemDisplayProperties display = AskSliceToDisplaySetFeatures();

Assert.That(display.Enabled, Is.True);
Assert.That(display.Visible, Is.True);
}

[Test]
public void SetFeaturesIsOfferedForAnUnnamedFeatureClass()
{
m_ruleFormulaControl.ObjectUnderCursor = MakeFeatureClassContext(MakeFeatureClass(null));

UIItemDisplayProperties display = AskSliceToDisplaySetFeatures();

Assert.That(display.Enabled, Is.True);
Assert.That(display.Visible, Is.True);
}

[Test]
public void SetFeaturesIsHiddenForAUserDefinedFeatureClass()
{
m_ruleFormulaControl.ObjectUnderCursor = MakeFeatureClassContext(MakeFeatureClass("Voiced obstruents"));

UIItemDisplayProperties display = AskSliceToDisplaySetFeatures();

Assert.That(display.Enabled, Is.False);
Assert.That(display.Visible, Is.False);
}

[Test]
public void SetFeaturesIsHiddenForASegmentBasedNaturalClass()
{
m_ruleFormulaControl.ObjectUnderCursor = MakeSegmentClassContext();

UIItemDisplayProperties display = AskSliceToDisplaySetFeatures();

Assert.That(display.Enabled, Is.False);
Assert.That(display.Visible, Is.False);
}

[Test]
public void SetFeaturesIsHiddenForAPhonemeContext()
{
m_ruleFormulaControl.ObjectUnderCursor = MakePhonemeContext();

UIItemDisplayProperties display = AskSliceToDisplaySetFeatures();

Assert.That(display.Enabled, Is.False);
Assert.That(display.Visible, Is.False);
}

[Test]
public void SetFeaturesIsHiddenForAnEmptySelection()
{
m_ruleFormulaControl.ObjectUnderCursor = null;

UIItemDisplayProperties display = AskSliceToDisplaySetFeatures();

Assert.That(display.Enabled, Is.False);
Assert.That(display.Visible, Is.False);
}

}
}
Loading