Skip to content

Commit

Permalink
mock generator
Browse files Browse the repository at this point in the history
  • Loading branch information
AppElent committed Jan 23, 2025
1 parent 1a45efa commit ec5000f
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/config/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import DataSources from '@/pages/default/test/data-sources/index';
import FileUploads from '@/pages/default/test/file-uploads';
import FiltersPage from '@/pages/default/test/filters-page';
import Forms from '@/pages/default/test/forms';
import SchemaPage from '@/pages/default/test/schema-page';
import Translations from '@/pages/default/test/translations';
import { CustomRouteObject, routes as routesImport } from './routing';

Expand All @@ -27,6 +28,7 @@ const routeElements: { [key: string]: JSX.Element } = {
testForms: <Forms />,
testTranslations: <Translations />,
testFilters: <FiltersPage />,
testSchemas: <SchemaPage />,
// Default pages
login: <SignIn mode="signin" />,
signup: <SignIn mode="signup" />,
Expand Down
12 changes: 12 additions & 0 deletions src/pages/default/test/schema-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createDummySchema } from '@/schemas/dummy';
import DefaultPage from '../DefaultPage';

const SchemaPage = () => {
const schema = createDummySchema();
console.log(schema);
const mockdata = schema.generateMockData();
console.log(mockdata);
return <DefaultPage></DefaultPage>;
};

export default SchemaPage;
7 changes: 7 additions & 0 deletions src/routes/appRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ const appRoutes: CustomRouteObject[] = [
category: 'test',
path: 'filters',
},
{
id: 'testSchemas',
label: 'Schemas',
Icon: <QuizIcon />,
category: 'test',
path: 'schemas',
},
],
},
{
Expand Down
16 changes: 16 additions & 0 deletions src/schemas/dummy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ export const dummyYupSchema = Yup.object().shape({
booleanKey: Yup.boolean().required('Key is required').default(false),
})
.optional(),
email: Yup.string().email().optional(),
url: Yup.string().url().optional(),
uuid: Yup.string().uuid().optional(),
uppercase: Yup.string().uppercase().optional(),
lowercase: Yup.string().lowercase().optional(),
min: Yup.string().min(3).optional(),
max: Yup.string().max(20).optional(),
oneOf: Yup.number().oneOf([1, 2, 3]).optional(),
minNumber: Yup.number().min(0).optional(),
maxNumber: Yup.number().max(100).optional(),
oneOfNumber: Yup.number().oneOf([1, 2, 3]).optional(),
minDate: Yup.date().min(new Date()).optional(),
maxDate: Yup.date().max(new Date()).optional(),
oneOfDate: Yup.date().oneOf([new Date()]).optional(),
minArray: Yup.array().of(Yup.string().required()).min(1).optional(),
maxArray: Yup.array().of(Yup.string().required()).max(3).optional(),
});

export type Dummy = Yup.InferType<typeof dummyYupSchema>;
Expand Down
17 changes: 16 additions & 1 deletion src/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FieldConfig } from '@/libs/forms';
import { faker } from '@faker-js/faker';
import _ from 'lodash';
import * as Yup from 'yup';
import { createMockDataGenerator } from './mock-data-generator';

// export interface Schema {
// [key: string]: {
Expand All @@ -24,6 +25,8 @@ interface ValidationResult<T> {
export interface DefaultSchemaReturn<T> {
schema: Yup.ObjectSchema<any>;
getCustomFieldDefinitions: () => { [key: string]: Partial<FieldConfig> };
generateMockData: <T extends Yup.AnyObject>(yupSchema?: YupSchema<T>) => T;
getMockData: <T extends Yup.AnyObject>(count: number) => T[];
generateTestData: <T extends Yup.AnyObject>(schema: YupSchema<T>) => T;
generateObjectName: () => string;
generateName: () => string;
Expand All @@ -36,13 +39,16 @@ export interface DefaultSchemaReturn<T> {
getFieldDefinitions: () => { [key: string]: FieldConfig };
validate: (data: T) => Promise<ValidationResult<T>>;
clean: (data: T) => T;
toDatabase: (data: T) => any;
fromDatabase: (data: any) => T;
}

export const createDefaultSchema = <T>(
export const createDefaultSchema = <T extends Yup.AnyObject>(
yupSchema: Yup.ObjectSchema<any>,
customFieldDefinitions?: { [key: string]: Partial<FieldConfig> }
): DefaultSchemaReturn<T> => {
const schema = yupSchema;
const mockDataGenerator = createMockDataGenerator<T>(yupSchema);
//const getCustomFieldDefinitions: () => { [key: string]: Partial<FieldConfig> } = () => ({});

const removeUndefined = (obj: any) => {
Expand Down Expand Up @@ -102,6 +108,9 @@ export const createDefaultSchema = <T>(

return {
schema,
generateMockData: mockDataGenerator.generateMockData,
getMockData: <T extends Yup.AnyObject>(count: number) =>
mockDataGenerator.getMockData(count) as unknown as T[],
getCustomFieldDefinitions: () => {
return {};
},
Expand Down Expand Up @@ -225,6 +234,12 @@ export const createDefaultSchema = <T>(
clean: (data: T) => {
return removeUndefined(data);
},
toDatabase: (data: T): any => {
return data;
},
fromDatabase: (data: any): T => {
return data;
},
};
};

Expand Down
199 changes: 199 additions & 0 deletions src/schemas/mock-data-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { faker } from '@faker-js/faker';
import * as Yup from 'yup';

type Overrides = Record<string, () => any>;

type SchemaType =
| Yup.ObjectSchema<any>
| Yup.StringSchema
| Yup.NumberSchema
| Yup.BooleanSchema
| Yup.DateSchema
| Yup.ArraySchema<any, any>
| Yup.MixedSchema;

interface MockDataGeneratorConfig {
arrayLength?: number;
overrides?: Overrides;
validate?: boolean;
}

const defaultOverrides: Overrides = {
id: () => faker.string.nanoid(),
};

export function createMockDataGenerator<T>(
schema: SchemaType,
config: MockDataGeneratorConfig = {}
) {
const { arrayLength = 3, overrides: customOverrides = {} } = config || {};
const overrides = { ...defaultOverrides, ...customOverrides };

function validate(data: any, schema: SchemaType) {
try {
schema.validateSync(data, { abortEarly: false });
return true;
} catch (error) {
console.error(error);
return false;
}
}

function generateMockData<T>(yupSchema?: SchemaType): T {
const generateSchema = yupSchema || schema;
if (generateSchema instanceof Yup.ObjectSchema) {
const fields = generateSchema.fields;
const mockData: Partial<Record<keyof T, any>> = {};

for (const key in fields) {
const typedKey = key as keyof T;
const field = fields[key];

if (overrides[key]) {
if (typeof overrides[key] === 'function') {
mockData[typedKey] = overrides[key]();
continue;
} else {
mockData[typedKey] = overrides[key];
continue;
}
}

// Handle common fields by key
if (key === 'id') {
mockData[typedKey] = faker.string.nanoid();
continue;
}
// if (key === 'createdAt' || key === 'updatedAt') {
// mockData[typedKey] = faker.date.past();
// continue;
// }
// Generate field-specific data using type guards
if (field instanceof Yup.StringSchema) {
mockData[typedKey] = generateString(field);
} else if (field instanceof Yup.NumberSchema) {
mockData[typedKey] = generateNumber(field);
} else if (field instanceof Yup.BooleanSchema) {
mockData[typedKey] = faker.datatype.boolean();
} else if (field instanceof Yup.DateSchema) {
mockData[typedKey] = generateDate(field);
} else if (field instanceof Yup.ArraySchema) {
const innerSchema = field.innerType;
mockData[typedKey] = Array.from({ length: arrayLength }, () =>
generateMockData(innerSchema as SchemaType)
);
} else if (field instanceof Yup.ObjectSchema) {
mockData[typedKey] = generateMockData(field);
} else {
mockData[typedKey] = null;
}
}

if (config.validate) {
const validated = schema.validateSync(mockData as T, { abortEarly: false });
return validated;
}
return mockData as T;
}

return null as any;
}

function generateString(field: Yup.StringSchema): string {
const oneOfTest = field.tests.find((test) => test.OPTIONS?.name === 'oneOf');
if (oneOfTest) {
const validValues = oneOfTest.OPTIONS?.params?.values;
return faker.helpers.arrayElement(validValues as string[]);
}

// Create default value
let value = faker.lorem.words(2);

// Check default Yup tests
if (field.tests.some((test) => test.OPTIONS?.name === 'email')) {
value = faker.internet.email();
} else if (field.tests.some((test) => test.OPTIONS?.name === 'url')) {
value = faker.internet.url();
} else if (field.tests.some((test) => test.OPTIONS?.name === 'uuid')) {
value = faker.string.uuid();
}

// CHeck min-max length
const minTest = field.tests.find((test) => test.OPTIONS?.name === 'min');
const maxTest = field.tests.find((test) => test.OPTIONS?.name === 'max');

if (minTest || maxTest) {
const min = (minTest?.OPTIONS?.params?.min as number) || 1;
const max = (maxTest?.OPTIONS?.params?.max as number) || 20;

value = faker.lorem
.word()
.repeat(Math.floor(Math.random() * (max - min + 1) + min))
.slice(0, max);
}

// Check uppercase and lowercase
// console.log(field.tests);
// if (field.tests.some((test) => test.OPTIONS?.name === 'string_case')) {
// const foundTest = field.tests.find((test) => test.OPTIONS?.name === 'string_case');
// if (foundTest?.OPTIONS) {
// value = isUpperCase ? value.toUpperCase() : value.toLowerCase();
// }
// } else if (field.tests.some((test) => test.OPTIONS?.name === 'lowercase')) {
// value = value.toLowerCase();
// } TODO: not working

return value;
}

function generateNumber(field: Yup.NumberSchema): number {
const oneOfTest = field.tests.find((test) => test.OPTIONS?.name === 'oneOf');
if (oneOfTest) {
const validValues = oneOfTest.OPTIONS?.params?.values;
return faker.helpers.arrayElement(validValues as number[]);
}

// Min-max test
const minTest = field.tests.find((test) => test.OPTIONS?.name === 'min');
const maxTest = field.tests.find((test) => test.OPTIONS?.name === 'max');

const min = (minTest?.OPTIONS?.params?.min as number) || 0;
const max = (maxTest?.OPTIONS?.params?.max as number) || 100;

return faker.number.int({ min, max });
}

function generateDate(field: Yup.DateSchema): Date {
const oneOfTest = field.tests.find((test) => test.OPTIONS?.name === 'oneOf');
if (oneOfTest) {
const validValues = oneOfTest.OPTIONS?.params?.values;
return faker.helpers.arrayElement(
(validValues as string[]).map((date) => new Date(date)) as Date[]
);
}

const minTest = field.tests.find((test) => test.OPTIONS?.name === 'min');
const maxTest = field.tests.find((test) => test.OPTIONS?.name === 'max');

const minDate = minTest ? new Date(minTest.OPTIONS?.params?.min as string) : faker.date.past();
const maxDate = maxTest
? new Date(maxTest.OPTIONS?.params?.max as string)
: faker.date.future();

return faker.date.between({ from: minDate, to: maxDate });
}

const getMockData = (count: number): T[] => {
const mockData: T[] = [];
for (let i = 0; i < count; i++) {
mockData.push(generateMockData());
}
return mockData;
};

return {
generateMockData,
getMockData,
validate: (data: T) => validate(data, schema),
};
}

0 comments on commit ec5000f

Please sign in to comment.