1+ import { ExecutionContext } from "ava" ;
2+
13import * as json from "." ;
24
5+ /**
6+ * Constructs an object based on `schema` for unit tests.
7+ * Assumes that all keys in `schema` have string values.
8+ *
9+ * @param includeOptional Whether to include optional properties.
10+ * @param schema The schema to base the object on.
11+ * @returns An object that satisfies `schema`.
12+ */
313export function makeFromSchema < S extends json . Schema > (
414 includeOptional : boolean ,
515 schema : S ,
@@ -13,3 +23,75 @@ export function makeFromSchema<S extends json.Schema>(
1323 }
1424 return result as json . FromSchema < S > ;
1525}
26+
27+ /** Options for `withSchemaMatrix`. */
28+ export interface SchemaMatrixOptions {
29+ /** Whether cases where the properties are entirely absent should be excluded. */
30+ excludeAbsent ?: boolean ;
31+ }
32+
33+ /**
34+ * Constructs a test matrix of possible objects for `schema`: all required properties
35+ * plus all permutations of possible states for the optional properties.
36+ *
37+ * @param schema The schema to construct a test matrix for.
38+ * @param body The test body to call with each value from the test matrix.
39+ */
40+ export function withSchemaMatrix < S extends json . Schema > (
41+ t : ExecutionContext < any > ,
42+ schema : S ,
43+ opts : SchemaMatrixOptions ,
44+ body : ( value : json . FromSchema < S > ) => void ,
45+ ) : void {
46+ // Construct a base object that includes all required properties.
47+ const required = makeFromSchema ( false , schema ) ;
48+
49+ // Identify optional properties.
50+ const optionalKeys : Array < keyof S > = [ ] ;
51+
52+ for ( const [ key , validator ] of Object . entries ( schema ) ) {
53+ if ( ! validator . required ) {
54+ optionalKeys . push ( key ) ;
55+ }
56+ }
57+
58+ const optionalValues = ( key : keyof S ) => [
59+ null ,
60+ undefined ,
61+ `value-for-${ String ( key ) } ` ,
62+ ] ;
63+
64+ // Constructs an array of test objects, starting with `required` and adding
65+ // acceptable values for any
66+ const permutations = ( keys : Array < keyof S > ) => {
67+ if ( keys . length === 0 ) return [ required ] ;
68+
69+ const bases = permutations ( keys . slice ( 1 ) ) ;
70+ const result : Array < json . FromSchema < S > > = [ ] ;
71+
72+ const optionalKey = keys [ 0 ] ;
73+ for ( const base of bases ) {
74+ if ( ! opts . excludeAbsent ) {
75+ // Optional keys can be absent entirely.
76+ result . push ( base ) ;
77+ }
78+
79+ // Or be present and have one of the `optionalValues`.
80+ for ( const optionalValue of optionalValues ( optionalKey ) ) {
81+ result . push ( { ...base , [ optionalKey ] : optionalValue } ) ;
82+ }
83+ }
84+ return result ;
85+ } ;
86+
87+ // Call `body` for all test cases.
88+ const testCases = permutations ( optionalKeys ) ;
89+ for ( const testCase of testCases ) {
90+ try {
91+ body ( testCase ) ;
92+ } catch ( err ) {
93+ t . log ( testCase ) ;
94+ throw err ;
95+ }
96+ }
97+ }
0 commit comments