Skip to content

Commit

Permalink
feat: models with props and indexers
Browse files Browse the repository at this point in the history
  • Loading branch information
TomerAberbach committed Dec 8, 2024
1 parent 2b30e7e commit b94817b
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 5 deletions.
6 changes: 6 additions & 0 deletions src/arbitrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type Arbitrary =
| DictionaryArbitrary
| UnionArbitrary
| RecordArbitrary
| MergedArbitrary

export type BaseArbitrary = {
name: string
Expand Down Expand Up @@ -90,3 +91,8 @@ export type RecordArbitrary = BaseArbitrary & {
type: `record`
properties: Map<string, Arbitrary>
}

export type MergedArbitrary = BaseArbitrary & {
type: `merged`
arbitraries: Arbitrary[]
}
22 changes: 22 additions & 0 deletions src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
BytesArbitrary,
DictionaryArbitrary,
EnumArbitrary,
MergedArbitrary,
NumberArbitrary,
RecordArbitrary,
StringArbitrary,
Expand Down Expand Up @@ -166,6 +167,8 @@ const ArbitraryDefinition = ({
return UnionArbitrary({ arbitrary, sharedArbitraries })
case `record`:
return RecordArbitrary({ arbitrary, sharedArbitraries })
case `merged`:
return MergedArbitrary({ arbitrary, sharedArbitraries })
}
}

Expand Down Expand Up @@ -330,6 +333,25 @@ const RecordArbitrary = ({
emitEmpty: true,
})})`

const MergedArbitrary = ({
arbitrary,
sharedArbitraries,
}: {
arbitrary: MergedArbitrary
sharedArbitraries: ReadonlySet<Arbitrary>
}): Child => code`
fc
.tuple(
${ayJoin(
arbitrary.arbitraries.map(
arbitrary => code`${Arbitrary({ arbitrary, sharedArbitraries })},`,
),
{ joiner: `\n` },
)}
)
.map(values => Object.assign(...values))
`

const Options = ({
properties,
emitEmpty = false,
Expand Down
32 changes: 27 additions & 5 deletions src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,27 @@ const convertModel = (
model: Model,
options: ConvertTypeOptions,
): Arbitrary => {
if (!model.indexer) {
return convertRecord(program, model, options)
const indexerArbitrary = model.indexer
? (model.indexer.key.name === `integer` ? convertArray : convertDictionary)(
program,
model as Model & { indexer: ModelIndexer },
options,
)
: null
if (indexerArbitrary && model.properties.size === 0) {
return indexerArbitrary
}

const recordArbitrary = convertRecord(program, model, options)
if (!indexerArbitrary) {
return recordArbitrary
}

return (
model.indexer.key.name === `integer` ? convertArray : convertDictionary
)(program, model as Model & { indexer: ModelIndexer }, options)
return memoize({
type: `merged`,
name: pascalcase(model.name || options.propertyName || `Model`),
arbitraries: [recordArbitrary, indexerArbitrary],
})
}

const convertArray = (
Expand Down Expand Up @@ -418,6 +432,12 @@ const getArbitraryKey = (arbitrary: Arbitrary): ArbitraryKey => {
arbitrary.name,
...flatten(arbitrary.properties),
])
case `merged`:
return keyalesce([
arbitrary.type,
arbitrary.name,
...arbitrary.arbitraries,
])
}
}

Expand Down Expand Up @@ -506,6 +526,8 @@ const getDirectArbitraryDependencies = (
return new Set(arbitrary.variants)
case `record`:
return new Set(values(arbitrary.properties))
case `merged`:
return new Set(arbitrary.arbitraries)
}
}

Expand Down
19 changes: 19 additions & 0 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,25 @@ test.each([
model Dictionary is Record<int32>;
`,
},
{
name: `model`,
code: `
model $Model {
a: int32,
b: string
}
`,
},
{
name: `model-and-dictionary`,
code: `
model $Model {
a: int32,
b: string,
...Record<boolean>
}
`,
},
] satisfies TestCase[])(`$name`, async ({ name, code }) => {
const snapshotPath = `./snapshots/${name}`
const arbitrariesPath = `${snapshotPath}/arbitraries.js`
Expand Down
13 changes: 13 additions & 0 deletions test/snapshots/model-and-dictionary/arbitraries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as fc from 'fast-check';

const string = fc.string();

export const $Model = fc
.tuple(
fc.record({
a: fc.integer(),
b: string,
}),
fc.dictionary(string, fc.boolean()),
)
.map(values => Object.assign(...values));
40 changes: 40 additions & 0 deletions test/snapshots/model-and-dictionary/samples.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export const samples = {
'$Model': [
{
'a': 9,
'b': 'LZy\\;m',
'0#E': false,
'<b+u.Lx@D(': false,
'': true,
'qO(ha': false
},
{
'a': -994490854,
'b': '__proto__',
' =RiGs.>Ju': false
},
{
'a': -1536816376,
'b': 'S',
'RFYz': true
},
{
'a': -25,
'b': '0&k& #FFD',
'8MxC': false
},
{
'a': true,
'b': 'J',
'propertyIs': false,
'j1]>zw': true,
'>x:)dw{': false,
'"': false,
'X%@syNc': true,
'"En%:r__': true,
'V%|': true,
'O': true,
'ref': false
}
]
};
6 changes: 6 additions & 0 deletions test/snapshots/model/arbitraries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as fc from 'fast-check';

export const $Model = fc.record({
a: fc.integer(),
b: fc.string(),
});
24 changes: 24 additions & 0 deletions test/snapshots/model/samples.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const samples = {
'$Model': [
{
'a': 9,
'b': 'LZy\\;m'
},
{
'a': -994490854,
'b': '__proto__'
},
{
'a': -1536816376,
'b': 'S'
},
{
'a': -25,
'b': '0&k& #FFD'
},
{
'a': -2147483626,
'b': 'J'
}
]
};

0 comments on commit b94817b

Please sign in to comment.