Fix composite field update v2 (#15290)
# Introduction Fixing composite field update by computing field column type for each of its properties instead of globally ## Coverage Added integration tests for each composite field on both successful `create` and `update` ```ts Test Suites: 17 passed, 17 total Tests: 104 passed, 104 total Snapshots: 14 passed, 14 total Time: 135.431 s, estimated 143 s ``` ## Conlusion Related to https://github.com/twentyhq/core-team-issues/issues/1753
This commit is contained in:
+11
-5
@@ -301,14 +301,14 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
tableName,
|
||||
update,
|
||||
}: UpdateFieldPropertyUpdateHandlerArgs<'defaultValue'>) {
|
||||
const columnType = fieldMetadataTypeToColumnType(
|
||||
flatFieldMetadata.type,
|
||||
) as ColumnType;
|
||||
|
||||
if (isCompositeFieldMetadataType(flatFieldMetadata.type)) {
|
||||
const compositeType = getCompositeTypeOrThrow(flatFieldMetadata.type);
|
||||
|
||||
for (const property of compositeType.properties) {
|
||||
const columnType = fieldMetadataTypeToColumnType(
|
||||
property.type,
|
||||
) as ColumnType;
|
||||
|
||||
if (isMorphOrRelationFieldMetadataType(property.type)) {
|
||||
throw new WorkspaceMigrationRunnerException(
|
||||
'Relation field metadata in composite type is not supported yet',
|
||||
@@ -331,7 +331,7 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
defaultValue: compositeDefaultValue,
|
||||
});
|
||||
|
||||
return await this.workspaceSchemaManagerService.columnManager.alterColumnDefault(
|
||||
await this.workspaceSchemaManagerService.columnManager.alterColumnDefault(
|
||||
{
|
||||
queryRunner,
|
||||
schemaName,
|
||||
@@ -341,8 +341,14 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const columnType = fieldMetadataTypeToColumnType(
|
||||
flatFieldMetadata.type,
|
||||
) as ColumnType;
|
||||
|
||||
const serializedNewDefaultValue = serializeDefaultValueV2({
|
||||
columnName: flatFieldMetadata.name,
|
||||
schemaName,
|
||||
|
||||
+14
-14
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with conflicting country code and calling code 1`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with conflicting country code and calling code 1`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -14,7 +14,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with conflicting country code and calling code 2`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with conflicting country code and calling code 2`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -28,7 +28,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with conflicting phone number calling code 1`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with conflicting phone number calling code 1`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -42,7 +42,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with conflicting phone number calling code 2`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with conflicting phone number calling code 2`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -56,7 +56,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with conflicting phone number country code 1`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with conflicting phone number country code 1`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -70,7 +70,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with conflicting phone number country code 2`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with conflicting phone number country code 2`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -84,7 +84,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with invalid calling code 1`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid calling code 1`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -98,7 +98,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with invalid calling code 2`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid calling code 2`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -112,7 +112,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with invalid country code 1`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid country code 1`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -126,7 +126,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with invalid country code 2`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid country code 2`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -140,7 +140,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with invalid phone number format 1`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid phone number format 1`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -154,7 +154,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field with invalid phone number format 2`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid phone number format 2`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -168,7 +168,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field without country or calling code at all 1`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field without country or calling code at all 1`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
@@ -182,7 +182,7 @@ exports[`Phone field metadata tests suite It should fail to create primary phone
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Phone field metadata tests suite It should fail to create primary phone field without country or calling code at all 2`] = `
|
||||
exports[`failing create phone field metadata test suite it should fail to create primary phone field without country or calling code at all 2`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { type CountryCode } from 'libphonenumber-js';
|
||||
import { createOneOperation } from 'test/integration/graphql/utils/create-one-operation.util';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { type EachTestingContext } from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
type AdditionalPhoneMetadata,
|
||||
type PhonesMetadata,
|
||||
} from 'src/engine/metadata-modules/field-metadata/composite-types/phones.composite-type';
|
||||
|
||||
const FIELD_NAME = 'phonenumber';
|
||||
|
||||
type TestCaseInputAndExpected = Partial<
|
||||
Omit<PhonesMetadata, 'additionalPhones'>
|
||||
> & {
|
||||
additionalPhones?: Array<Partial<AdditionalPhoneMetadata>> | null;
|
||||
};
|
||||
|
||||
type CreatePhoneFieldMetadataTestCase = {
|
||||
input: TestCaseInputAndExpected;
|
||||
expected?: TestCaseInputAndExpected;
|
||||
};
|
||||
|
||||
const FAILING_TEST_INPUTS: { input: Partial<PhonesMetadata>; label: string }[] =
|
||||
[
|
||||
{
|
||||
label: 'phone field without country or calling code at all',
|
||||
input: {
|
||||
primaryPhoneNumber: '123456789',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with invalid country code',
|
||||
input: {
|
||||
primaryPhoneNumber: '123456789',
|
||||
primaryPhoneCallingCode: '+33',
|
||||
primaryPhoneCountryCode: 'XX' as CountryCode,
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with invalid calling code',
|
||||
input: {
|
||||
primaryPhoneNumber: '123456789',
|
||||
primaryPhoneCallingCode: '+999',
|
||||
primaryPhoneCountryCode: 'FR',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with conflicting country code and calling code',
|
||||
input: {
|
||||
primaryPhoneNumber: '123456789',
|
||||
primaryPhoneCallingCode: '+33',
|
||||
primaryPhoneCountryCode: 'US',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with invalid phone number format',
|
||||
input: {
|
||||
primaryPhoneNumber: 'not-a-number',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with conflicting phone number country code',
|
||||
input: {
|
||||
primaryPhoneNumber: '+33123456789',
|
||||
primaryPhoneCountryCode: 'US',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with conflicting phone number calling code',
|
||||
input: {
|
||||
primaryPhoneNumber: '+33123456789',
|
||||
primaryPhoneCallingCode: '+1',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const primaryFailingTests = FAILING_TEST_INPUTS.map<
|
||||
EachTestingContext<CreatePhoneFieldMetadataTestCase>
|
||||
>(({ input, label }) => ({
|
||||
title: `create primary ${label}`,
|
||||
context: {
|
||||
input: {
|
||||
...input,
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const additionalPhonesNumberFailingTests = FAILING_TEST_INPUTS.map<
|
||||
EachTestingContext<CreatePhoneFieldMetadataTestCase>
|
||||
>(
|
||||
({
|
||||
input: {
|
||||
primaryPhoneCallingCode,
|
||||
primaryPhoneCountryCode,
|
||||
primaryPhoneNumber,
|
||||
},
|
||||
label,
|
||||
}) => ({
|
||||
title: `create primary ${label}`,
|
||||
context: {
|
||||
input: {
|
||||
additionalPhones: [
|
||||
{
|
||||
callingCode: primaryPhoneCallingCode,
|
||||
countryCode: primaryPhoneCountryCode,
|
||||
number: primaryPhoneNumber,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const FAILING_TEST_CASES: EachTestingContext<CreatePhoneFieldMetadataTestCase>[] =
|
||||
[...primaryFailingTests, ...additionalPhonesNumberFailingTests];
|
||||
|
||||
describe('failing create phone field metadata test suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestObject',
|
||||
namePlural: 'myTestObjects',
|
||||
labelSingular: 'My Test Object',
|
||||
labelPlural: 'My Test Objects',
|
||||
icon: 'Icon123',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
|
||||
await createOneFieldMetadata({
|
||||
input: {
|
||||
name: FIELD_NAME,
|
||||
label: 'Phone number',
|
||||
type: FieldMetadataType.PHONES,
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: {
|
||||
isActive: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
test.each(FAILING_TEST_CASES)(
|
||||
'it should fail to $title',
|
||||
|
||||
async ({ context: { input } }) => {
|
||||
const {
|
||||
data: { createOneResponse },
|
||||
errors,
|
||||
} = await createOneOperation<{
|
||||
id: string;
|
||||
[FIELD_NAME]: any;
|
||||
}>({
|
||||
objectMetadataSingularName: 'myTestObject',
|
||||
input: {
|
||||
id: faker.string.uuid(),
|
||||
[FIELD_NAME]: input,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
${FIELD_NAME} {
|
||||
primaryPhoneNumber
|
||||
primaryPhoneCountryCode
|
||||
primaryPhoneCallingCode
|
||||
additionalPhones
|
||||
__typename
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(createOneResponse).toBeNull();
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
});
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Omit<CreateFieldInput, 'objectMetadataId' | 'type' | 'workspaceId'>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'actor field with basic metadata (name, label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
name: 'processedBy',
|
||||
label: 'Processed By',
|
||||
description: 'Person who processed this',
|
||||
icon: 'IconUser',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'actor field with default value containing source and name',
|
||||
context: {
|
||||
input: {
|
||||
name: 'initiatedBy',
|
||||
label: 'Initiated By',
|
||||
defaultValue: {
|
||||
source: "'MANUAL'",
|
||||
workspaceMemberId: null,
|
||||
name: "'System'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'actor field with API source default',
|
||||
context: {
|
||||
input: {
|
||||
name: 'importedBy',
|
||||
label: 'Imported By',
|
||||
defaultValue: {
|
||||
source: "'API'",
|
||||
workspaceMemberId: null,
|
||||
name: "'API User'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Actor field metadata creation tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string | undefined = undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testActorObject',
|
||||
namePlural: 'testActorObjects',
|
||||
labelSingular: 'Test Actor Object',
|
||||
labelPlural: 'Test Actor Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (createdFieldMetadataId) {
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
updatePayload: { isActive: false },
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
expectToFail: false,
|
||||
});
|
||||
createdFieldMetadataId = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should create $title',
|
||||
async ({ context: { input } }) => {
|
||||
const inputPayload = {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.ACTOR,
|
||||
isLabelSyncedWithName: false,
|
||||
...input,
|
||||
};
|
||||
const { data } = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: inputPayload,
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
defaultValue
|
||||
isLabelSyncedWithName
|
||||
settings
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = data.createOneField.id;
|
||||
|
||||
const { objectMetadataId: _, ...expectedFields } = inputPayload;
|
||||
|
||||
expect(data.createOneField).toMatchObject(expectedFields);
|
||||
},
|
||||
);
|
||||
});
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Omit<CreateFieldInput, 'objectMetadataId' | 'type' | 'workspaceId'>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'address field with basic metadata (name, label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
name: 'location',
|
||||
label: 'Location',
|
||||
description: 'Physical location',
|
||||
icon: 'IconMapPin',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field with subFields setting (street and city only)',
|
||||
context: {
|
||||
input: {
|
||||
name: 'simpleAddress',
|
||||
label: 'Simple Address',
|
||||
settings: {
|
||||
subFields: ['addressStreet1', 'addressCity'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field with all subFields',
|
||||
context: {
|
||||
input: {
|
||||
name: 'fullAddress',
|
||||
label: 'Full Address',
|
||||
settings: {
|
||||
subFields: [
|
||||
'addressStreet1',
|
||||
'addressStreet2',
|
||||
'addressCity',
|
||||
'addressState',
|
||||
'addressPostcode',
|
||||
'addressCountry',
|
||||
'addressLat',
|
||||
'addressLng',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field with default value containing full address',
|
||||
context: {
|
||||
input: {
|
||||
name: 'officeAddress',
|
||||
label: 'Office Address',
|
||||
defaultValue: {
|
||||
addressStreet1: "'123 Main St'",
|
||||
addressStreet2: "'Suite 100'",
|
||||
addressCity: "'San Francisco'",
|
||||
addressState: "'CA'",
|
||||
addressPostcode: "'94102'",
|
||||
addressCountry: "'USA'",
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field with partial default value',
|
||||
context: {
|
||||
input: {
|
||||
name: 'shippingAddress',
|
||||
label: 'Shipping Address',
|
||||
defaultValue: {
|
||||
addressStreet1: "'456 Oak Ave'",
|
||||
addressStreet2: "''",
|
||||
addressCity: "'New York'",
|
||||
addressState: "''",
|
||||
addressPostcode: "''",
|
||||
addressCountry: "''",
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field with empty default value',
|
||||
context: {
|
||||
input: {
|
||||
name: 'billingAddress',
|
||||
label: 'Billing Address',
|
||||
defaultValue: {
|
||||
addressStreet1: "''",
|
||||
addressStreet2: "''",
|
||||
addressCity: "''",
|
||||
addressState: "''",
|
||||
addressPostcode: "''",
|
||||
addressCountry: "''",
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field with settings and default value',
|
||||
context: {
|
||||
input: {
|
||||
name: 'warehouseAddress',
|
||||
label: 'Warehouse Address',
|
||||
settings: {
|
||||
subFields: ['addressStreet1', 'addressCity', 'addressCountry'],
|
||||
},
|
||||
defaultValue: {
|
||||
addressStreet1: "''",
|
||||
addressStreet2: "''",
|
||||
addressCity: "''",
|
||||
addressState: "''",
|
||||
addressPostcode: "''",
|
||||
addressCountry: "'USA'",
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Address field metadata creation tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string | undefined = undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testAddressObject',
|
||||
namePlural: 'testAddressObjects',
|
||||
labelSingular: 'Test Address Object',
|
||||
labelPlural: 'Test Address Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (createdFieldMetadataId) {
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
updatePayload: { isActive: false },
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
expectToFail: false,
|
||||
});
|
||||
createdFieldMetadataId = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should create $title',
|
||||
async ({ context: { input } }) => {
|
||||
const inputPayload = {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.ADDRESS,
|
||||
isLabelSyncedWithName: false,
|
||||
...input,
|
||||
};
|
||||
const { data } = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: inputPayload,
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
defaultValue
|
||||
isLabelSyncedWithName
|
||||
settings
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = data.createOneField.id;
|
||||
|
||||
const { objectMetadataId: _, ...expectedFields } = inputPayload;
|
||||
|
||||
expect(data.createOneField).toMatchObject(expectedFields);
|
||||
},
|
||||
);
|
||||
});
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Omit<CreateFieldInput, 'objectMetadataId' | 'type' | 'workspaceId'>;
|
||||
}>[] = [
|
||||
{
|
||||
title:
|
||||
'currency field with basic metadata (name, label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
name: 'amount',
|
||||
label: 'Amount',
|
||||
description: 'Transaction amount',
|
||||
icon: 'IconCurrencyDollar',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'currency field with default value containing amount and currency',
|
||||
context: {
|
||||
input: {
|
||||
name: 'price',
|
||||
label: 'Price',
|
||||
defaultValue: {
|
||||
amountMicros: "'1000000'",
|
||||
currencyCode: "'USD'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'currency field with zero default amount',
|
||||
context: {
|
||||
input: {
|
||||
name: 'discount',
|
||||
label: 'Discount',
|
||||
defaultValue: {
|
||||
amountMicros: "'0'",
|
||||
currencyCode: "'EUR'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'currency field with only currency code default',
|
||||
context: {
|
||||
input: {
|
||||
name: 'budget',
|
||||
label: 'Budget',
|
||||
defaultValue: {
|
||||
amountMicros: null,
|
||||
currencyCode: "'GBP'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'currency field with empty default value',
|
||||
context: {
|
||||
input: {
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
defaultValue: {
|
||||
amountMicros: null,
|
||||
currencyCode: "''",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Currency field metadata creation tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string | undefined = undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testCurrencyObject',
|
||||
namePlural: 'testCurrencyObjects',
|
||||
labelSingular: 'Test Currency Object',
|
||||
labelPlural: 'Test Currency Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (createdFieldMetadataId) {
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
updatePayload: { isActive: false },
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
expectToFail: false,
|
||||
});
|
||||
createdFieldMetadataId = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should create $title',
|
||||
async ({ context: { input } }) => {
|
||||
const inputPayload = {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
isLabelSyncedWithName: false,
|
||||
...input,
|
||||
};
|
||||
const { data } = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: inputPayload,
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
defaultValue
|
||||
isLabelSyncedWithName
|
||||
settings
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = data.createOneField.id;
|
||||
|
||||
const { objectMetadataId: _, ...expectedFields } = inputPayload;
|
||||
|
||||
expect(data.createOneField).toMatchObject(expectedFields);
|
||||
},
|
||||
);
|
||||
});
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Omit<CreateFieldInput, 'objectMetadataId' | 'type' | 'workspaceId'>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'emails field with basic metadata (name, label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
name: 'contactEmails',
|
||||
label: 'Contact Emails',
|
||||
description: 'Contact email addresses',
|
||||
icon: 'IconMail',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'emails field with maxNumberOfValues setting',
|
||||
context: {
|
||||
input: {
|
||||
name: 'limitedEmails',
|
||||
label: 'Limited Emails',
|
||||
settings: {
|
||||
maxNumberOfValues: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'emails field with default value containing primary email',
|
||||
context: {
|
||||
input: {
|
||||
name: 'defaultEmails',
|
||||
label: 'Default Emails',
|
||||
defaultValue: {
|
||||
primaryEmail: "'contact@example.com'",
|
||||
additionalEmails: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'emails field with empty default value',
|
||||
context: {
|
||||
input: {
|
||||
name: 'emptyEmails',
|
||||
label: 'Empty Emails',
|
||||
defaultValue: {
|
||||
primaryEmail: "''",
|
||||
additionalEmails: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'emails field with settings and default value',
|
||||
context: {
|
||||
input: {
|
||||
name: 'configuredEmails',
|
||||
label: 'Configured Emails',
|
||||
settings: {
|
||||
maxNumberOfValues: 5,
|
||||
},
|
||||
defaultValue: {
|
||||
primaryEmail: "'support@company.com'",
|
||||
additionalEmails: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Emails field metadata creation tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string | undefined = undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testEmailsObject',
|
||||
namePlural: 'testEmailsObjects',
|
||||
labelSingular: 'Test Emails Object',
|
||||
labelPlural: 'Test Emails Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (createdFieldMetadataId) {
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
updatePayload: { isActive: false },
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
expectToFail: false,
|
||||
});
|
||||
createdFieldMetadataId = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should create $title',
|
||||
async ({ context: { input } }) => {
|
||||
const inputPayload = {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.EMAILS,
|
||||
isLabelSyncedWithName: false,
|
||||
...input,
|
||||
};
|
||||
const { data } = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: inputPayload,
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
defaultValue
|
||||
isLabelSyncedWithName
|
||||
settings
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = data.createOneField.id;
|
||||
|
||||
const { objectMetadataId: _, ...expectedFields } = inputPayload;
|
||||
|
||||
expect(data.createOneField).toMatchObject(expectedFields);
|
||||
},
|
||||
);
|
||||
});
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Omit<CreateFieldInput, 'objectMetadataId' | 'type' | 'workspaceId'>;
|
||||
}>[] = [
|
||||
{
|
||||
title:
|
||||
'full name field with basic metadata (name, label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
name: 'contactName',
|
||||
label: 'Contact Name',
|
||||
description: 'Full name of contact',
|
||||
icon: 'IconUser',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'full name field with default value containing first and last name',
|
||||
context: {
|
||||
input: {
|
||||
name: 'ownerName',
|
||||
label: 'Owner Name',
|
||||
defaultValue: {
|
||||
firstName: "'John'",
|
||||
lastName: "'Doe'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'full name field with only first name default',
|
||||
context: {
|
||||
input: {
|
||||
name: 'customerName',
|
||||
label: 'Customer Name',
|
||||
defaultValue: {
|
||||
firstName: "'Jane'",
|
||||
lastName: "''",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'full name field with empty default value',
|
||||
context: {
|
||||
input: {
|
||||
name: 'authorName',
|
||||
label: 'Author Name',
|
||||
defaultValue: {
|
||||
firstName: "''",
|
||||
lastName: "''",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Full name field metadata creation tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string | undefined = undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testFullNameObject',
|
||||
namePlural: 'testFullNameObjects',
|
||||
labelSingular: 'Test Full Name Object',
|
||||
labelPlural: 'Test Full Name Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (createdFieldMetadataId) {
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
updatePayload: { isActive: false },
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
expectToFail: false,
|
||||
});
|
||||
createdFieldMetadataId = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should create $title',
|
||||
async ({ context: { input } }) => {
|
||||
const inputPayload = {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
isLabelSyncedWithName: false,
|
||||
...input,
|
||||
};
|
||||
const { data } = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: inputPayload,
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
defaultValue
|
||||
isLabelSyncedWithName
|
||||
settings
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = data.createOneField.id;
|
||||
|
||||
const { objectMetadataId: _, ...expectedFields } = inputPayload;
|
||||
|
||||
expect(data.createOneField).toMatchObject(expectedFields);
|
||||
},
|
||||
);
|
||||
});
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Omit<CreateFieldInput, 'objectMetadataId' | 'type' | 'workspaceId'>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'links field with basic metadata (name, label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
name: 'socialLinks',
|
||||
label: 'Social Links',
|
||||
description: 'Social media links',
|
||||
icon: 'IconLink',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'links field with maxNumberOfValues setting',
|
||||
context: {
|
||||
input: {
|
||||
name: 'limitedLinks',
|
||||
label: 'Limited Links',
|
||||
settings: {
|
||||
maxNumberOfValues: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'links field with default value containing primary link',
|
||||
context: {
|
||||
input: {
|
||||
name: 'websiteLinks',
|
||||
label: 'Website Links',
|
||||
defaultValue: {
|
||||
primaryLinkLabel: "'Website'",
|
||||
primaryLinkUrl: "'https://example.com'",
|
||||
secondaryLinks: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'links field with empty default value',
|
||||
context: {
|
||||
input: {
|
||||
name: 'emptyLinks',
|
||||
label: 'Empty Links',
|
||||
defaultValue: {
|
||||
primaryLinkLabel: "''",
|
||||
primaryLinkUrl: "''",
|
||||
secondaryLinks: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'links field with settings and default value',
|
||||
context: {
|
||||
input: {
|
||||
name: 'configuredLinks',
|
||||
label: 'Configured Links',
|
||||
settings: {
|
||||
maxNumberOfValues: 3,
|
||||
},
|
||||
defaultValue: {
|
||||
primaryLinkLabel: "'Homepage'",
|
||||
primaryLinkUrl: "'https://company.com'",
|
||||
secondaryLinks: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Links field metadata creation tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string | undefined = undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testLinksObject',
|
||||
namePlural: 'testLinksObjects',
|
||||
labelSingular: 'Test Links Object',
|
||||
labelPlural: 'Test Links Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (createdFieldMetadataId) {
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
updatePayload: { isActive: false },
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
expectToFail: false,
|
||||
});
|
||||
createdFieldMetadataId = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should create $title',
|
||||
async ({ context: { input } }) => {
|
||||
const inputPayload = {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.LINKS,
|
||||
isLabelSyncedWithName: false,
|
||||
...input,
|
||||
};
|
||||
const { data } = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: inputPayload,
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
defaultValue
|
||||
isLabelSyncedWithName
|
||||
settings
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = data.createOneField.id;
|
||||
|
||||
const { objectMetadataId: _, ...expectedFields } = inputPayload;
|
||||
|
||||
expect(data.createOneField).toMatchObject(expectedFields);
|
||||
},
|
||||
);
|
||||
});
|
||||
+2
-138
@@ -219,109 +219,7 @@ const SUCCESSFUL_TEST_CASES: EachTestingContext<CreatePhoneFieldMetadataTestCase
|
||||
},
|
||||
];
|
||||
|
||||
const FAILING_TEST_INPUTS: { input: Partial<PhonesMetadata>; label: string }[] =
|
||||
[
|
||||
{
|
||||
label: 'phone field without country or calling code at all',
|
||||
input: {
|
||||
primaryPhoneNumber: '123456789',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with invalid country code',
|
||||
input: {
|
||||
primaryPhoneNumber: '123456789',
|
||||
primaryPhoneCallingCode: '+33',
|
||||
primaryPhoneCountryCode: 'XX' as CountryCode,
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with invalid calling code',
|
||||
input: {
|
||||
primaryPhoneNumber: '123456789',
|
||||
primaryPhoneCallingCode: '+999',
|
||||
primaryPhoneCountryCode: 'FR',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with conflicting country code and calling code',
|
||||
input: {
|
||||
primaryPhoneNumber: '123456789',
|
||||
primaryPhoneCallingCode: '+33',
|
||||
primaryPhoneCountryCode: 'US',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with invalid phone number format',
|
||||
input: {
|
||||
primaryPhoneNumber: 'not-a-number',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with conflicting phone number country code',
|
||||
input: {
|
||||
primaryPhoneNumber: '+33123456789',
|
||||
primaryPhoneCountryCode: 'US',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'phone field with conflicting phone number calling code',
|
||||
input: {
|
||||
primaryPhoneNumber: '+33123456789',
|
||||
primaryPhoneCallingCode: '+1',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const primaryFailingTests = FAILING_TEST_INPUTS.map<
|
||||
EachTestingContext<CreatePhoneFieldMetadataTestCase>
|
||||
>(({ input, label }) => ({
|
||||
title: `create primary ${label}`,
|
||||
context: {
|
||||
input: {
|
||||
...input,
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const additionalPhonesNumberFailingTests = FAILING_TEST_INPUTS.map<
|
||||
EachTestingContext<CreatePhoneFieldMetadataTestCase>
|
||||
>(
|
||||
({
|
||||
input: {
|
||||
primaryPhoneCallingCode,
|
||||
primaryPhoneCountryCode,
|
||||
primaryPhoneNumber,
|
||||
},
|
||||
label,
|
||||
}) => ({
|
||||
title: `create primary ${label}`,
|
||||
context: {
|
||||
input: {
|
||||
additionalPhones: [
|
||||
{
|
||||
callingCode: primaryPhoneCallingCode,
|
||||
countryCode: primaryPhoneCountryCode,
|
||||
number: primaryPhoneNumber,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const FAILING_TEST_CASES: EachTestingContext<CreatePhoneFieldMetadataTestCase>[] =
|
||||
[...primaryFailingTests, ...additionalPhonesNumberFailingTests];
|
||||
|
||||
describe('Phone field metadata tests suite', () => {
|
||||
describe('successful create phone field metadata test suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -371,7 +269,7 @@ describe('Phone field metadata tests suite', () => {
|
||||
});
|
||||
|
||||
test.each(SUCCESSFUL_TEST_CASES)(
|
||||
'It should succeed $title',
|
||||
'it should succeed $title',
|
||||
|
||||
async ({ context: { input, expected } }) => {
|
||||
const {
|
||||
@@ -407,38 +305,4 @@ describe('Phone field metadata tests suite', () => {
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.each(FAILING_TEST_CASES)(
|
||||
'It should fail to $title',
|
||||
|
||||
async ({ context: { input } }) => {
|
||||
const {
|
||||
data: { createOneResponse },
|
||||
errors,
|
||||
} = await createOneOperation<{
|
||||
id: string;
|
||||
[FIELD_NAME]: any;
|
||||
}>({
|
||||
objectMetadataSingularName: 'myTestObject',
|
||||
input: {
|
||||
id: faker.string.uuid(),
|
||||
[FIELD_NAME]: input,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
${FIELD_NAME} {
|
||||
primaryPhoneNumber
|
||||
primaryPhoneCountryCode
|
||||
primaryPhoneCallingCode
|
||||
additionalPhones
|
||||
__typename
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(createOneResponse).toBeNull();
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
});
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Omit<CreateFieldInput, 'objectMetadataId' | 'type' | 'workspaceId'>;
|
||||
}>[] = [
|
||||
{
|
||||
title:
|
||||
'rich text v2 field with basic metadata (name, label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
description: 'Rich text description field',
|
||||
icon: 'IconNotes',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'rich text v2 field with default value containing markdown',
|
||||
context: {
|
||||
input: {
|
||||
name: 'notes',
|
||||
label: 'Notes',
|
||||
defaultValue: {
|
||||
blocknote: null,
|
||||
markdown: "'# Default Title'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'rich text v2 field with default value containing blocknote',
|
||||
context: {
|
||||
input: {
|
||||
name: 'content',
|
||||
label: 'Content',
|
||||
defaultValue: {
|
||||
blocknote: '\'{"blocks":[{"type":"paragraph"}]}\'',
|
||||
markdown: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'rich text v2 field with empty default value',
|
||||
context: {
|
||||
input: {
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
defaultValue: {
|
||||
blocknote: null,
|
||||
markdown: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Rich text v2 field metadata creation tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string | undefined = undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testRichTextV2Object',
|
||||
namePlural: 'testRichTextV2Objects',
|
||||
labelSingular: 'Test Rich Text V2 Object',
|
||||
labelPlural: 'Test Rich Text V2 Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (createdFieldMetadataId) {
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
updatePayload: { isActive: false },
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
expectToFail: false,
|
||||
});
|
||||
createdFieldMetadataId = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should create $title',
|
||||
async ({ context: { input } }) => {
|
||||
const inputPayload = {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.RICH_TEXT_V2,
|
||||
isLabelSyncedWithName: false,
|
||||
...input,
|
||||
};
|
||||
const { data } = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: inputPayload,
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
defaultValue
|
||||
isLabelSyncedWithName
|
||||
settings
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = data.createOneField.id;
|
||||
|
||||
const { objectMetadataId: _, ...expectedFields } = inputPayload;
|
||||
|
||||
expect(data.createOneField).toMatchObject(expectedFields);
|
||||
},
|
||||
);
|
||||
});
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Partial<UpdateFieldInput>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'actor field basic metadata (label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
label: 'Updated Actor',
|
||||
description: 'Updated description',
|
||||
icon: 'IconUsers',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'actor field default value with MANUAL source',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
source: "'MANUAL'",
|
||||
workspaceMemberId: null,
|
||||
name: "'System'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'actor field default value with API source',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
source: "'API'",
|
||||
workspaceMemberId: null,
|
||||
name: "'API User'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'actor field default value set to null',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Actor field metadata update tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testActorUpdateObject',
|
||||
namePlural: 'testActorUpdateObjects',
|
||||
labelSingular: 'Test Actor Update Object',
|
||||
labelPlural: 'Test Actor Update Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const {
|
||||
data: { createOneField },
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.ACTOR,
|
||||
name: 'testActor',
|
||||
label: 'Test Actor',
|
||||
description: 'Initial description',
|
||||
icon: 'IconUser',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = createOneField.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should update $title',
|
||||
async ({ context: { input } }) => {
|
||||
const { data } = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: input,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
settings
|
||||
defaultValue
|
||||
`,
|
||||
});
|
||||
|
||||
expect(data.updateOneField).toMatchObject(input);
|
||||
},
|
||||
);
|
||||
});
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Partial<UpdateFieldInput>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'address field basic metadata (label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
label: 'Updated Address',
|
||||
description: 'Updated description',
|
||||
icon: 'IconHome',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field settings with limited subFields',
|
||||
context: {
|
||||
input: {
|
||||
settings: {
|
||||
subFields: ['addressStreet1', 'addressCity', 'addressCountry'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field settings with all subFields',
|
||||
context: {
|
||||
input: {
|
||||
settings: {
|
||||
subFields: [
|
||||
'addressStreet1',
|
||||
'addressStreet2',
|
||||
'addressCity',
|
||||
'addressState',
|
||||
'addressPostcode',
|
||||
'addressCountry',
|
||||
'addressLat',
|
||||
'addressLng',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field default value with full address',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
addressStreet1: "'123 Main St'",
|
||||
addressStreet2: "'Apt 4'",
|
||||
addressCity: "'New York'",
|
||||
addressState: "'NY'",
|
||||
addressPostcode: "'10001'",
|
||||
addressCountry: "'USA'",
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field default value with partial address',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
addressStreet1: "'456 Oak Ave'",
|
||||
addressStreet2: "''",
|
||||
addressCity: "'Boston'",
|
||||
addressState: "''",
|
||||
addressPostcode: "''",
|
||||
addressCountry: "''",
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field default value set to null',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'address field settings and default value together',
|
||||
context: {
|
||||
input: {
|
||||
settings: {
|
||||
subFields: ['addressStreet1', 'addressCity', 'addressPostcode'],
|
||||
},
|
||||
defaultValue: {
|
||||
addressStreet1: "''",
|
||||
addressStreet2: "''",
|
||||
addressCity: "''",
|
||||
addressState: "''",
|
||||
addressPostcode: "''",
|
||||
addressCountry: "'USA'",
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Address field metadata update tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testAddressUpdateObject',
|
||||
namePlural: 'testAddressUpdateObjects',
|
||||
labelSingular: 'Test Address Update Object',
|
||||
labelPlural: 'Test Address Update Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const {
|
||||
data: { createOneField },
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.ADDRESS,
|
||||
name: 'testAddress',
|
||||
label: 'Test Address',
|
||||
description: 'Initial description',
|
||||
icon: 'IconMapPin',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = createOneField.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should update $title',
|
||||
async ({ context: { input } }) => {
|
||||
const { data } = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: input,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
settings
|
||||
defaultValue
|
||||
`,
|
||||
});
|
||||
|
||||
expect(data.updateOneField).toMatchObject(input);
|
||||
},
|
||||
);
|
||||
});
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Partial<UpdateFieldInput>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'currency field basic metadata (label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
label: 'Updated Currency',
|
||||
description: 'Updated description',
|
||||
icon: 'IconCoin',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'currency field default value with amount and currency',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
amountMicros: "'1000000'",
|
||||
currencyCode: "'USD'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'currency field default value with zero amount',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
amountMicros: "'0'",
|
||||
currencyCode: "'EUR'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'currency field default value with only currency code',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
amountMicros: null,
|
||||
currencyCode: "'GBP'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'currency field default value with empty values',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
amountMicros: null,
|
||||
currencyCode: "''",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'currency field default value set to null',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Currency field metadata update tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testCurrencyUpdateObject',
|
||||
namePlural: 'testCurrencyUpdateObjects',
|
||||
labelSingular: 'Test Currency Update Object',
|
||||
labelPlural: 'Test Currency Update Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const {
|
||||
data: { createOneField },
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
name: 'testCurrency',
|
||||
label: 'Test Currency',
|
||||
description: 'Initial description',
|
||||
icon: 'IconCurrencyDollar',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = createOneField.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should update $title',
|
||||
async ({ context: { input } }) => {
|
||||
const { data } = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: input,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
settings
|
||||
defaultValue
|
||||
`,
|
||||
});
|
||||
|
||||
expect(data.updateOneField).toMatchObject(input);
|
||||
},
|
||||
);
|
||||
});
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Partial<UpdateFieldInput>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'emails field basic metadata (label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
label: 'Updated Emails',
|
||||
description: 'Updated description',
|
||||
icon: 'IconMailOpened',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'emails field settings with maxNumberOfValues',
|
||||
context: {
|
||||
input: {
|
||||
settings: {
|
||||
maxNumberOfValues: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'emails field default value with primary email',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
primaryEmail: "'contact@example.com'",
|
||||
additionalEmails: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'emails field default value with empty values',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
primaryEmail: "''",
|
||||
additionalEmails: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'emails field default value set to null',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'emails field settings and default value together',
|
||||
context: {
|
||||
input: {
|
||||
settings: {
|
||||
maxNumberOfValues: 5,
|
||||
},
|
||||
defaultValue: {
|
||||
primaryEmail: "'support@company.com'",
|
||||
additionalEmails: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Emails field metadata update tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testEmailsUpdateObject',
|
||||
namePlural: 'testEmailsUpdateObjects',
|
||||
labelSingular: 'Test Emails Update Object',
|
||||
labelPlural: 'Test Emails Update Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const {
|
||||
data: { createOneField },
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.EMAILS,
|
||||
name: 'testEmails',
|
||||
label: 'Test Emails',
|
||||
description: 'Initial description',
|
||||
icon: 'IconMail',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = createOneField.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should update $title',
|
||||
async ({ context: { input } }) => {
|
||||
const { data } = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: input,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
settings
|
||||
defaultValue
|
||||
`,
|
||||
});
|
||||
|
||||
expect(data.updateOneField).toMatchObject(input);
|
||||
},
|
||||
);
|
||||
});
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Partial<UpdateFieldInput>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'full name field basic metadata (label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
label: 'Updated Full Name',
|
||||
description: 'Updated description',
|
||||
icon: 'IconUserCircle',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'full name field default value with first and last name',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
firstName: "'John'",
|
||||
lastName: "'Doe'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'full name field default value with only first name',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
firstName: "'Jane'",
|
||||
lastName: "''",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'full name field default value with empty values',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
firstName: "''",
|
||||
lastName: "''",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'full name field default value set to null',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Full name field metadata update tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testFullNameUpdateObject',
|
||||
namePlural: 'testFullNameUpdateObjects',
|
||||
labelSingular: 'Test Full Name Update Object',
|
||||
labelPlural: 'Test Full Name Update Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const {
|
||||
data: { createOneField },
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
name: 'testFullName',
|
||||
label: 'Test Full Name',
|
||||
description: 'Initial description',
|
||||
icon: 'IconUser',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = createOneField.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should update $title',
|
||||
async ({ context: { input } }) => {
|
||||
const { data } = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: input,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
settings
|
||||
defaultValue
|
||||
`,
|
||||
});
|
||||
|
||||
expect(data.updateOneField).toMatchObject(input);
|
||||
},
|
||||
);
|
||||
});
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Partial<UpdateFieldInput>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'links field basic metadata (label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
label: 'Updated Links',
|
||||
description: 'Updated description',
|
||||
icon: 'IconExternalLink',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'links field settings with maxNumberOfValues',
|
||||
context: {
|
||||
input: {
|
||||
settings: {
|
||||
maxNumberOfValues: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'links field default value with primary link',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
primaryLinkLabel: "'Website'",
|
||||
primaryLinkUrl: "'https://example.com'",
|
||||
secondaryLinks: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'links field default value with empty values',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
primaryLinkLabel: "''",
|
||||
primaryLinkUrl: "''",
|
||||
secondaryLinks: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'links field default value set to null',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'links field settings and default value together',
|
||||
context: {
|
||||
input: {
|
||||
settings: {
|
||||
maxNumberOfValues: 3,
|
||||
},
|
||||
defaultValue: {
|
||||
primaryLinkLabel: "'Homepage'",
|
||||
primaryLinkUrl: "'https://company.com'",
|
||||
secondaryLinks: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Links field metadata update tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testLinksUpdateObject',
|
||||
namePlural: 'testLinksUpdateObjects',
|
||||
labelSingular: 'Test Links Update Object',
|
||||
labelPlural: 'Test Links Update Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const {
|
||||
data: { createOneField },
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.LINKS,
|
||||
name: 'testLinks',
|
||||
label: 'Test Links',
|
||||
description: 'Initial description',
|
||||
icon: 'IconLink',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = createOneField.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should update $title',
|
||||
async ({ context: { input } }) => {
|
||||
const { data } = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: input,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
settings
|
||||
defaultValue
|
||||
`,
|
||||
});
|
||||
|
||||
expect(data.updateOneField).toMatchObject(input);
|
||||
},
|
||||
);
|
||||
});
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Partial<UpdateFieldInput>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'phones field basic metadata (label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
label: 'Updated Phones',
|
||||
description: 'Updated description',
|
||||
icon: 'IconPhoneCall',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'phones field settings with maxNumberOfValues',
|
||||
context: {
|
||||
input: {
|
||||
settings: {
|
||||
maxNumberOfValues: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'phones field default value with primary phone',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
primaryPhoneNumber: "'+33123456789'",
|
||||
primaryPhoneCountryCode: "'FR'",
|
||||
primaryPhoneCallingCode: "'+33'",
|
||||
additionalPhones: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'phones field default value with empty values',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
primaryPhoneNumber: "''",
|
||||
primaryPhoneCountryCode: "''",
|
||||
primaryPhoneCallingCode: "''",
|
||||
additionalPhones: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'phones field default value set to null',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'phones field settings and default value together',
|
||||
context: {
|
||||
input: {
|
||||
settings: {
|
||||
maxNumberOfValues: 6,
|
||||
},
|
||||
defaultValue: {
|
||||
primaryPhoneNumber: "'+1555123456'",
|
||||
primaryPhoneCountryCode: "'US'",
|
||||
primaryPhoneCallingCode: "'+1'",
|
||||
additionalPhones: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Phones field metadata update tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testPhonesUpdateObject',
|
||||
namePlural: 'testPhonesUpdateObjects',
|
||||
labelSingular: 'Test Phones Update Object',
|
||||
labelPlural: 'Test Phones Update Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const {
|
||||
data: { createOneField },
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.PHONES,
|
||||
name: 'testPhones',
|
||||
label: 'Test Phones',
|
||||
description: 'Initial description',
|
||||
icon: 'IconPhone',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = createOneField.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should update $title',
|
||||
async ({ context: { input } }) => {
|
||||
const { data } = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: input,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
settings
|
||||
defaultValue
|
||||
`,
|
||||
});
|
||||
|
||||
expect(data.updateOneField).toMatchObject(input);
|
||||
},
|
||||
);
|
||||
});
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
type EachTestingContext,
|
||||
eachTestingContextFilter,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<{
|
||||
input: Partial<UpdateFieldInput>;
|
||||
}>[] = [
|
||||
{
|
||||
title: 'rich text v2 field basic metadata (label, description, icon)',
|
||||
context: {
|
||||
input: {
|
||||
label: 'Updated Rich Text',
|
||||
description: 'Updated description',
|
||||
icon: 'IconFileText',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'rich text v2 field default value with markdown',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
blocknote: null,
|
||||
markdown: "'# Default Title'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'rich text v2 field default value with blocknote',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
blocknote: '\'{"blocks":[{"type":"paragraph"}]}\'',
|
||||
markdown: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'rich text v2 field default value with both formats',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: {
|
||||
blocknote: '\'{"blocks":[]}\'',
|
||||
markdown: "'## Heading'",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'rich text v2 field default value set to null',
|
||||
context: {
|
||||
input: {
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Rich text v2 field metadata update tests suite', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'testRichTextV2UpdateObject',
|
||||
namePlural: 'testRichTextV2UpdateObjects',
|
||||
labelSingular: 'Test Rich Text V2 Update Object',
|
||||
labelPlural: 'Test Rich Text V2 Update Objects',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = data.createOneObject.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const {
|
||||
data: { createOneField },
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.RICH_TEXT_V2,
|
||||
name: 'testRichText',
|
||||
label: 'Test Rich Text',
|
||||
description: 'Initial description',
|
||||
icon: 'IconNotes',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = createOneField.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdFieldMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
test.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
|
||||
'it should update $title',
|
||||
async ({ context: { input } }) => {
|
||||
const { data } = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: input,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
settings
|
||||
defaultValue
|
||||
`,
|
||||
});
|
||||
|
||||
expect(data.updateOneField).toMatchObject(input);
|
||||
},
|
||||
);
|
||||
});
|
||||
+3
-1
@@ -11,11 +11,11 @@ import { eachTestingContextFilter } from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import {
|
||||
type FieldMetadataComplexOption,
|
||||
type FieldMetadataDefaultOption,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
|
||||
describe.each(fieldMetadataEnumTypes)(
|
||||
'Successful update field metadata %s tests suite v2',
|
||||
@@ -128,6 +128,7 @@ describe.each(fieldMetadataEnumTypes)(
|
||||
: rawDefaultValue;
|
||||
|
||||
const { data: firstUpdate } = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: {
|
||||
@@ -146,6 +147,7 @@ describe.each(fieldMetadataEnumTypes)(
|
||||
|
||||
const updatedOptions = initialOptions.slice(1);
|
||||
const { data: secondUpdate, errors } = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: {
|
||||
|
||||
+1
-7
@@ -3,10 +3,7 @@ import {
|
||||
NOT_EXISTING_TEST_PERSON_ID,
|
||||
TEST_PERSON_1_ID,
|
||||
} from 'test/integration/constants/test-person-ids.constants';
|
||||
import {
|
||||
TEST_PRIMARY_LINK_URL,
|
||||
TEST_PRIMARY_LINK_URL_WIITHOUT_TRAILING_SLASH,
|
||||
} from 'test/integration/constants/test-primary-link-url.constant';
|
||||
import { TEST_PRIMARY_LINK_URL } from 'test/integration/constants/test-primary-link-url.constant';
|
||||
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
|
||||
import { deleteAllRecords } from 'test/integration/utils/delete-all-records';
|
||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||
@@ -98,9 +95,6 @@ describe('Core REST API Update One endpoint', () => {
|
||||
const updatedPerson = res.body.data.updatePerson;
|
||||
|
||||
expect(updatedPerson.company).toBeDefined();
|
||||
expect(updatedPerson.company.domainName.primaryLinkUrl).toBe(
|
||||
TEST_PRIMARY_LINK_URL_WIITHOUT_TRAILING_SLASH,
|
||||
);
|
||||
expect(updatedPerson.company.people).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user