88
99namespace FwLiteProjectSync . Tests ;
1010
11- public class EntrySyncTests : IClassFixture < SyncFixture > , IAsyncLifetime
11+ public class CrdtEntrySyncTests ( SyncFixture fixture ) : EntrySyncTestsBase ( fixture )
1212{
1313 private static readonly AutoFaker AutoFaker = new ( new AutoFakerConfig ( )
1414 {
@@ -22,37 +22,25 @@ public class EntrySyncTests : IClassFixture<SyncFixture>, IAsyncLifetime
2222 ]
2323 } ) ;
2424
25- public EntrySyncTests ( SyncFixture fixture )
25+ protected override IMiniLcmApi GetApi ( SyncFixture fixture )
2626 {
27- _fixture = fixture ;
27+ return fixture . CrdtApi ;
2828 }
2929
30- public async Task InitializeAsync ( )
31- {
32- await _fixture . EnsureDefaultVernacularWritingSystemExistsInCrdt ( ) ;
33- }
34-
35- public Task DisposeAsync ( )
36- {
37- return Task . CompletedTask ;
38- }
39-
40- private readonly SyncFixture _fixture ;
41-
4230 [ Fact ]
4331 public async Task CanSyncRandomEntries ( )
4432 {
45- var createdEntry = await _fixture . CrdtApi . CreateEntry ( await AutoFaker . EntryReadyForCreation ( _fixture . CrdtApi ) ) ;
46- var after = await AutoFaker . EntryReadyForCreation ( _fixture . CrdtApi , entryId : createdEntry . Id ) ;
33+ var createdEntry = await Api . CreateEntry ( await AutoFaker . EntryReadyForCreation ( Api ) ) ;
34+ var after = await AutoFaker . EntryReadyForCreation ( Api , entryId : createdEntry . Id ) ;
4735
4836 after . Senses = [ .. AutoFaker . Faker . Random . Shuffle ( [
4937 // copy some senses over, so moves happen
5038 ..AutoFaker . Faker . Random . ListItems ( createdEntry . Senses ) ,
5139 ..after . Senses
5240 ] ) ] ;
5341
54- await EntrySync . Sync ( createdEntry , after , _fixture . CrdtApi ) ;
55- var actual = await _fixture . CrdtApi . GetEntry ( after . Id ) ;
42+ await EntrySync . Sync ( createdEntry , after , Api ) ;
43+ var actual = await Api . GetEntry ( after . Id ) ;
5644 actual . Should ( ) . NotBeNull ( ) ;
5745 actual . Should ( ) . BeEquivalentTo ( after , options => options
5846 . For ( e => e . Senses ) . Exclude ( s => s . Order )
@@ -61,14 +49,41 @@ public async Task CanSyncRandomEntries()
6149 . For ( e => e . Senses ) . For ( s => s . ExampleSentences ) . Exclude ( e => e . Order )
6250 ) ;
6351 }
52+ }
53+
54+ public class FwDataEntrySyncTests ( SyncFixture fixture ) : EntrySyncTestsBase ( fixture )
55+ {
56+ protected override IMiniLcmApi GetApi ( SyncFixture fixture )
57+ {
58+ return fixture . FwDataApi ;
59+ }
60+ }
61+
62+ public abstract class EntrySyncTestsBase ( SyncFixture fixture ) : IClassFixture < SyncFixture > , IAsyncLifetime
63+ {
64+ public async Task InitializeAsync ( )
65+ {
66+ await _fixture . EnsureDefaultVernacularWritingSystemExistsInCrdt ( ) ;
67+ Api = GetApi ( _fixture ) ;
68+ }
69+
70+ public Task DisposeAsync ( )
71+ {
72+ return Task . CompletedTask ;
73+ }
74+
75+ protected abstract IMiniLcmApi GetApi ( SyncFixture fixture ) ;
76+
77+ private readonly SyncFixture _fixture = fixture ;
78+ protected IMiniLcmApi Api = null ! ;
6479
6580 [ Fact ]
6681 public async Task CanChangeComplexFormViaSync_Components ( )
6782 {
68- var component1 = await _fixture . CrdtApi . CreateEntry ( new ( ) { LexemeForm = { { "en" , "component1" } } } ) ;
69- var component2 = await _fixture . CrdtApi . CreateEntry ( new ( ) { LexemeForm = { { "en" , "component2" } } } ) ;
83+ var component1 = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "component1" } } } ) ;
84+ var component2 = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "component2" } } } ) ;
7085 var complexFormId = Guid . NewGuid ( ) ;
71- var complexForm = await _fixture . CrdtApi . CreateEntry ( new ( )
86+ var complexForm = await Api . CreateEntry ( new ( )
7287 {
7388 Id = complexFormId ,
7489 LexemeForm = { { "en" , "complex form" } } ,
@@ -83,24 +98,25 @@ public async Task CanChangeComplexFormViaSync_Components()
8398 }
8499 ]
85100 } ) ;
86- Entry after = ( Entry ) complexForm . Copy ( ) ;
87- after . Components [ 0 ] . ComponentEntryId = component2 . Id ;
88- after . Components [ 0 ] . ComponentHeadword = component2 . Headword ( ) ;
101+ var complexFormAfter = complexForm . Copy ( ) ;
102+ complexFormAfter . Components [ 0 ] . ComponentEntryId = component2 . Id ;
103+ complexFormAfter . Components [ 0 ] . ComponentHeadword = component2 . Headword ( ) ;
89104
90- await EntrySync . Sync ( complexForm , after , _fixture . CrdtApi ) ;
105+ await EntrySync . Sync ( complexForm , complexFormAfter , Api ) ;
91106
92- var actual = await _fixture . CrdtApi . GetEntry ( after . Id ) ;
107+ var actual = await Api . GetEntry ( complexFormAfter . Id ) ;
93108 actual . Should ( ) . NotBeNull ( ) ;
94- actual . Should ( ) . BeEquivalentTo ( after , options => options ) ;
109+ actual . Should ( ) . BeEquivalentTo ( complexFormAfter , options => options
110+ . For ( e => e . Components ) . Exclude ( c => c . Id ) ) ;
95111 }
96112
97113 [ Fact ]
98114 public async Task CanChangeComplexFormViaSync_ComplexForms ( )
99115 {
100- var complexForm1 = await _fixture . CrdtApi . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complexForm1" } } } ) ;
101- var complexForm2 = await _fixture . CrdtApi . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complexForm2" } } } ) ;
116+ var complexForm1 = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complexForm1" } } } ) ;
117+ var complexForm2 = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complexForm2" } } } ) ;
102118 var componentId = Guid . NewGuid ( ) ;
103- var component = await _fixture . CrdtApi . CreateEntry ( new ( )
119+ var component = await Api . CreateEntry ( new ( )
104120 {
105121 Id = componentId ,
106122 LexemeForm = { { "en" , "component" } } ,
@@ -115,47 +131,44 @@ public async Task CanChangeComplexFormViaSync_ComplexForms()
115131 }
116132 ]
117133 } ) ;
118- Entry after = ( Entry ) component . Copy ( ) ;
119- after . ComplexForms [ 0 ] . ComplexFormEntryId = complexForm2 . Id ;
120- after . ComplexForms [ 0 ] . ComplexFormHeadword = complexForm2 . Headword ( ) ;
134+ var componentAter = component . Copy ( ) ;
135+ componentAter . ComplexForms [ 0 ] . ComplexFormEntryId = complexForm2 . Id ;
136+ componentAter . ComplexForms [ 0 ] . ComplexFormHeadword = complexForm2 . Headword ( ) ;
121137
122- await EntrySync . Sync ( component , after , _fixture . CrdtApi ) ;
138+ await EntrySync . Sync ( component , componentAter , Api ) ;
123139
124- var actual = await _fixture . CrdtApi . GetEntry ( after . Id ) ;
140+ var actual = await Api . GetEntry ( componentAter . Id ) ;
125141 actual . Should ( ) . NotBeNull ( ) ;
126- actual . Should ( ) . BeEquivalentTo ( after , options => options ) ;
142+ actual . Should ( ) . BeEquivalentTo ( componentAter , options => options
143+ . For ( e => e . ComplexForms ) . Exclude ( c => c . Id ) ) ;
127144 }
128145
129146 [ Fact ]
130147 public async Task CanChangeComplexFormTypeViaSync ( )
131148 {
132- var complexFormType = await _fixture . CrdtApi . CreateComplexFormType ( new ( ) { Name = new ( ) { { "en" , "complexFormType" } } } ) ;
133- var entry = await _fixture . CrdtApi . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complexForm1" } } } ) ;
134- var after = ( Entry ) entry . Copy ( ) ;
149+ var complexFormType = await Api . CreateComplexFormType ( new ( ) { Name = new ( ) { { "en" , "complexFormType" } } } ) ;
150+ var entry = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complexForm1" } } } ) ;
151+ var after = entry . Copy ( ) ;
135152 after . ComplexFormTypes = [ complexFormType ] ;
136- await EntrySync . Sync ( entry , after , _fixture . CrdtApi ) ;
153+ await EntrySync . Sync ( entry , after , Api ) ;
137154
138- var actual = await _fixture . CrdtApi . GetEntry ( after . Id ) ;
155+ var actual = await Api . GetEntry ( after . Id ) ;
139156 actual . Should ( ) . NotBeNull ( ) ;
140157 actual . Should ( ) . BeEquivalentTo ( after , options => options ) ;
141158 }
142159
143160 [ Theory ]
144- [ InlineData ( true , ProjectDataFormat . Harmony ) ]
145- [ InlineData ( true , ProjectDataFormat . FwData ) ]
146- [ InlineData ( false , ProjectDataFormat . Harmony ) ]
147- [ InlineData ( false , ProjectDataFormat . FwData ) ]
148- public async Task CanInsertComplexFormComponentViaSync ( bool componentThenComplexForm , ProjectDataFormat apiType )
161+ [ InlineData ( true ) ]
162+ [ InlineData ( false ) ]
163+ public async Task CanInsertComplexFormComponentViaSync ( bool componentThenComplexForm )
149164 {
150165 // arrange
151- IMiniLcmApi api = apiType == ProjectDataFormat . Harmony ? _fixture . CrdtApi : _fixture . FwDataApi ;
152-
153- var existingComponent = await api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "existing-component" } } } ) ;
154- var complexFormBefore = await api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complex-form" } } } ) ;
155- await api . CreateComplexFormComponent ( ComplexFormComponent . FromEntries ( complexFormBefore , existingComponent ) ) ;
156- complexFormBefore = ( await api . GetEntry ( complexFormBefore . Id ) ) ! ;
166+ var existingComponent = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "existing-component" } } } ) ;
167+ var complexFormBefore = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "complex-form" } } } ) ;
168+ await Api . CreateComplexFormComponent ( ComplexFormComponent . FromEntries ( complexFormBefore , existingComponent ) ) ;
169+ complexFormBefore = ( await Api . GetEntry ( complexFormBefore . Id ) ) ! ;
157170
158- var newComponentBefore = await api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "component" } } } ) ;
171+ var newComponentBefore = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "component" } } } ) ;
159172 var newComplexFormComponent = ComplexFormComponent . FromEntries ( complexFormBefore , newComponentBefore ) ;
160173
161174 var newComponentAfter = newComponentBefore . Copy ( ) ;
@@ -169,18 +182,18 @@ public async Task CanInsertComplexFormComponentViaSync(bool componentThenComplex
169182 // this results in 2 crdt changes:
170183 // (1) add complex-form (i.e. implicitly add component)
171184 // (2) move component to the right place
172- await EntrySync . Sync ( [ newComponentBefore , complexFormBefore ] , [ newComponentAfter , complexFormAfter ] , api ) ;
185+ await EntrySync . Sync ( [ newComponentBefore , complexFormBefore ] , [ newComponentAfter , complexFormAfter ] , Api ) ;
173186 }
174187 else
175188 {
176189 // this results in 1 crdt change:
177190 // the component is added in the right place
178191 // (adding the complex-form becomes a no-op, because it already exists and a BetweenPosition is not specified)
179- await EntrySync . Sync ( [ complexFormBefore , newComponentBefore ] , [ complexFormAfter , newComponentAfter ] , api ) ;
192+ await EntrySync . Sync ( [ complexFormBefore , newComponentBefore ] , [ complexFormAfter , newComponentAfter ] , Api ) ;
180193 }
181194
182195 // assert
183- var actual = await api . GetEntry ( complexFormAfter . Id ) ;
196+ var actual = await Api . GetEntry ( complexFormAfter . Id ) ;
184197 actual . Should ( ) . NotBeNull ( ) ;
185198 actual . Should ( ) . BeEquivalentTo ( complexFormAfter , options => options
186199 . WithStrictOrdering ( )
@@ -191,4 +204,34 @@ public async Task CanInsertComplexFormComponentViaSync(bool componentThenComplex
191204 . Excluding ( c => c . Order )
192205 . Excluding ( c => c . Id ) ) ;
193206 }
207+
208+ [ Fact ]
209+ public async Task CanSyncNewEntryReferencedByExistingEntry ( )
210+ {
211+ // arrange
212+ // - before
213+ var existingEntryBefore = await Api . CreateEntry ( new ( ) { LexemeForm = { { "en" , "existing-component" } } } ) ;
214+
215+ // - after
216+ var existingEntryAfter = existingEntryBefore . Copy ( ) ;
217+ var newEntry = new Entry ( ) { Id = Guid . NewGuid ( ) , LexemeForm = { { "en" , "complex-form" } } } ;
218+ var newComplexFormComponent = ComplexFormComponent . FromEntries ( newEntry , existingEntryAfter ) ;
219+ existingEntryAfter . ComplexForms . Add ( newComplexFormComponent ) ;
220+ newEntry . Components . Add ( newComplexFormComponent ) ;
221+
222+ // act
223+ await EntrySync . Sync ( [ existingEntryBefore ] , [ existingEntryAfter , newEntry ] , Api ) ;
224+
225+ // assert
226+ var actualExistingEntry = await Api . GetEntry ( existingEntryAfter . Id ) ;
227+ actualExistingEntry . Should ( ) . BeEquivalentTo ( existingEntryAfter , options => options
228+ . For ( e => e . ComplexForms ) . Exclude ( c => c . Id )
229+ . For ( e => e . ComplexForms ) . Exclude ( c => c . Order ) ) ;
230+
231+ var actualNewEntry = await Api . GetEntry ( newEntry . Id ) ;
232+ actualNewEntry . Should ( ) . BeEquivalentTo ( newEntry , options => options
233+ . Excluding ( e => e . ComplexFormTypes ) // LibLcm automatically creates a complex form type. Should we?
234+ . For ( e => e . Components ) . Exclude ( c => c . Id )
235+ . For ( e => e . Components ) . Exclude ( c => c . Order ) ) ;
236+ }
194237}
0 commit comments