Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using FwDataMiniLcmBridge.Api;
using FwDataMiniLcmBridge.LcmUtils;
using FwDataMiniLcmBridge.Tests.Fixtures;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using MiniLcm.Models;

namespace FwDataMiniLcmBridge.Tests;

public class CanonicalMorphTypeTests : IDisposable
{
private readonly ServiceProvider _serviceProvider;
private readonly FwDataMiniLcmApi _api;
private readonly FwDataProject _project;

public CanonicalMorphTypeTests()
{
var services = new ServiceCollection()
.AddTestFwDataBridge(mockProjectLoader: false)
.PostConfigure<FwDataBridgeConfig>(config =>
config.TemplatesFolder = Path.GetFullPath("Templates"))
.BuildServiceProvider();
_serviceProvider = services;

var config = services.GetRequiredService<IOptions<FwDataBridgeConfig>>();
Directory.CreateDirectory(config.Value.ProjectsFolder);
var projectName = $"canonical-morph-types-test_{Guid.NewGuid()}";
_project = new FwDataProject(projectName, config.Value.ProjectsFolder);
var projectLoader = services.GetRequiredService<IProjectLoader>();
projectLoader.NewProject(_project, "en", "en");

var fwDataFactory = services.GetRequiredService<FwDataFactory>();
_api = fwDataFactory.GetFwDataMiniLcmApi(_project, false);
}

public void Dispose()
{
_api.Dispose();
_serviceProvider.Dispose();
if (Directory.Exists(_project.ProjectFolder))
Directory.Delete(_project.ProjectFolder, true);
}

[Fact]
public async Task CanonicalMorphTypes_MatchNewLangProjMorphTypes()
{
var libLcmMorphTypes = await _api.GetMorphTypes().ToArrayAsync();
libLcmMorphTypes.Should().NotBeEmpty();
CanonicalMorphTypes.All.Values.Should().BeEquivalentTo(libLcmMorphTypes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,9 @@
</ItemGroup>
<ItemGroup>
<Folder Include="TestData\" />
<PackageReference Include="SIL.LCModel" GeneratePathProperty="true" />
<Content Include="$(PkgSIL_LCModel)/contentFiles/Templates/*.*"
Link="Templates/%(Filename)%(Extension)"
CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using FwDataMiniLcmBridge.Tests.Fixtures;

namespace FwDataMiniLcmBridge.Tests.MiniLcmTests;

[Collection(ProjectLoaderFixture.Name)]
public class MorphTypeTests(ProjectLoaderFixture fixture) : MorphTypeTestsBase
{
protected override Task<IMiniLcmApi> NewApi()
{
return Task.FromResult<IMiniLcmApi>(fixture.NewProjectApi("morph-type-test", "en", "en"));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using FwDataMiniLcmBridge.Api;
using FwDataMiniLcmBridge.LcmUtils;
using FwDataMiniLcmBridge.Tests.Fixtures;
using MiniLcm.Models;

namespace FwDataMiniLcmBridge.Tests.MiniLcmTests;

Expand All @@ -9,4 +12,42 @@ protected override Task<IMiniLcmApi> NewApi()
{
return Task.FromResult<IMiniLcmApi>(fixture.NewProjectApi("sorting-test", "en", "en"));
}

[Theory]
[InlineData("aaaa", SortField.Headword)] // FTS
[InlineData("a", SortField.Headword)] // non-FTS
[InlineData("aaaa", SortField.SearchRelevance)] // FTS
[InlineData("a", SortField.SearchRelevance)] // non-FTS
public async Task SecondaryOrder_DefaultsToStem(string query, SortField sortField)
{
var unknownMorphTypeEntryId = Guid.NewGuid();
Entry[] expected = [
new() { Id = unknownMorphTypeEntryId, LexemeForm = { ["en"] = "aaaa" }, MorphType = MorphTypeKind.Unknown }, // SecondaryOrder defaults to Stem = 0
new() { Id = Guid.NewGuid(), LexemeForm = { ["en"] = "aaaa" }, MorphType = MorphTypeKind.BoundStem }, // SecondaryOrder = 10
new() { Id = Guid.NewGuid(), LexemeForm = { ["en"] = "aaaa" }, MorphType = MorphTypeKind.Suffix }, // SecondaryOrder = 70
];

var ids = expected.Select(e => e.Id).ToHashSet();

foreach (var entry in Faker.Faker.Random.Shuffle(expected))
await Api.CreateEntry(entry);

var fwDataApi = (BaseApi as FwDataMiniLcmApi)!;
await fwDataApi.Cache.DoUsingNewOrCurrentUOW("Clear morph type",
"Revert morph type",
() =>
{
// the fwdata api doesn't allow creating entries with MorphType.Other or Unknown, so we force it
var unknownMorphTypeEntry = fwDataApi.EntriesRepository.GetObject(unknownMorphTypeEntryId);
unknownMorphTypeEntry.LexemeFormOA.MorphTypeRA = null;
return ValueTask.CompletedTask;
});

var results = (await Api.SearchEntries(query, new(new(sortField))).ToArrayAsync())
.Where(e => ids.Contains(e.Id))
.ToList();

results.Should().BeEquivalentTo(expected,
options => options.WithStrictOrdering());
}
}
69 changes: 33 additions & 36 deletions backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ await Cache.DoUsingNewOrCurrentUOW("Delete Complex Form Type",
});
}

public IAsyncEnumerable<MorphTypeData> GetAllMorphTypeData()
public IAsyncEnumerable<MorphType> GetMorphTypes()
{
return
MorphTypeRepository
Expand All @@ -550,35 +550,38 @@ public IAsyncEnumerable<MorphTypeData> GetAllMorphTypeData()
.Select(FromLcmMorphType);
}

public Task<MorphTypeData?> GetMorphTypeData(Guid id)
public Task<MorphType?> GetMorphType(Guid id)
{
MorphTypeRepository.TryGetObject(id, out var lcmMorphType);
if (lcmMorphType is null) return Task.FromResult<MorphTypeData?>(null);
return Task.FromResult<MorphTypeData?>(FromLcmMorphType(lcmMorphType));
if (lcmMorphType is null) return Task.FromResult<MorphType?>(null);
return Task.FromResult<MorphType?>(FromLcmMorphType(lcmMorphType));
}

internal MorphTypeData FromLcmMorphType(IMoMorphType morphType)
public Task<MorphType?> GetMorphType(MorphTypeKind kind)
{
return new MorphTypeData
var guid = LcmHelpers.ToLcmMorphTypeId(kind);
if (guid is null) return Task.FromResult<MorphType?>(null);
MorphTypeRepository.TryGetObject(guid.Value, out var lcmMorphType);
if (lcmMorphType is null) return Task.FromResult<MorphType?>(null);
return Task.FromResult<MorphType?>(FromLcmMorphType(lcmMorphType));
}

internal MorphType FromLcmMorphType(IMoMorphType morphType)
{
return new MorphType
{
Id = morphType.Guid,
MorphType = LcmHelpers.FromLcmMorphType(morphType),
Kind = LcmHelpers.FromLcmMorphType(morphType),
Name = FromLcmMultiString(morphType.Name),
Abbreviation = FromLcmMultiString(morphType.Abbreviation),
Description = FromLcmMultiString(morphType.Description),
LeadingToken = morphType.Prefix,
TrailingToken = morphType.Postfix,
Prefix = morphType.Prefix,
Postfix = morphType.Postfix,
SecondaryOrder = morphType.SecondaryOrder,
};
}

public Task<MorphTypeData> CreateMorphTypeData(MorphTypeData morphTypeData)
{
// Creating new morph types not allowed in FwData projects, so silently ignore operation
return Task.FromResult(morphTypeData);
}

public Task<MorphTypeData> UpdateMorphTypeData(Guid id, UpdateObjectInput<MorphTypeData> update)
public Task<MorphType> UpdateMorphType(Guid id, UpdateObjectInput<MorphType> update)
{
var lcmMorphType = MorphTypeRepository.GetObject(id);
if (lcmMorphType is null) throw new NullReferenceException($"unable to find morph type with id {id}");
Expand All @@ -587,22 +590,16 @@ public Task<MorphTypeData> UpdateMorphTypeData(Guid id, UpdateObjectInput<MorphT
Cache.ServiceLocator.ActionHandler,
() =>
{
var updateProxy = new UpdateMorphTypeDataProxy(lcmMorphType, this);
var updateProxy = new UpdateMorphTypeProxy(lcmMorphType, this);
update.Apply(updateProxy);
});
return Task.FromResult(FromLcmMorphType(lcmMorphType));
}

public async Task<MorphTypeData> UpdateMorphTypeData(MorphTypeData before, MorphTypeData after, IMiniLcmApi? api = null)
public async Task<MorphType> UpdateMorphType(MorphType before, MorphType after, IMiniLcmApi? api = null)
{
await MorphTypeDataSync.Sync(before, after, api ?? this);
return await GetMorphTypeData(after.Id) ?? throw new NullReferenceException("unable to find morph type with id " + after.Id);
}

public Task DeleteMorphTypeData(Guid id)
{
// Deleting morph types not allowed in FwData projects, so silently ignore operation
return Task.CompletedTask;
await MorphTypeSync.Sync(before, after, api ?? this);
return await GetMorphType(after.Id) ?? throw new NullReferenceException("unable to find morph type with id " + after.Id);
}

public IAsyncEnumerable<VariantType> GetVariantTypes()
Expand Down Expand Up @@ -643,7 +640,7 @@ private Entry FromLexEntry(ILexEntry entry)
{
try
{
return new Entry
var result = new Entry
{
Id = entry.Guid,
Note = FromLcmMultiString(entry.Comment),
Expand All @@ -661,6 +658,7 @@ private Entry FromLexEntry(ILexEntry entry)
// ILexEntry.PublishIn is a virtual property that inverts DoNotPublishInRC against all publications
PublishIn = entry.PublishIn.Select(FromLcmPossibility).ToList(),
};
return result;
}
catch (Exception e)
{
Expand Down Expand Up @@ -716,24 +714,22 @@ private ComplexFormComponent ToEntryReference(ILexEntry component, ILexEntry com
return new ComplexFormComponent
{
ComponentEntryId = component.Guid,
ComponentHeadword = component.LexEntryHeadwordOrUnknown(),
ComponentHeadword = component.LexEntryHeadwordOrUnknown(applyMorphTokens: false), // match CRDT for now
ComplexFormEntryId = complexEntry.Guid,
ComplexFormHeadword = complexEntry.LexEntryHeadwordOrUnknown(),
ComplexFormHeadword = complexEntry.LexEntryHeadwordOrUnknown(applyMorphTokens: false), // match CRDT for now
Order = Order(component, complexEntry)
};
}



private ComplexFormComponent ToSenseReference(ILexSense componentSense, ILexEntry complexEntry)
{
return new ComplexFormComponent
{
ComponentEntryId = componentSense.Entry.Guid,
ComponentSenseId = componentSense.Guid,
ComponentHeadword = componentSense.Entry.LexEntryHeadwordOrUnknown(),
ComponentHeadword = componentSense.Entry.LexEntryHeadwordOrUnknown(applyMorphTokens: false), // match CRDT for now
ComplexFormEntryId = complexEntry.Guid,
ComplexFormHeadword = complexEntry.LexEntryHeadwordOrUnknown(),
ComplexFormHeadword = complexEntry.LexEntryHeadwordOrUnknown(applyMorphTokens: false), // match CRDT for now
Order = Order(componentSense, complexEntry)
};
}
Expand Down Expand Up @@ -930,12 +926,13 @@ private IEnumerable<ILexEntry> GetFilteredAndSortedEntries(Func<ILexEntry, bool>
private IEnumerable<ILexEntry> ApplySorting(SortOptions order, IEnumerable<ILexEntry> entries, string? query)
{
var sortWs = GetWritingSystemHandle(order.WritingSystem, WritingSystemType.Vernacular);
var stemSecondaryOrder = MorphTypeRepository.GetObject(MoMorphTypeTags.kguidMorphStem).SecondaryOrder;
if (order.Field == SortField.SearchRelevance)
{
return entries.ApplyRoughBestMatchOrder(order, sortWs, query);
return entries.ApplyRoughBestMatchOrder(order, sortWs, stemSecondaryOrder, query);
}

return order.ApplyOrder(entries, e => e.LexEntryHeadword(sortWs));
return entries.ApplyHeadwordOrder(order, sortWs, stemSecondaryOrder);
}

public IAsyncEnumerable<Entry> SearchEntries(string query, QueryOptions? options = null)
Expand All @@ -947,7 +944,7 @@ public IAsyncEnumerable<Entry> SearchEntries(string query, QueryOptions? options
private Func<ILexEntry, bool>? EntrySearchPredicate(string? query = null)
{
if (string.IsNullOrEmpty(query)) return null;
return entry => entry.CitationForm.SearchValue(query) ||
return entry => entry.SearchHeadWord(query) || // CitationForm.SearchValue would be redundant
entry.LexemeFormOA?.Form.SearchValue(query) is true ||
entry.AllSenses.Any(s => s.Gloss.SearchValue(query));
}
Expand Down
Loading
Loading