1- import { Merged , assertMergedWithoutOverlap , mergeParams } from '../internal/params_utils.js' ;
1+ import { Merged , mergeParams , mergeParamsChecked } from '../internal/params_utils.js' ;
2+ import { comparePublicParamsPaths , Ordering } from '../internal/query/compare.js' ;
23import { stringifyPublicParams } from '../internal/query/stringify_params.js' ;
34import { assert , mapLazy } from '../util/util.js' ;
45
6+ import { TestParams } from './fixture.js' ;
7+
58// ================================================================
69// "Public" ParamsBuilder API / Documentation
710// ================================================================
@@ -102,27 +105,32 @@ export type CaseSubcaseIterable<CaseP, SubcaseP> = Iterable<
102105 * Base class for `CaseParamsBuilder` and `SubcaseParamsBuilder`.
103106 */
104107export abstract class ParamsBuilderBase < CaseP extends { } , SubcaseP extends { } > {
105- protected readonly cases : ( ) => Generator < CaseP > ;
108+ protected readonly cases : ( caseFilter : TestParams | null ) => Generator < CaseP > ;
106109
107- constructor ( cases : ( ) => Generator < CaseP > ) {
110+ constructor ( cases : ( caseFilter : TestParams | null ) => Generator < CaseP > ) {
108111 this . cases = cases ;
109112 }
110113
111114 /**
112115 * Hidden from test files. Use `builderIterateCasesWithSubcases` to access this.
113116 */
114- protected abstract iterateCasesWithSubcases ( ) : CaseSubcaseIterable < CaseP , SubcaseP > ;
117+ protected abstract iterateCasesWithSubcases (
118+ caseFilter : TestParams | null
119+ ) : CaseSubcaseIterable < CaseP , SubcaseP > ;
115120}
116121
117122/**
118123 * Calls the (normally hidden) `iterateCasesWithSubcases()` method.
119124 */
120- export function builderIterateCasesWithSubcases ( builder : ParamsBuilderBase < { } , { } > ) {
125+ export function builderIterateCasesWithSubcases (
126+ builder : ParamsBuilderBase < { } , { } > ,
127+ caseFilter : TestParams | null
128+ ) {
121129 interface IterableParamsBuilder {
122- iterateCasesWithSubcases ( ) : CaseSubcaseIterable < { } , { } > ;
130+ iterateCasesWithSubcases ( caseFilter : TestParams | null ) : CaseSubcaseIterable < { } , { } > ;
123131 }
124132
125- return ( ( builder as unknown ) as IterableParamsBuilder ) . iterateCasesWithSubcases ( ) ;
133+ return ( ( builder as unknown ) as IterableParamsBuilder ) . iterateCasesWithSubcases ( caseFilter ) ;
126134}
127135
128136/**
@@ -136,31 +144,66 @@ export function builderIterateCasesWithSubcases(builder: ParamsBuilderBase<{}, {
136144export class CaseParamsBuilder < CaseP extends { } >
137145 extends ParamsBuilderBase < CaseP , { } >
138146 implements Iterable < CaseP > , ParamsBuilder {
139- * iterateCasesWithSubcases ( ) : CaseSubcaseIterable < CaseP , { } > {
140- for ( const a of this . cases ( ) ) {
141- yield [ a , undefined ] ;
147+ * iterateCasesWithSubcases ( caseFilter : TestParams | null ) : CaseSubcaseIterable < CaseP , { } > {
148+ for ( const caseP of this . cases ( caseFilter ) ) {
149+ if ( caseFilter ) {
150+ // this.cases() only filters out cases which conflict with caseFilter. Now that we have
151+ // the final caseP, filter out cases which are missing keys that caseFilter requires.
152+ const ordering = comparePublicParamsPaths ( caseP , caseFilter ) ;
153+ if ( ordering === Ordering . StrictSuperset || ordering === Ordering . Unordered ) {
154+ continue ;
155+ }
156+ }
157+
158+ yield [ caseP , undefined ] ;
142159 }
143160 }
144161
145162 [ Symbol . iterator ] ( ) : Iterator < CaseP > {
146- return this . cases ( ) ;
163+ return this . cases ( null ) ;
147164 }
148165
149166 /** @inheritDoc */
150167 expandWithParams < NewP extends { } > (
151- expander : ( _ : Merged < { } , CaseP > ) => Iterable < NewP >
168+ expander : ( _ : CaseP ) => Iterable < NewP >
152169 ) : CaseParamsBuilder < Merged < CaseP , NewP > > {
153- const newGenerator = genExpandWithParams ( this . cases , expander ) ;
154- return new CaseParamsBuilder ( ( ) => newGenerator ( { } ) ) ;
170+ const baseGenerator = this . cases ;
171+ return new CaseParamsBuilder ( function * ( caseFilter ) {
172+ for ( const a of baseGenerator ( caseFilter ) ) {
173+ for ( const b of expander ( a ) ) {
174+ if ( caseFilter ) {
175+ // If the expander generated any key-value pair that conflicts with caseFilter, skip.
176+ if ( Object . entries ( b ) . some ( ( [ k , v ] ) => k in caseFilter && caseFilter [ k ] !== v ) ) {
177+ continue ;
178+ }
179+ }
180+
181+ yield mergeParamsChecked ( a , b ) ;
182+ }
183+ }
184+ } ) ;
155185 }
156186
157187 /** @inheritDoc */
158188 expand < NewPKey extends string , NewPValue > (
159189 key : NewPKey ,
160- expander : ( _ : Merged < { } , CaseP > ) => Iterable < NewPValue >
190+ expander : ( _ : CaseP ) => Iterable < NewPValue >
161191 ) : CaseParamsBuilder < Merged < CaseP , { [ name in NewPKey ] : NewPValue } > > {
162- const newGenerator = genExpand ( this . cases , key , expander ) ;
163- return new CaseParamsBuilder ( ( ) => newGenerator ( { } ) ) ;
192+ const baseGenerator = this . cases ;
193+ return new CaseParamsBuilder ( function * ( caseFilter ) {
194+ for ( const a of baseGenerator ( caseFilter ) ) {
195+ assert ( ! ( key in a ) , `New key '${ key } ' already exists in ${ JSON . stringify ( a ) } ` ) ;
196+
197+ const caseFilterV = caseFilter ?. [ key ] ;
198+ for ( const v of expander ( a ) ) {
199+ // If the expander generated a value for this key that conflicts with caseFilter, skip.
200+ if ( caseFilter && ( caseFilterV as { } ) !== v ) {
201+ continue ;
202+ }
203+ yield { ...a , [ key ] : v } as Merged < CaseP , { [ name in NewPKey ] : NewPValue } > ;
204+ }
205+ }
206+ } ) ;
164207 }
165208
166209 /** @inheritDoc */
@@ -189,13 +232,17 @@ export class CaseParamsBuilder<CaseP extends {}>
189232 }
190233
191234 /** @inheritDoc */
192- filter ( pred : ( _ : Merged < { } , CaseP > ) => boolean ) : CaseParamsBuilder < CaseP > {
193- const newGenerator = filterGenerator ( this . cases , pred ) ;
194- return new CaseParamsBuilder ( ( ) => newGenerator ( { } ) ) ;
235+ filter ( pred : ( _ : CaseP ) => boolean ) : CaseParamsBuilder < CaseP > {
236+ const baseGenerator = this . cases ;
237+ return new CaseParamsBuilder ( function * ( caseFilter ) {
238+ for ( const a of baseGenerator ( caseFilter ) ) {
239+ if ( pred ( a ) ) yield a ;
240+ }
241+ } ) ;
195242 }
196243
197244 /** @inheritDoc */
198- unless ( pred : ( _ : Merged < { } , CaseP > ) => boolean ) : CaseParamsBuilder < CaseP > {
245+ unless ( pred : ( _ : CaseP ) => boolean ) : CaseParamsBuilder < CaseP > {
199246 return this . filter ( x => ! pred ( x ) ) ;
200247 }
201248
@@ -205,12 +252,9 @@ export class CaseParamsBuilder<CaseP extends {}>
205252 * generate new subcases instead of new cases.
206253 */
207254 beginSubcases ( ) : SubcaseParamsBuilder < CaseP , { } > {
208- return new SubcaseParamsBuilder (
209- ( ) => this . cases ( ) ,
210- function * ( ) {
211- yield { } ;
212- }
213- ) ;
255+ return new SubcaseParamsBuilder ( this . cases , function * ( ) {
256+ yield { } ;
257+ } ) ;
214258 }
215259}
216260
@@ -235,13 +279,25 @@ export class SubcaseParamsBuilder<CaseP extends {}, SubcaseP extends {}>
235279 implements ParamsBuilder {
236280 protected readonly subcases : ( _ : CaseP ) => Generator < SubcaseP > ;
237281
238- constructor ( cases : ( ) => Generator < CaseP > , generator : ( _ : CaseP ) => Generator < SubcaseP > ) {
282+ constructor (
283+ cases : ( caseFilter : TestParams | null ) => Generator < CaseP > ,
284+ generator : ( _ : CaseP ) => Generator < SubcaseP >
285+ ) {
239286 super ( cases ) ;
240287 this . subcases = generator ;
241288 }
242289
243- * iterateCasesWithSubcases ( ) : CaseSubcaseIterable < CaseP , SubcaseP > {
244- for ( const caseP of this . cases ( ) ) {
290+ * iterateCasesWithSubcases ( caseFilter : TestParams | null ) : CaseSubcaseIterable < CaseP , SubcaseP > {
291+ for ( const caseP of this . cases ( caseFilter ) ) {
292+ if ( caseFilter ) {
293+ // this.cases() only filters out cases which conflict with caseFilter. Now that we have
294+ // the final caseP, filter out cases which are missing keys that caseFilter requires.
295+ const ordering = comparePublicParamsPaths ( caseP , caseFilter ) ;
296+ if ( ordering === Ordering . StrictSuperset || ordering === Ordering . Unordered ) {
297+ continue ;
298+ }
299+ }
300+
245301 const subcases = Array . from ( this . subcases ( caseP ) ) ;
246302 if ( subcases . length ) {
247303 yield [ caseP , subcases ] ;
@@ -253,15 +309,32 @@ export class SubcaseParamsBuilder<CaseP extends {}, SubcaseP extends {}>
253309 expandWithParams < NewP extends { } > (
254310 expander : ( _ : Merged < CaseP , SubcaseP > ) => Iterable < NewP >
255311 ) : SubcaseParamsBuilder < CaseP , Merged < SubcaseP , NewP > > {
256- return new SubcaseParamsBuilder ( this . cases , genExpandWithParams ( this . subcases , expander ) ) ;
312+ const baseGenerator = this . subcases ;
313+ return new SubcaseParamsBuilder ( this . cases , function * ( base ) {
314+ for ( const a of baseGenerator ( base ) ) {
315+ for ( const b of expander ( mergeParams ( base , a ) ) ) {
316+ yield mergeParamsChecked ( a , b ) ;
317+ }
318+ }
319+ } ) ;
257320 }
258321
259322 /** @inheritDoc */
260323 expand < NewPKey extends string , NewPValue > (
261324 key : NewPKey ,
262325 expander : ( _ : Merged < CaseP , SubcaseP > ) => Iterable < NewPValue >
263326 ) : SubcaseParamsBuilder < CaseP , Merged < SubcaseP , { [ name in NewPKey ] : NewPValue } > > {
264- return new SubcaseParamsBuilder ( this . cases , genExpand ( this . subcases , key , expander ) ) ;
327+ const baseGenerator = this . subcases ;
328+ return new SubcaseParamsBuilder ( this . cases , function * ( base ) {
329+ for ( const a of baseGenerator ( base ) ) {
330+ const before = mergeParams ( base , a ) ;
331+ assert ( ! ( key in before ) , ( ) => `Key '${ key } ' already exists in ${ JSON . stringify ( before ) } ` ) ;
332+
333+ for ( const v of expander ( before ) ) {
334+ yield { ...a , [ key ] : v } as Merged < SubcaseP , { [ k in NewPKey ] : NewPValue } > ;
335+ }
336+ }
337+ } ) ;
265338 }
266339
267340 /** @inheritDoc */
@@ -283,7 +356,12 @@ export class SubcaseParamsBuilder<CaseP extends {}, SubcaseP extends {}>
283356
284357 /** @inheritDoc */
285358 filter ( pred : ( _ : Merged < CaseP , SubcaseP > ) => boolean ) : SubcaseParamsBuilder < CaseP , SubcaseP > {
286- return new SubcaseParamsBuilder ( this . cases , filterGenerator ( this . subcases , pred ) ) ;
359+ const baseGenerator = this . subcases ;
360+ return new SubcaseParamsBuilder ( this . cases , function * ( base ) {
361+ for ( const a of baseGenerator ( base ) ) {
362+ if ( pred ( mergeParams ( base , a ) ) ) yield a ;
363+ }
364+ } ) ;
287365 }
288366
289367 /** @inheritDoc */
@@ -292,54 +370,6 @@ export class SubcaseParamsBuilder<CaseP extends {}, SubcaseP extends {}>
292370 }
293371}
294372
295- /** Creates a generator function for expandWithParams() methods above. */
296- function genExpandWithParams < Base , A , B > (
297- baseGenerator : ( _ : Base ) => Generator < A > ,
298- expander : ( _ : Merged < Base , A > ) => Iterable < B >
299- ) : ( _ : Base ) => Generator < Merged < A , B > > {
300- return function * ( base : Base ) {
301- for ( const a of baseGenerator ( base ) ) {
302- for ( const b of expander ( mergeParams ( base , a ) ) ) {
303- const merged = mergeParams ( a , b ) ;
304- assertMergedWithoutOverlap ( [ a , b ] , merged ) ;
305-
306- yield merged ;
307- }
308- }
309- } ;
310- }
311-
312- /** Creates a generator function for expand() methods above. */
313- function genExpand < Base , A , NewPKey extends string , NewPValue > (
314- baseGenerator : ( _ : Base ) => Generator < A > ,
315- key : NewPKey ,
316- expander : ( _ : Merged < Base , A > ) => Iterable < NewPValue >
317- ) : ( _ : Base ) => Generator < Merged < A , { [ k in NewPKey ] : NewPValue } > > {
318- return function * ( base : Base ) {
319- for ( const a of baseGenerator ( base ) ) {
320- const before = mergeParams ( base , a ) ;
321- assert ( ! ( key in before ) , ( ) => `Key '${ key } ' already exists in ${ JSON . stringify ( before ) } ` ) ;
322-
323- for ( const v of expander ( before ) ) {
324- yield { ...a , [ key ] : v } as Merged < A , { [ k in NewPKey ] : NewPValue } > ;
325- }
326- }
327- } ;
328- }
329-
330- function filterGenerator < Base , A > (
331- baseGenerator : ( _ : Base ) => Generator < A > ,
332- pred : ( _ : Merged < Base , A > ) => boolean
333- ) : ( _ : Base ) => Generator < A > {
334- return function * ( base : Base ) {
335- for ( const a of baseGenerator ( base ) ) {
336- if ( pred ( mergeParams ( base , a ) ) ) {
337- yield a ;
338- }
339- }
340- } ;
341- }
342-
343373/** Assert an object is not a Generator (a thing returned from a generator function). */
344374function assertNotGenerator ( x : object ) {
345375 if ( 'constructor' in x ) {
0 commit comments