Skip to content

Commit

Permalink
Update Core helpers to support the new proposed way of writing test d…
Browse files Browse the repository at this point in the history
…ata models (#688)

* refactor(core): support new test data models pattern

* refactor(models/commons): add meta info

* fix(core): transformers execution

* refactor(core): refine code

* refactor(core): remove unsupported tests

* chore(core): add code comment

* chore: add changeset
  • Loading branch information
CarlosCortizasCT authored Oct 8, 2024
1 parent 4b65580 commit 6bdcbe6
Show file tree
Hide file tree
Showing 36 changed files with 267 additions and 210 deletions.
12 changes: 12 additions & 0 deletions .changeset/blue-sloths-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@commercetools-test-data/standalone-price': minor
'@commercetools-test-data/customer-group': minor
'@commercetools-test-data/product-type': minor
'@commercetools-test-data/channel': minor
'@commercetools-test-data/commons': minor
'@commercetools-test-data/core': minor
---

The main change is about the `core` package where we are introducing support for writing test data models using new implementation patterns which makes the process simpler. Also, the resulting code will be more maintainable.

You can head over [here]() for updated documentation about those new patterns.
163 changes: 1 addition & 162 deletions core/src/builder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { sequence, fake } from './@jackfranklin/test-data-bot';
import Builder from './builder';
import Generator from './generator';
import {
buildField,
buildFields,
buildGraphqlList,
buildRestList,
} from './helpers';
import { buildGraphqlList, buildRestList } from './helpers';
import Transformer from './transformer';

type TestUser = {
Expand All @@ -24,11 +19,6 @@ type TestOrganization = {
name: string;
email?: string;
};
type TestOrganizationTransformed = {
identifier: number;
v: string;
name: string;
};
type TestOrganizationTransformedWithEmail = {
name: string;
email: string;
Expand Down Expand Up @@ -287,71 +277,6 @@ describe('building', () => {
);
});

it('should build all build upon properties with callbacks', () => {
const built = Builder<TestOrganization>({ generator })
.id('my-id')
.name(({ version }) => ({
name: 'My name',
version: version ?? 0 + 1,
}))
.build<TestOrganization>();

expect(built).toEqual(
expect.objectContaining({
id: 'my-id',
name: 'My name',
version: 2,
})
);
});

it('should build all build upon properties with transforms', () => {
const transformers = {
graphql: Transformer<TestOrganization, TestOrganizationTransformed>(
'graphql',
{
addFields: ({ fields }) => {
const identifier = 1;
return {
identifier,
v: `${fields.version}-${identifier}`,
};
},
removeFields: ['id', 'version'],
}
),
};
const built = Builder<TestOrganization>({
generator,
transformers,
})
.name('My name')
.buildGraphql<TestOrganizationTransformed>();

// Should keep non overwritten properties
expect(built).toEqual(
expect.objectContaining({
name: 'My name',
})
);

// Should allow overwriting generated properties
expect(built).toEqual(
expect.objectContaining({
identifier: 1,
v: '3-1',
})
);

// Should remove properties
expect(built).toEqual(
expect.not.objectContaining({
id: expect.any(String),
version: expect.any(Number),
})
);
});

describe('when fields should be omitted', () => {
describe('with `omitFields`', () => {
it('should build properties and omit as requested', () => {
Expand Down Expand Up @@ -509,92 +434,6 @@ describe('building', () => {
});
});
});

describe('building in transform', () => {
describe('with property', () => {
it('should build nested builders on demand', () => {
const transformers = {
graphql: Transformer<TestUserReference, TestUserReference>(
'graphql',
{
replaceFields: ({ fields }) => ({
...fields,
user: buildField<TestExpandedUserReference>(fields.user),
}),
}
),
};
const userBuilder =
Builder<TestExpandedUserReference>().name('My name');
const built = Builder<TestUserReference>({ transformers })
.id('my-id')
.user<TestExpandedUserReference>(userBuilder)
.buildGraphql<TestUserReference>();

expect(built).toEqual({
id: 'my-id',
user: {
name: 'My name',
},
});
});
});

describe('with list', () => {
it('should build nested builders on demand', () => {
const teamTransformers = {
graphql: Transformer<TestTeam, TestTeam>('graphql', {
replaceFields: ({ fields }) => ({
...fields,
users: buildFields<TestExpandedUserReferenceGraphql>(
fields.users,
'graphql'
),
}),
}),
};
const userTransformers = {
graphql: Transformer<
TestExpandedUserReference,
TestExpandedUserReferenceGraphql
>('graphql', {
addFields: () => ({
__typename: 'User',
}),
}),
};
const userBuilder1 = Builder<TestExpandedUserReferenceGraphql>({
transformers: userTransformers,
}).name('My name');
const userBuilder2 = Builder<TestExpandedUserReferenceGraphql>({
transformers: userTransformers,
}).name('My other name');
const built = Builder<TestTeam>({
transformers: teamTransformers,
})
.id('my-id')
.users<TestExpandedUserReferenceGraphql>([
userBuilder1,
userBuilder2,
])
.buildGraphql<TestTeam>();

expect(built).toEqual({
id: 'my-id',
users: [
{
__typename: 'User',
name: 'My name',
},
{
__typename: 'User',
name: 'My other name',
},
],
});
});
});
});
});

describe('paginated list', () => {
Expand Down
111 changes: 77 additions & 34 deletions core/src/builder.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import {
isFunction,
isBuilderFunction,
isString,
omitMany,
pickMany,
} from './helpers';
import omit from 'lodash/omit';
import { isFunction, isBuilderFunction, isString, pickMany } from './helpers';
import type {
TBuilderMapStateFunction,
TBuilderOptions,
TFieldBuilderArgs,
TBuilder,
TPropertyBuilder,
TPropertyFieldUpdater,
TGeneratorResult,
} from './types';

// The Proxy constructor type does not differentiate between the target and the return type.
Expand All @@ -34,8 +30,12 @@ const createState = <Model>({

return {
get: () => state,
merge: (update: Partial<Model>) => {
state = { ...state, ...update };
merge: (update: Partial<Model>, overwrite = true) => {
if (overwrite) {
state = { ...state, ...update };
} else {
state = { ...update, ...state };
}
},
set: (prop: string, value: unknown) => {
state = { ...state, [prop]: value };
Expand Down Expand Up @@ -69,6 +69,15 @@ function PropertyBuilder<Model>(initialProps?: Partial<Model>) {
}
}
},
// This allows to inject initial props into the state after it's created.
set(_target, prop, value) {
if (prop === 'initialProps') {
state.merge(value, false);
} else {
state.set(prop as string, value);
}
return true;
},
}
);
return builder;
Expand All @@ -77,13 +86,27 @@ function PropertyBuilder<Model>(initialProps?: Partial<Model>) {
function Builder<Model>({
generator,
transformers,
type,
postBuild,
name = 'Unknown Builder',
compatConfig,
}: TBuilderOptions<Model> = {}): TBuilder<Model> {
const applyGeneratorIfExists = (): Partial<Model> => {
if (!generator) return {};
return generator.generate();
const applyGenerator = (
type: 'rest' | 'graphql'
): ReturnType<TGeneratorResult<Model>['generate']> => {
if (compatConfig?.generators) {
return compatConfig.generators[type].generate();
}
if (generator) {
return generator.generate();
}
return {} as Model;
};

const propertyBuilder = PropertyBuilder<Model>(applyGeneratorIfExists());
// We build the properties builder here becuase it handles the builder state and
// it needs to be bound to the instance.
// We do not run the generator here though as it can depend on the build call (rest or graphql).
const propertyBuilder = PropertyBuilder<Model>();

const builder: {
proxy: TBuilder<Model>;
Expand Down Expand Up @@ -112,35 +135,55 @@ function Builder<Model>({
omitFields = [],
keepFields = [],
}: TFieldBuilderArgs<Model> = {}) => {
const builderType =
type === 'graphql' || propToSet === 'buildGraphql'
? 'graphql'
: 'rest';

// Now that we know which type of builder we are dealing with, we can
// run the appropriate generator
// This is required for compatibility between the new and legacy models.
const generatedFields = applyGenerator(builderType);
// @ts-expect-error `initialProps` is a dymamic property created in the PropertyBuilder proxy
propertyBuilder.initialProps = generatedFields;

const built = propertyBuilder.get() as Model;
let transformed = built;

switch (propToSet) {
case 'build': {
transformed = (transformers?.default?.transform(built) ??
built) as Model;
break;
}
case 'buildGraphql': {
transformed = (transformers?.graphql?.transform(built) ??
built) as Model;
break;
}
case 'buildRest': {
transformed = (transformers?.rest?.transform(built) ??
built) as Model;
break;
}
default:
break;
}
// Run transformers (they build the nested models)
// By now we need to keep the three types in order to be backwards compatible
// but the "default" one should be removed in the future. When that happens
// we can get rid of this property and just use the `builderType` one.
const transformersType =
type === 'graphql' || propToSet === 'buildGraphql'
? 'graphql'
: type === 'rest' || propToSet === 'buildRest'
? 'rest'
: 'default';
transformed = (transformers?.[transformersType]?.transform({
fields: built,
builderName: name,
}) ?? built) as Model;

if (keepFields.length > 0) {
return pickMany<Model>(transformed, ...keepFields);
transformed = pickMany<Model>(transformed, ...keepFields);
}
if (omitFields.length > 0) {
return omitMany<Model>(transformed, ...omitFields);
transformed = omit(transformed as {}, omitFields) as Model;
}

// This is required for compatibility between the new and legacy models.
const postBuilder = compatConfig?.postBuilders
? compatConfig.postBuilders[builderType]
: postBuild;
// Run the callback to allow for additional transformations
if (postBuilder) {
transformed = {
...transformed,
...postBuilder(transformed),
};
}

return transformed;
};
}
Expand Down
Loading

0 comments on commit 6bdcbe6

Please sign in to comment.