1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
4+ using System . Runtime . CompilerServices ;
5+ using System . Threading ;
6+ using System . Threading . Tasks ;
7+ using EnumerableAsyncProcessor . Extensions ;
8+ using TUnit . Assertions ;
9+ using TUnit . Core ;
10+
11+ namespace EnumerableAsyncProcessor . UnitTests ;
12+
13+ public class SelectManyExtensionsTests
14+ {
15+ private static async IAsyncEnumerable < int > GenerateAsyncEnumerable ( int count , [ EnumeratorCancellation ] CancellationToken cancellationToken = default )
16+ {
17+ for ( int i = 1 ; i <= count ; i ++ )
18+ {
19+ await Task . Yield ( ) ;
20+ cancellationToken . ThrowIfCancellationRequested ( ) ;
21+ yield return i ;
22+ }
23+ }
24+
25+ private static async IAsyncEnumerable < int > GenerateAsyncRange ( int start , int count , [ EnumeratorCancellation ] CancellationToken cancellationToken = default )
26+ {
27+ for ( int i = start ; i < start + count ; i ++ )
28+ {
29+ await Task . Yield ( ) ;
30+ cancellationToken . ThrowIfCancellationRequested ( ) ;
31+ yield return i ;
32+ }
33+ }
34+
35+ [ Test ]
36+ public async Task SelectMany_IAsyncEnumerable_WithIEnumerable_FlattensResults ( )
37+ {
38+ var asyncEnumerable = GenerateAsyncEnumerable ( 3 ) ;
39+
40+ var results = new List < int > ( ) ;
41+ await foreach ( var item in asyncEnumerable . SelectMany ( x => Enumerable . Range ( x * 10 , 3 ) ) )
42+ {
43+ results . Add ( item ) ;
44+ }
45+
46+ // Input: [1, 2, 3]
47+ // Output: [10,11,12, 20,21,22, 30,31,32]
48+ await Assert . That ( results ) . IsEquivalentTo ( new [ ] { 10 , 11 , 12 , 20 , 21 , 22 , 30 , 31 , 32 } ) ;
49+ }
50+
51+ [ Test ]
52+ public async Task SelectMany_IAsyncEnumerable_WithIAsyncEnumerable_FlattensResults ( )
53+ {
54+ var asyncEnumerable = GenerateAsyncEnumerable ( 3 ) ;
55+
56+ var results = new List < int > ( ) ;
57+ await foreach ( var item in asyncEnumerable . SelectMany ( x => GenerateAsyncRange ( x * 10 , 2 ) ) )
58+ {
59+ results . Add ( item ) ;
60+ }
61+
62+ // Input: [1, 2, 3]
63+ // Output: [10,11, 20,21, 30,31]
64+ await Assert . That ( results ) . IsEquivalentTo ( new [ ] { 10 , 11 , 20 , 21 , 30 , 31 } ) ;
65+ }
66+
67+ [ Test ]
68+ public async Task SelectManyAsync_IAsyncEnumerable_WithTaskIEnumerable_FlattensResults ( )
69+ {
70+ var asyncEnumerable = GenerateAsyncEnumerable ( 3 ) ;
71+
72+ var results = new List < string > ( ) ;
73+ await foreach ( var item in asyncEnumerable . SelectManyAsync ( async x =>
74+ {
75+ await Task . Delay ( 10 ) ;
76+ return Enumerable . Range ( x * 10 , 2 ) . Select ( n => n . ToString ( ) ) ;
77+ } ) )
78+ {
79+ results . Add ( item ) ;
80+ }
81+
82+ // Input: [1, 2, 3]
83+ // Output: ["10","11", "20","21", "30","31"]
84+ await Assert . That ( results ) . IsEquivalentTo ( new [ ] { "10" , "11" , "20" , "21" , "30" , "31" } ) ;
85+ }
86+
87+ [ Test ]
88+ public async Task SelectManyAsync_IAsyncEnumerable_WithTaskIAsyncEnumerable_FlattensResults ( )
89+ {
90+ var asyncEnumerable = GenerateAsyncEnumerable ( 2 ) ;
91+
92+ var results = new List < int > ( ) ;
93+ await foreach ( var item in asyncEnumerable . SelectManyAsync ( async x =>
94+ {
95+ await Task . Delay ( 10 ) ;
96+ return GenerateAsyncRange ( x * 100 , 3 ) ;
97+ } ) )
98+ {
99+ results . Add ( item ) ;
100+ }
101+
102+ // Input: [1, 2]
103+ // Output: [100,101,102, 200,201,202]
104+ await Assert . That ( results ) . IsEquivalentTo ( new [ ] { 100 , 101 , 102 , 200 , 201 , 202 } ) ;
105+ }
106+
107+ [ Test ]
108+ public async Task SelectManyAsync_IEnumerable_WithIAsyncEnumerable_FlattensResults ( )
109+ {
110+ var enumerable = Enumerable . Range ( 1 , 3 ) ;
111+
112+ var results = new List < int > ( ) ;
113+ await foreach ( var item in enumerable . SelectManyAsync ( x => GenerateAsyncRange ( x * 10 , 2 ) ) )
114+ {
115+ results . Add ( item ) ;
116+ }
117+
118+ // Input: [1, 2, 3]
119+ // Output: [10,11, 20,21, 30,31]
120+ await Assert . That ( results ) . IsEquivalentTo ( new [ ] { 10 , 11 , 20 , 21 , 30 , 31 } ) ;
121+ }
122+
123+ [ Test ]
124+ public async Task SelectManyAsync_IEnumerable_WithTaskIEnumerable_FlattensResults ( )
125+ {
126+ var enumerable = Enumerable . Range ( 1 , 3 ) ;
127+
128+ var results = new List < string > ( ) ;
129+ await foreach ( var item in enumerable . SelectManyAsync ( async x =>
130+ {
131+ await Task . Delay ( 10 ) ;
132+ return new [ ] { $ "A{ x } ", $ "B{ x } " } ;
133+ } ) )
134+ {
135+ results . Add ( item ) ;
136+ }
137+
138+ // Input: [1, 2, 3]
139+ // Output: ["A1","B1", "A2","B2", "A3","B3"]
140+ await Assert . That ( results ) . IsEquivalentTo ( new [ ] { "A1" , "B1" , "A2" , "B2" , "A3" , "B3" } ) ;
141+ }
142+
143+ [ Test ]
144+ public async Task SelectManyAsync_IEnumerable_WithTaskIAsyncEnumerable_FlattensResults ( )
145+ {
146+ var enumerable = Enumerable . Range ( 1 , 2 ) ;
147+
148+ var results = new List < int > ( ) ;
149+ await foreach ( var item in enumerable . SelectManyAsync ( async x =>
150+ {
151+ await Task . Delay ( 10 ) ;
152+ return GenerateAsyncRange ( x * 100 , 3 ) ;
153+ } ) )
154+ {
155+ results . Add ( item ) ;
156+ }
157+
158+ // Input: [1, 2]
159+ // Output: [100,101,102, 200,201,202]
160+ await Assert . That ( results ) . IsEquivalentTo ( new [ ] { 100 , 101 , 102 , 200 , 201 , 202 } ) ;
161+ }
162+
163+ [ Test ]
164+ public async Task SelectMany_WithEmptySubCollections_HandlesCorrectly ( )
165+ {
166+ var asyncEnumerable = GenerateAsyncEnumerable ( 3 ) ;
167+
168+ var results = new List < int > ( ) ;
169+ await foreach ( var item in asyncEnumerable . SelectMany ( x =>
170+ x == 2 ? Enumerable . Empty < int > ( ) : new [ ] { x * 10 } ) )
171+ {
172+ results . Add ( item ) ;
173+ }
174+
175+ // Input: [1, 2, 3]
176+ // Output: [10, 30] (2 produces empty)
177+ await Assert . That ( results ) . IsEquivalentTo ( new [ ] { 10 , 30 } ) ;
178+ }
179+
180+ [ Test ]
181+ public async Task SelectMany_WithCancellation_ThrowsOperationCanceledException ( )
182+ {
183+ using var cts = new CancellationTokenSource ( ) ;
184+ var asyncEnumerable = GenerateAsyncEnumerable ( 100 ) ;
185+
186+ cts . CancelAfter ( 50 ) ;
187+
188+ var results = new List < int > ( ) ;
189+ await Assert . ThrowsAsync < OperationCanceledException > ( async ( ) =>
190+ {
191+ await foreach ( var item in asyncEnumerable . SelectMany (
192+ x => Enumerable . Range ( x * 10 , 10 ) , cts . Token ) )
193+ {
194+ results . Add ( item ) ;
195+ await Task . Delay ( 10 ) ;
196+ }
197+ } ) ;
198+ }
199+
200+ [ Test ]
201+ public async Task SelectManyAsync_HandlesExceptions ( )
202+ {
203+ var asyncEnumerable = GenerateAsyncEnumerable ( 3 ) ;
204+
205+ var results = asyncEnumerable . SelectManyAsync ( async x =>
206+ {
207+ if ( x == 2 )
208+ {
209+ throw new InvalidOperationException ( "Test exception" ) ;
210+ }
211+ await Task . Delay ( 10 ) ;
212+ return new [ ] { x * 10 } ;
213+ } ) ;
214+
215+ var items = new List < int > ( ) ;
216+ await Assert . ThrowsAsync < InvalidOperationException > ( async ( ) =>
217+ {
218+ await foreach ( var item in results )
219+ {
220+ items . Add ( item ) ;
221+ }
222+ } ) ;
223+
224+ // Should have processed first item before exception
225+ await Assert . That ( items ) . IsEquivalentTo ( new [ ] { 10 } ) ;
226+ }
227+
228+ [ Test ]
229+ public async Task SelectMany_ComplexNesting_WorksCorrectly ( )
230+ {
231+ // Test a more complex scenario with nested SelectMany using LINQ's built-in
232+ var data = new [ ] { 1 , 2 } ;
233+
234+ var results = System . Linq . Enumerable . SelectMany ( data , x =>
235+ System . Linq . Enumerable . SelectMany ( Enumerable . Range ( 1 , 2 ) , y =>
236+ new [ ] { $ "{ x } -{ y } -A", $ "{ x } -{ y } -B" } ) ) . ToList ( ) ;
237+
238+ await Assert . That ( results ) . IsEquivalentTo ( new [ ]
239+ {
240+ "1-1-A" , "1-1-B" , "1-2-A" , "1-2-B" ,
241+ "2-1-A" , "2-1-B" , "2-2-A" , "2-2-B"
242+ } ) ;
243+ }
244+ }
0 commit comments