snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 3s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
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": {
"code": "BAD_USER_INPUT",
"subCode": "CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE",
"userFriendlyMessage": "Provided country code and calling code are conflicting",
},
"message": "Provided country code and calling code are conflicting",
},
]
`;
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": {
"code": "BAD_USER_INPUT",
"subCode": "CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE",
"userFriendlyMessage": "Provided country code and calling code are conflicting",
},
"message": "Provided country code and calling code are conflicting",
},
]
`;
exports[`failing create phone field metadata test suite it should fail to create primary phone field with conflicting phone number calling code 1`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
"subCode": "CONFLICTING_PHONE_CALLING_CODE",
"userFriendlyMessage": "Provided and inferred calling code are conflicting",
},
"message": "Provided and inferred calling code are conflicting",
},
]
`;
exports[`failing create phone field metadata test suite it should fail to create primary phone field with conflicting phone number calling code 2`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
"subCode": "CONFLICTING_PHONE_CALLING_CODE",
"userFriendlyMessage": "Provided and inferred calling code are conflicting",
},
"message": "Provided and inferred calling code are conflicting",
},
]
`;
exports[`failing create phone field metadata test suite it should fail to create primary phone field with conflicting phone number country code 1`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
"subCode": "CONFLICTING_PHONE_COUNTRY_CODE",
"userFriendlyMessage": "Provided and inferred country code are conflicting",
},
"message": "Provided and inferred country code are conflicting",
},
]
`;
exports[`failing create phone field metadata test suite it should fail to create primary phone field with conflicting phone number country code 2`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
"subCode": "CONFLICTING_PHONE_COUNTRY_CODE",
"userFriendlyMessage": "Provided and inferred country code are conflicting",
},
"message": "Provided and inferred country code are conflicting",
},
]
`;
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid calling code 1`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
"subCode": "INVALID_PHONE_CALLING_CODE",
"userFriendlyMessage": "Invalid calling code +999",
},
"message": "Invalid calling code +999",
},
]
`;
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid calling code 2`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
"subCode": "INVALID_PHONE_CALLING_CODE",
"userFriendlyMessage": "Invalid calling code +999",
},
"message": "Invalid calling code +999",
},
]
`;
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid country code 1`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
"subCode": "INVALID_PHONE_COUNTRY_CODE",
"userFriendlyMessage": "Invalid country code XX",
},
"message": "Invalid country code XX",
},
]
`;
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid country code 2`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
"subCode": "INVALID_PHONE_COUNTRY_CODE",
"userFriendlyMessage": "Invalid country code XX",
},
"message": "Invalid country code XX",
},
]
`;
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid phone number format 1`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
"subCode": "INVALID_PHONE_NUMBER",
"userFriendlyMessage": "Provided phone number is invalid not-a-number",
},
"message": "Provided phone number is invalid not-a-number",
},
]
`;
exports[`failing create phone field metadata test suite it should fail to create primary phone field with invalid phone number format 2`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
"subCode": "INVALID_PHONE_NUMBER",
"userFriendlyMessage": "Provided phone number is invalid not-a-number",
},
"message": "Provided phone number is invalid not-a-number",
},
]
`;
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": {
"code": "BAD_USER_INPUT",
"subCode": "INVALID_PHONE_NUMBER",
"userFriendlyMessage": "Provided phone number is invalid 123456789",
},
"message": "Provided phone number is invalid 123456789",
},
]
`;
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": {
"code": "BAD_USER_INPUT",
"subCode": "INVALID_PHONE_NUMBER",
"userFriendlyMessage": "Provided phone number is invalid 123456789",
},
"message": "Provided phone number is invalid 123456789",
},
]
`;

View File

@@ -0,0 +1,216 @@
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,
type AdditionalPhoneMetadata,
type PhonesMetadata,
} from 'twenty-shared/types';
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 {
number
callingCode
countryCode
}
__typename
}
`,
});
expect(createOneResponse).toBeNull();
expect(errors).toBeDefined();
expect(errors).toMatchSnapshot();
},
);
});

View File

@@ -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);
},
);
});

View File

@@ -0,0 +1,217 @@
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: null,
addressCity: "'New York'",
addressState: null,
addressPostcode: null,
addressCountry: null,
addressLat: null,
addressLng: null,
},
},
},
},
{
title: 'address field with empty default value',
context: {
input: {
name: 'billingAddress',
label: 'Billing Address',
defaultValue: null,
},
},
},
{
title: 'address field with settings and default value',
context: {
input: {
name: 'warehouseAddress',
label: 'Warehouse Address',
settings: {
subFields: ['addressStreet1', 'addressCity', 'addressCountry'],
},
defaultValue: {
addressStreet1: null,
addressStreet2: null,
addressCity: null,
addressState: null,
addressPostcode: null,
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);
},
);
});

View File

@@ -0,0 +1,164 @@
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: null,
},
},
},
];
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);
},
);
});

View File

@@ -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 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: 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);
},
);
});

View File

@@ -0,0 +1,151 @@
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: null,
},
},
},
},
{
title: 'full name field with empty default value',
context: {
input: {
name: 'authorName',
label: 'Author Name',
defaultValue: null,
},
},
},
];
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);
},
);
});

View File

@@ -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: '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: 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);
},
);
});

View File

@@ -0,0 +1,316 @@
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,
type AdditionalPhoneMetadata,
type PhonesMetadata,
} from 'twenty-shared/types';
const FIELD_NAME = 'phonenumber';
type TestCaseInputAndExpected = Partial<
Omit<PhonesMetadata, 'additionalPhones'>
> & {
additionalPhones?: Array<
Partial<Record<keyof AdditionalPhoneMetadata, string | null>>
> | null;
};
type CreatePhoneFieldMetadataTestCase = {
input: TestCaseInputAndExpected;
expected?: TestCaseInputAndExpected;
};
const SUCCESSFUL_TEST_CASES: EachTestingContext<CreatePhoneFieldMetadataTestCase>[] =
[
{
title: 'create primary phone field',
context: {
input: {
primaryPhoneNumber: '123456789',
primaryPhoneCallingCode: '+33',
primaryPhoneCountryCode: 'FR',
additionalPhones: [],
},
expected: {
primaryPhoneNumber: '123456789',
primaryPhoneCallingCode: '+33',
primaryPhoneCountryCode: 'FR',
additionalPhones: [],
},
},
},
{
title: 'create primary phone field with number and other information',
context: {
input: {
primaryPhoneNumber: '123456789',
primaryPhoneCallingCode: '+33',
primaryPhoneCountryCode: 'FR',
additionalPhones: [],
},
expected: {
primaryPhoneNumber: '123456789',
primaryPhoneCallingCode: '+33',
primaryPhoneCountryCode: 'FR',
additionalPhones: [],
},
},
},
{
title:
'create primary phone field with full international format and other information',
context: {
input: {
primaryPhoneNumber: '+1123456789',
primaryPhoneCountryCode: 'US',
primaryPhoneCallingCode: '+1',
additionalPhones: null,
},
expected: {
primaryPhoneNumber: '123456789',
primaryPhoneCountryCode: 'US',
primaryPhoneCallingCode: '+1',
additionalPhones: [],
},
},
},
{
title:
'create primary phone field with full international and infer other information from it but not the countryCode as its shared',
context: {
input: { primaryPhoneNumber: '+1123456789' },
expected: {
primaryPhoneNumber: '123456789',
primaryPhoneCountryCode: '' as CountryCode,
primaryPhoneCallingCode: '+1',
additionalPhones: [],
},
},
},
{
title:
'create primary phone field with full international and infer other information from it',
context: {
input: { primaryPhoneNumber: '+33123456789' },
expected: {
primaryPhoneNumber: '123456789',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
additionalPhones: [],
},
},
},
{
title: 'create primary phone field with empty payload',
context: {
input: {},
expected: {
primaryPhoneNumber: '',
primaryPhoneCountryCode: '' as CountryCode,
primaryPhoneCallingCode: '',
additionalPhones: [],
},
},
},
{
title: 'create primary phone field with empty strings in payload',
context: {
input: {
primaryPhoneNumber: '',
primaryPhoneCountryCode: '' as CountryCode,
primaryPhoneCallingCode: '',
additionalPhones: null,
},
expected: {
primaryPhoneNumber: '',
primaryPhoneCountryCode: '' as CountryCode,
primaryPhoneCallingCode: '',
additionalPhones: [],
},
},
},
{
title: 'create additional phone field with number and other information',
context: {
input: {
additionalPhones: [
{ callingCode: '+33', countryCode: 'FR', number: '123456789' },
],
},
expected: {
primaryPhoneCallingCode: '',
primaryPhoneCountryCode: '' as CountryCode,
primaryPhoneNumber: '',
additionalPhones: [
{ callingCode: '+33', countryCode: 'FR', number: '123456789' },
],
},
},
},
{
title:
'create additional phone field with full international format and other information',
context: {
input: {
additionalPhones: [
{ callingCode: '+1', countryCode: 'US', number: '+1123456789' },
],
},
expected: {
primaryPhoneCallingCode: '',
primaryPhoneCountryCode: '' as CountryCode,
primaryPhoneNumber: '',
additionalPhones: [
{ callingCode: '+1', countryCode: 'US', number: '123456789' },
],
},
},
},
{
title:
'create additional phone field with full international and infer other information from it but not the countryCode as its shared',
context: {
input: { additionalPhones: [{ number: '+1123456789' }] },
expected: {
primaryPhoneCallingCode: '',
primaryPhoneCountryCode: '' as CountryCode,
primaryPhoneNumber: '',
additionalPhones: [
{
callingCode: '+1',
countryCode: null,
number: '123456789',
},
],
},
},
},
{
title:
'create additional phone field with full international and infer other information from it',
context: {
input: { additionalPhones: [{ number: '+33123456789' }] },
expected: {
primaryPhoneCallingCode: '',
primaryPhoneCountryCode: '' as CountryCode,
primaryPhoneNumber: '',
additionalPhones: [
{ callingCode: '+33', countryCode: 'FR', number: '123456789' },
],
},
},
},
{
title: 'create additional phone field with empty payload',
context: {
input: { additionalPhones: [{}] },
expected: {
primaryPhoneCallingCode: '',
primaryPhoneCountryCode: '' as CountryCode,
primaryPhoneNumber: '',
additionalPhones: [
{ callingCode: null, countryCode: null, number: null },
],
},
},
},
];
describe('successful 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(SUCCESSFUL_TEST_CASES)(
'it should succeed $title',
async ({ context: { input, expected } }) => {
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 {
number
callingCode
countryCode
}
__typename
}
`,
});
expect(errors).toBeUndefined();
const { id: _id, ...rest } = createOneResponse;
expect(rest[FIELD_NAME]).toStrictEqual({
...expected,
__typename: 'Phones',
});
},
);
});

View File

@@ -0,0 +1,151 @@
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: 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: 'testRichTextObject',
namePlural: 'testRichTextObjects',
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,
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);
},
);
});

View File

@@ -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);
},
);
});

View File

@@ -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: null,
addressCity: "'Boston'",
addressState: null,
addressPostcode: null,
addressCountry: null,
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: null,
addressStreet2: null,
addressCity: null,
addressState: null,
addressPostcode: null,
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);
},
);
});

View File

@@ -0,0 +1,173 @@
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: null,
},
},
},
{
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);
},
);
});

View File

@@ -0,0 +1,175 @@
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: 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);
},
);
});

View File

@@ -0,0 +1,162 @@
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: null,
},
},
},
},
{
title: 'full name field default value with empty values',
context: {
input: {
defaultValue: null,
},
},
},
{
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);
},
);
});

View File

@@ -0,0 +1,177 @@
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: 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);
},
);
});

View File

@@ -0,0 +1,179 @@
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: 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);
},
);
});

View File

@@ -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: 'testRichTextUpdateObject',
namePlural: 'testRichTextUpdateObjects',
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,
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);
},
);
});