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,27 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`successful update default value option value and side effect on records should update existing records when default value option value is changed and not mutate when default option is changed 1`] = `
[
{
"color": "green",
"id": Any<String>,
"label": "Option 1",
"position": 0,
"value": "OPTION_1_MODIFIED",
},
{
"color": "blue",
"id": Any<String>,
"label": "Option 2",
"position": 1,
"value": "OPTION_2",
},
{
"color": "red",
"id": Any<String>,
"label": "Option 3",
"position": 2,
"value": "OPTION_3",
},
]
`;

View File

@@ -0,0 +1,49 @@
import { MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/multi-select-operation-agnostic-test-cases';
import { SELECT_OPERATION_AGNOSTIC_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/select-operation-agnostic-test-cases';
import { type FieldMetadataEnumSuccessfulAndFailingTestCases } from 'test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases';
import { type UpdateCreateFieldMetadataSelectTestCase } from 'test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case';
import { type FieldMetadataComplexOption } from 'twenty-shared/types';
import { type EnumFieldMetadataUnionType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
const fieldMetadataTypeAgnosticCreateFailingTestCases: UpdateCreateFieldMetadataSelectTestCase[] =
[
{
title: 'should fail with null options',
context: {
input: {
options: null as unknown as FieldMetadataComplexOption[],
},
},
},
{
title: 'should fail with undefined options',
context: {
input: {
options: undefined as unknown as FieldMetadataComplexOption[],
},
},
},
];
export const CREATE_ENUM_FIELD_METADATA_TEST_CASES: Partial<
Record<
EnumFieldMetadataUnionType,
FieldMetadataEnumSuccessfulAndFailingTestCases
>
> = {
MULTI_SELECT: {
failing: [
...MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES.failing,
...fieldMetadataTypeAgnosticCreateFailingTestCases,
],
successful: [...MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES.successful],
},
SELECT: {
failing: [
...SELECT_OPERATION_AGNOSTIC_TEST_CASES.failing,
...fieldMetadataTypeAgnosticCreateFailingTestCases,
],
successful: [...SELECT_OPERATION_AGNOSTIC_TEST_CASES.successful],
},
};

View File

@@ -0,0 +1,122 @@
import { isDefined } from 'class-validator';
import { BASIC_FAILING_STRING_EDGE_CASE_INPUTS } from 'test/constants/basic-failing-string-edge-case-inputs.constant';
import { SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/select-and-multi-select-operation-agnostic-tests-cases';
import { type FieldMetadataEnumSuccessfulAndFailingTestCases } from 'test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases';
import { type UpdateCreateFieldMetadataSelectTestCase } from 'test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case';
const fuzzedDefaultValueFailingTestCases: UpdateCreateFieldMetadataSelectTestCase[] =
BASIC_FAILING_STRING_EDGE_CASE_INPUTS.filter((el) => isDefined(el.input)).map(
({ input, label }) => ({
title: `should fail with ${label} defaultValue`,
context: {
input: {
defaultValue: [input],
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
}),
);
export const MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES: FieldMetadataEnumSuccessfulAndFailingTestCases =
{
successful: [
...SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES.successful,
{
title: 'should succeed with valid multi-select default values',
context: {
input: {
defaultValue: ["'OPTION_1'", "'OPTION_2'"],
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
{
label: 'Option 2',
value: 'OPTION_2',
color: 'blue',
position: 2,
},
],
},
},
},
],
failing: [
...fuzzedDefaultValueFailingTestCases,
...SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES.failing,
{
title: 'should fail with non stringified array default value',
context: {
input: {
defaultValue: 'Option_1',
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should fail with empty multi-select default values array',
context: {
input: {
defaultValue: [],
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should fail with unknown multi-select default values',
context: {
input: {
defaultValue: ["'OPTION_1'", "'UNKNOWN_OPTION'"],
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should fail with invalid multi-select default value format',
context: {
input: {
defaultValue: ['invalid', 'format'],
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
},
],
};

View File

@@ -0,0 +1,369 @@
import { BASIC_FAILING_STRING_EDGE_CASE_INPUTS } from 'test/constants/basic-failing-string-edge-case-inputs.constant';
import { type FieldMetadataEnumSuccessfulAndFailingTestCases } from 'test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases';
import { type UpdateCreateFieldMetadataSelectTestCase } from 'test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case';
import {
type FieldMetadataComplexOption,
type TagColor,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
const stringFields: (keyof FieldMetadataComplexOption)[] = [
'id',
'label',
'value',
];
const fuzzedOptionsStringFieldFailingTestCases: UpdateCreateFieldMetadataSelectTestCase[] =
stringFields.flatMap((field) => {
return BASIC_FAILING_STRING_EDGE_CASE_INPUTS.map<UpdateCreateFieldMetadataSelectTestCase>(
({ input, label }) => ({
title: `should fail with ${label} ${field}`,
context: {
input: {
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
[field]: input,
},
],
},
},
}),
);
});
export const SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES: FieldMetadataEnumSuccessfulAndFailingTestCases =
{
failing: [
...fuzzedOptionsStringFieldFailingTestCases,
{
title: 'should fail with invalid option id',
context: {
input: {
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
id: 'not a uuid',
},
],
},
},
},
{
title: 'should fail with empty options',
context: {
input: {
options: [],
},
},
},
{
title: 'should fail with invalid option value format',
context: {
input: {
options: [
{
label: 'Option 1',
value: 'Option 1 and some other things, /',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should fail with comma in option label',
context: {
input: {
options: [
{
label: 'Option ,1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should fail with duplicated option values',
context: {
input: {
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 0,
},
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should fail with duplicated option ids',
context: {
input: {
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
id: 'fd1f11fd-3f05-4a33-bddf-800c3412ce98',
},
{
label: 'Option 2',
value: 'OPTION_2',
color: 'green',
position: 2,
id: 'fd1f11fd-3f05-4a33-bddf-800c3412ce98',
},
],
},
},
},
{
title: 'should fail with duplicated option positions',
context: {
input: {
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
{
label: 'Option 2',
value: 'OPTION_2',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should fail with duplicated trimmed option values',
context: {
input: {
options: [
{
label: 'Option 1',
value: ' OPTION_1 ',
color: 'green',
position: 1,
},
{
label: 'Option 2',
value: ' OPTION_1 ',
color: 'green',
position: 2,
},
],
},
},
},
{
title: 'should fail with undefined option label',
context: {
input: {
options: [
{
label: undefined as unknown as string,
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should fail with an invalid default value',
context: {
input: {
defaultValue: 'invalid',
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should fail with an unknown default value',
context: {
input: {
defaultValue: "'OPTION_424242'",
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should fail with undefined option value',
context: {
input: {
options: [
{
label: 'Option 1',
value: undefined as unknown as string,
color: 'green',
position: 1,
},
],
},
},
},
],
successful: [
{
title: 'should succeed with provided option id',
context: {
input: {
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
id: '26c602c3-cba9-4d83-92d4-4ba7dbae2f31',
},
],
},
},
},
{
title: 'should succeed with various options id',
context: {
input: {
options: Array.from({ length: 42 }, (_value, index) => {
const optionWithoutId: FieldMetadataComplexOption = {
label: `Option ${index}`,
value: `OPTION_${index}`,
color: 'green',
position: index,
};
if (index % 2 === 0) {
return {
...optionWithoutId,
id: v4(),
};
}
return optionWithoutId;
}),
},
},
},
{
title: 'should succeed without option id',
context: {
input: {
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
},
{
title: 'should trim option values',
context: {
input: {
options: [
{
label: ' Option 1 ',
value: ' OPTION_1 ',
color: 'green',
position: 1,
},
],
},
expectedOptions: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
{
title: 'should default a missing option color to gray',
context: {
input: {
options: [
{
label: 'Option 1',
value: 'OPTION_1',
position: 1,
},
],
},
expectedOptions: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'gray',
position: 1,
},
],
},
},
{
title: 'should trim option color',
context: {
input: {
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: ' blue ' as TagColor,
position: 1,
},
],
},
expectedOptions: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'blue',
position: 1,
},
],
},
},
],
};

View File

@@ -0,0 +1,70 @@
import { isDefined } from 'class-validator';
import { BASIC_FAILING_STRING_EDGE_CASE_INPUTS } from 'test/constants/basic-failing-string-edge-case-inputs.constant';
import { SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/select-and-multi-select-operation-agnostic-tests-cases';
import { type FieldMetadataEnumSuccessfulAndFailingTestCases } from 'test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases';
import { type UpdateCreateFieldMetadataSelectTestCase } from 'test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case';
const fuzzedDefaultValueFailingTestCases: UpdateCreateFieldMetadataSelectTestCase[] =
BASIC_FAILING_STRING_EDGE_CASE_INPUTS.filter((el) => isDefined(el.input)).map(
({ input, label }) => ({
title: `should fail with ${label} defaultValue`,
context: {
input: {
defaultValue: input,
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
],
},
},
}),
);
export const SELECT_OPERATION_AGNOSTIC_TEST_CASES: FieldMetadataEnumSuccessfulAndFailingTestCases =
{
failing: [
...fuzzedDefaultValueFailingTestCases,
...SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES.failing,
],
successful: [
...SELECT_AND_MULTI_SELECT_OPERATION_AGNOSTIC_SUCCESSFUL_AND_FAILING_TEST_CASES.successful,
{
title: 'should succeed with valid default value',
context: {
input: {
defaultValue: "'OPTION_1'",
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
id: '26c602c3-cba9-4d83-92d4-4ba7dbae2f31',
},
],
},
},
},
{
title: 'should succeed with null default value',
context: {
input: {
defaultValue: null,
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
id: '26c602c3-cba9-4d83-92d4-4ba7dbae2f31',
},
],
},
},
},
],
};

View File

@@ -0,0 +1,94 @@
import { MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/multi-select-operation-agnostic-test-cases';
import { SELECT_OPERATION_AGNOSTIC_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/select-operation-agnostic-test-cases';
import { type FieldMetadataEnumSuccessfulAndFailingTestCases } from 'test/integration/metadata/suites/field-metadata/enum/types/fieldMetadataEnumSuccessfulAndFailingTestCases';
import { type FieldMetadataComplexOption } from 'twenty-shared/types';
import { type EnumFieldMetadataUnionType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
export const UPDATE_ENUM_FIELD_METADATA_TEST_CASES: Partial<
Record<
EnumFieldMetadataUnionType,
FieldMetadataEnumSuccessfulAndFailingTestCases
>
> = {
MULTI_SELECT: {
failing: [
...MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES.failing,
{
title: 'should fail with unknown default value and no options',
context: {
input: {
defaultValue: ["'OPTION_42'"],
options: undefined as unknown as FieldMetadataComplexOption[],
},
},
},
],
successful: [
...MUTLI_SELECT_OPERATION_AGNOSTIC_TEST_CASES.successful,
{
title: 'should succeed with default value and no options',
context: {
input: {
defaultValue: ["'OPTION_2'"],
options: undefined as unknown as FieldMetadataComplexOption[],
},
expectedOptions: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
{
label: 'Option 2',
value: 'OPTION_2',
color: 'green',
position: 2,
},
],
},
},
],
},
SELECT: {
failing: [
...SELECT_OPERATION_AGNOSTIC_TEST_CASES.failing,
{
title: 'should fail with unknown default value and no options',
context: {
input: {
defaultValue: "'OPTION_42'",
options: undefined as unknown as FieldMetadataComplexOption[],
},
},
},
],
successful: [
...SELECT_OPERATION_AGNOSTIC_TEST_CASES.successful,
{
title: 'should succeed with default value and no options',
context: {
input: {
defaultValue: "'OPTION_2'",
options: undefined as unknown as FieldMetadataComplexOption[],
},
expectedOptions: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
{
label: 'Option 2',
value: 'OPTION_2',
color: 'green',
position: 2,
},
],
},
},
],
},
};

View File

@@ -0,0 +1,92 @@
import { CREATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/create-enum-field-metadata-test-cases';
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
import { CUSTOM_OBJECT_DISHES } from 'test/integration/metadata/suites/object-metadata/constants/custom-object-dishes.constants';
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 { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import { eachTestingContextFilter } from 'twenty-shared/testing';
import { isDefined } from 'twenty-shared/utils';
import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
describe.each(fieldMetadataEnumTypes)(
'Failing create field metadata %s tests suite v2',
(testedFieldMetadataType) => {
let createdObjectMetadataId: string;
const testCases =
CREATE_ENUM_FIELD_METADATA_TEST_CASES[testedFieldMetadataType];
if (!isDefined(testCases)) {
return;
}
const { failing: failingTestCases } = testCases;
beforeAll(async () => {
const {
labelPlural,
description,
labelSingular,
namePlural,
nameSingular,
} = CUSTOM_OBJECT_DISHES;
const { data } = await createOneObjectMetadata({
expectToFail: false,
input: {
labelSingular,
labelPlural,
nameSingular,
description,
namePlural,
icon: 'IconBuildingSkyscraper',
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 },
});
});
test.each(eachTestingContextFilter(failingTestCases))(
'Create $title',
async ({ context: { input } }) => {
const { data, errors } = await createOneFieldMetadata({
expectToFail: true,
input: {
objectMetadataId: createdObjectMetadataId,
type: testedFieldMetadataType,
name: 'testField',
label: 'Test Field',
isLabelSyncedWithName: false,
...input,
},
gqlFields: `
id
options
defaultValue
`,
});
expect(data).toBeNull();
expect(errors).toBeDefined();
expect(errors).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(errors),
);
expect(errors[0].extensions.code).not.toBe('INTERNAL_SERVER_ERROR');
},
);
},
);

View File

@@ -0,0 +1,126 @@
import { UPDATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/update-enum-field-metadata-test-cases';
import { type CreateOneFieldFactoryInput } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-query-factory.util';
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
import { CUSTOM_OBJECT_DISHES } from 'test/integration/metadata/suites/object-metadata/constants/custom-object-dishes.constants';
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 { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import { eachTestingContextFilter } from 'twenty-shared/testing';
import { isDefined } from 'twenty-shared/utils';
import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
describe.each(fieldMetadataEnumTypes)(
'Failing update field metadata %s tests suite v2',
(testedFieldMetadataType) => {
let createdObjectMetadataId: string;
let createdFieldMetadataId: string;
const testCases =
UPDATE_ENUM_FIELD_METADATA_TEST_CASES[testedFieldMetadataType];
if (!isDefined(testCases)) {
return;
}
const { failing: failingTestCases } = testCases;
const initialOptions: CreateOneFieldFactoryInput['options'] = [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
{
label: 'Option 2',
value: 'OPTION_2',
color: 'green',
position: 2,
},
];
beforeAll(async () => {
const {
labelPlural,
description,
labelSingular,
namePlural,
nameSingular,
} = CUSTOM_OBJECT_DISHES;
const { data } = await createOneObjectMetadata({
input: {
labelSingular,
labelPlural,
nameSingular,
description,
namePlural,
icon: 'IconBuildingSkyscraper',
isLabelSyncedWithName: false,
},
});
createdObjectMetadataId = data.createOneObject.id;
const {
data: { createOneField },
} = await createOneFieldMetadata({
input: {
objectMetadataId: createdObjectMetadataId,
type: testedFieldMetadataType,
name: 'testField',
label: 'Test Field',
isLabelSyncedWithName: false,
options: initialOptions,
},
gqlFields: `
id
type
`,
});
createdFieldMetadataId = createOneField.id;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: createdObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
input: {
idToDelete: createdObjectMetadataId,
},
expectToFail: false,
});
});
test.each(eachTestingContextFilter(failingTestCases))(
'Update $title',
async ({ context: { input } }) => {
const { data, errors } = await updateOneFieldMetadata({
input: {
idToUpdate: createdFieldMetadataId,
updatePayload: input,
},
gqlFields: `
id
options
`,
});
expect(data).toBeNull();
expect(errors).toBeDefined();
expect(errors).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(errors),
);
expect(errors[0].extensions.code).not.toBe('INTERNAL_SERVER_ERROR');
},
);
},
);

View File

@@ -0,0 +1,130 @@
import { CREATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/create-enum-field-metadata-test-cases';
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 { CUSTOM_OBJECT_DISHES } from 'test/integration/metadata/suites/object-metadata/constants/custom-object-dishes.constants';
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 { eachTestingContextFilter } from 'twenty-shared/testing';
import { isDefined } from 'twenty-shared/utils';
import { fieldMetadataEnumTypes } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
describe.each(fieldMetadataEnumTypes)(
'Successful create field metadata %s tests suite v2',
(testedFieldMetadataType) => {
let createdObjectMetadataId: string;
let createdFieldMetadataId: string | undefined = undefined;
const testCases =
CREATE_ENUM_FIELD_METADATA_TEST_CASES[testedFieldMetadataType];
if (!isDefined(testCases)) {
return;
}
const { successful: successfulTestCases } = testCases;
beforeAll(async () => {
const {
labelPlural,
description,
labelSingular,
namePlural,
nameSingular,
} = CUSTOM_OBJECT_DISHES;
const { data } = await createOneObjectMetadata({
expectToFail: false,
input: {
labelPlural,
description,
labelSingular,
namePlural,
nameSingular,
icon: 'IconBuildingSkyscraper',
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(() => {
createdFieldMetadataId = undefined;
});
afterEach(async () => {
if (!isDefined(createdFieldMetadataId)) {
return;
}
await updateOneFieldMetadata({
input: {
updatePayload: {
isActive: false,
},
idToUpdate: createdFieldMetadataId,
},
expectToFail: false,
});
await deleteOneFieldMetadata({
input: {
idToDelete: createdFieldMetadataId,
},
expectToFail: false,
});
});
test.each(eachTestingContextFilter(successfulTestCases))(
'Create $title',
async ({ context: { input, expectedOptions } }) => {
const { data, errors } = await createOneFieldMetadata<
typeof testedFieldMetadataType
>({
expectToFail: false,
input: {
objectMetadataId: createdObjectMetadataId,
type: testedFieldMetadataType,
name: 'testField',
label: 'Test Field',
isLabelSyncedWithName: false,
...input,
},
gqlFields: `
id
options
defaultValue
type
`,
});
expect(data).not.toBeNull();
expect(data.createOneField).toBeDefined();
expect(data.createOneField.type).toEqual(testedFieldMetadataType);
createdFieldMetadataId = data.createOneField.id;
const createdOptions = data.createOneField.options;
const optionsToCompare = expectedOptions ?? input.options ?? [];
expect(errors).toBeUndefined();
expect(createdOptions?.length).toBe(optionsToCompare.length);
createdOptions?.forEach((option) => expect(option.id).toBeDefined());
expect(createdOptions).toMatchObject(optionsToCompare);
if (isDefined(input.defaultValue)) {
expect(data.createOneField.defaultValue).toEqual(input.defaultValue);
}
},
);
},
);

View File

@@ -0,0 +1,211 @@
import { createOneOperation } from 'test/integration/graphql/utils/create-one-operation.util';
import { findOneOperation } from 'test/integration/graphql/utils/find-one-operation.util';
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-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 { FieldMetadataType } from 'twenty-shared/types';
const TEST_OBJECT_NAME_SINGULAR = 'testMultiSelectRemoval';
const TEST_OBJECT_NAME_PLURAL = 'testMultiSelectRemovals';
// Reproduces the bug where removing a multi-select option fails when
// existing records contain both the removed option and surviving options.
describe('remove multi-select option with side effect on records', () => {
let createdObjectMetadataId: string;
let createdFieldMetadataId: string;
let firstOptionId: string;
let thirdOptionId: string;
beforeAll(async () => {
const { data: objectData } = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: TEST_OBJECT_NAME_SINGULAR,
namePlural: TEST_OBJECT_NAME_PLURAL,
labelSingular: 'Test Multi Select Removal',
labelPlural: 'Test Multi Select Removals',
icon: 'IconTestPipe',
isLabelSyncedWithName: false,
},
});
createdObjectMetadataId = objectData.createOneObject.id;
const { data: fieldData } = await createOneFieldMetadata({
expectToFail: false,
input: {
objectMetadataId: createdObjectMetadataId,
type: FieldMetadataType.MULTI_SELECT,
name: 'tagField',
label: 'Tag Field',
isLabelSyncedWithName: false,
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 0,
},
{
label: 'Option 2',
value: 'OPTION_2',
color: 'blue',
position: 1,
},
{
label: 'Option 3',
value: 'OPTION_3',
color: 'red',
position: 2,
},
],
},
gqlFields: `
id
options
`,
});
createdFieldMetadataId = fieldData.createOneField.id;
const options = fieldData.createOneField.options;
if (!options) {
throw new Error('Options should be defined');
}
const option1 = options.find(
(opt: { value: string }) => opt.value === 'OPTION_1',
);
const option2 = options.find(
(opt: { value: string }) => opt.value === 'OPTION_2',
);
const option3 = options.find(
(opt: { value: string }) => opt.value === 'OPTION_3',
);
if (!option1?.id || !option2?.id || !option3?.id) {
throw new Error('All options and their IDs should be defined');
}
firstOptionId = option1.id;
thirdOptionId = option3.id;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: createdObjectMetadataId,
updatePayload: { isActive: false },
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: createdObjectMetadataId },
});
});
it('should strip the removed option from existing records that contain both surviving and removed values', async () => {
// Record with OPTION_1 + OPTION_2 (OPTION_2 will be removed)
const { data: mixedRecordData } = await createOneOperation({
expectToFail: false,
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
input: { tagField: ['OPTION_1', 'OPTION_2'] },
gqlFields: `id tagField`,
});
const mixedRecordId = mixedRecordData.createOneResponse.id;
expect(mixedRecordData.createOneResponse.tagField).toEqual([
'OPTION_1',
'OPTION_2',
]);
// Record with only OPTION_2 (the option that will be removed)
const { data: removedOnlyData } = await createOneOperation({
expectToFail: false,
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
input: { tagField: ['OPTION_2'] },
gqlFields: `id tagField`,
});
const removedOnlyRecordId = removedOnlyData.createOneResponse.id;
const { data: survivingData } = await createOneOperation({
expectToFail: false,
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
input: { tagField: ['OPTION_1', 'OPTION_3'] },
gqlFields: `id tagField`,
});
const survivingRecordId = survivingData.createOneResponse.id;
const { data: updateData, errors: updateErrors } =
await updateOneFieldMetadata({
expectToFail: false,
input: {
idToUpdate: createdFieldMetadataId,
updatePayload: {
options: [
{
id: firstOptionId,
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 0,
},
{
id: thirdOptionId,
label: 'Option 3',
value: 'OPTION_3',
color: 'red',
position: 1,
},
],
},
},
gqlFields: `id options`,
});
expect(updateErrors).toBeUndefined();
expect(updateData.updateOneField.options).toHaveLength(2);
// Mixed record: OPTION_2 should be stripped, only OPTION_1 remains
const { data: mixedAfter } = await findOneOperation({
expectToFail: false,
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
gqlFields: `id tagField`,
filter: { id: { eq: mixedRecordId } },
});
expect(mixedAfter.findResponse.tagField).toEqual(['OPTION_1']);
// Removed-only record: all values were removed, field is empty
const { data: removedOnlyAfter } = await findOneOperation({
expectToFail: false,
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
gqlFields: `id tagField`,
filter: { id: { eq: removedOnlyRecordId } },
});
const removedOnlyTagField = removedOnlyAfter.findResponse.tagField;
expect(
removedOnlyTagField === null || removedOnlyTagField.length === 0,
).toBe(true);
const { data: survivingAfter } = await findOneOperation({
expectToFail: false,
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
gqlFields: `id tagField`,
filter: { id: { eq: survivingRecordId } },
});
expect(survivingAfter.findResponse.tagField).toEqual([
'OPTION_1',
'OPTION_3',
]);
});
});

View File

@@ -0,0 +1,237 @@
import { createOneOperation } from 'test/integration/graphql/utils/create-one-operation.util';
import { findOneOperation } from 'test/integration/graphql/utils/find-one-operation.util';
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-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 { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import { FieldMetadataType } from 'twenty-shared/types';
const TEST_OBJECT_NAME_SINGULAR = 'testSelectObject';
const TEST_OBJECT_NAME_PLURAL = 'testSelectObjects';
describe('successful update default value option value and side effect on records', () => {
let createdObjectMetadataId: string;
let createdFieldMetadataId: string;
let firstOptionId: string;
let secondOptionId: string;
let thirdOptionId: string;
beforeAll(async () => {
const { data: objectData } = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: TEST_OBJECT_NAME_SINGULAR,
namePlural: TEST_OBJECT_NAME_PLURAL,
labelSingular: 'Test Select Object',
labelPlural: 'Test Select Objects',
icon: 'IconTestPipe',
isLabelSyncedWithName: false,
},
});
createdObjectMetadataId = objectData.createOneObject.id;
const { data: fieldData } = await createOneFieldMetadata({
expectToFail: false,
input: {
objectMetadataId: createdObjectMetadataId,
type: FieldMetadataType.SELECT,
name: 'statusField',
label: 'Status Field',
isLabelSyncedWithName: false,
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 0,
},
{
label: 'Option 2',
value: 'OPTION_2',
color: 'blue',
position: 1,
},
{
label: 'Option 3',
value: 'OPTION_3',
color: 'red',
position: 2,
},
],
defaultValue: "'OPTION_1'",
},
gqlFields: `
id
options
defaultValue
`,
});
createdFieldMetadataId = fieldData.createOneField.id;
const options = fieldData.createOneField.options;
if (!options) {
throw new Error('Options should be defined');
}
const option1 = options.find((opt) => opt.value === 'OPTION_1');
const option2 = options.find((opt) => opt.value === 'OPTION_2');
const option3 = options.find((opt) => opt.value === 'OPTION_3');
if (!option1?.id || !option2?.id || !option3?.id) {
throw new Error('All options and their IDs should be defined');
}
firstOptionId = option1.id;
secondOptionId = option2.id;
thirdOptionId = option3.id;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: createdObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: createdObjectMetadataId },
});
});
it('should update existing records when default value option value is changed and not mutate when default option is changed', async () => {
const { data: createFirstRecordData } = await createOneOperation({
expectToFail: false,
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
input: {},
gqlFields: `
id
statusField
`,
});
const firstRecordId = createFirstRecordData.createOneResponse.id;
expect(createFirstRecordData.createOneResponse.statusField).toBe(
'OPTION_1',
);
const { data: updateOptionValueData } = await updateOneFieldMetadata({
expectToFail: false,
input: {
idToUpdate: createdFieldMetadataId,
updatePayload: {
defaultValue: "'OPTION_1_MODIFIED'",
options: [
{
id: firstOptionId,
label: 'Option 1',
value: 'OPTION_1_MODIFIED',
color: 'green',
position: 0,
},
{
id: secondOptionId,
label: 'Option 2',
value: 'OPTION_2',
color: 'blue',
position: 1,
},
{
id: thirdOptionId,
label: 'Option 3',
value: 'OPTION_3',
color: 'red',
position: 2,
},
],
},
},
gqlFields: `
id
options
defaultValue
`,
});
expect(updateOptionValueData.updateOneField.defaultValue).toBe(
"'OPTION_1_MODIFIED'",
);
const { data: queryFirstRecordAfterOptionValueUpdate } =
await findOneOperation({
expectToFail: false,
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
gqlFields: `
id
statusField
`,
filter: { id: { eq: firstRecordId } },
});
expect(
queryFirstRecordAfterOptionValueUpdate.findResponse.statusField,
).toBe('OPTION_1_MODIFIED');
const { data: updateDefaultOptionData } = await updateOneFieldMetadata({
expectToFail: false,
input: {
idToUpdate: createdFieldMetadataId,
updatePayload: {
defaultValue: "'OPTION_2'",
},
},
gqlFields: `
id
options
defaultValue
`,
});
expect(updateDefaultOptionData.updateOneField.defaultValue).toBe(
"'OPTION_2'",
);
expect(updateDefaultOptionData.updateOneField.options).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(
updateDefaultOptionData.updateOneField.options as any,
),
);
const { data: createSecondRecordData } = await createOneOperation({
expectToFail: false,
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
input: {},
gqlFields: `
id
statusField
`,
});
expect(createSecondRecordData.createOneResponse.statusField).toBe(
'OPTION_2',
);
const { data: queryFirstRecordAfterDefaultChange } = await findOneOperation(
{
expectToFail: false,
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
gqlFields: `
id
statusField
`,
filter: { id: { eq: firstRecordId } },
},
);
expect(queryFirstRecordAfterDefaultChange.findResponse.statusField).toBe(
'OPTION_1_MODIFIED',
);
});
});

View File

@@ -0,0 +1,213 @@
import { UPDATE_ENUM_FIELD_METADATA_TEST_CASES } from 'test/integration/metadata/suites/field-metadata/enum/common/update-enum-field-metadata-test-cases';
import { type CreateOneFieldFactoryInput } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-query-factory.util';
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 { CUSTOM_OBJECT_DISHES } from 'test/integration/metadata/suites/object-metadata/constants/custom-object-dishes.constants';
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 { eachTestingContextFilter } from 'twenty-shared/testing';
import {
type FieldMetadataComplexOption,
type FieldMetadataDefaultOption,
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';
describe.each(fieldMetadataEnumTypes)(
'Successful update field metadata %s tests suite v2',
(testedFieldMetadataType) => {
let createdObjectMetadataId: string;
let createdFieldMetadataId: string;
const testCases =
UPDATE_ENUM_FIELD_METADATA_TEST_CASES[testedFieldMetadataType];
if (!isDefined(testCases)) {
return;
}
const { successful: successfulTestCases } = testCases;
const initialOptions: CreateOneFieldFactoryInput['options'] = [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'green',
position: 1,
},
{
label: 'Option 2',
value: 'OPTION_2',
color: 'green',
position: 2,
},
];
beforeAll(async () => {
const {
labelPlural,
description,
labelSingular,
namePlural,
nameSingular,
} = CUSTOM_OBJECT_DISHES;
const { data } = await createOneObjectMetadata({
input: {
labelPlural,
description,
labelSingular,
namePlural,
nameSingular,
icon: 'IconBuildingSkyscraper',
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: testedFieldMetadataType,
name: 'testField',
label: 'Test Field',
isLabelSyncedWithName: false,
options: initialOptions,
},
gqlFields: `
id
type
`,
});
createdFieldMetadataId = createOneField.id;
});
afterEach(async () => {
await updateOneFieldMetadata({
expectToFail: false,
input: {
idToUpdate: createdFieldMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneFieldMetadata({
expectToFail: false,
input: { idToDelete: createdFieldMetadataId },
});
});
it('Should update default value to null even if it was set before', async () => {
const isMultiSelect =
testedFieldMetadataType === FieldMetadataType.MULTI_SELECT;
const rawDefaultValue = `'${initialOptions[0].value}'`;
const expectedDefaultValue = isMultiSelect
? [rawDefaultValue]
: rawDefaultValue;
const { data: firstUpdate } = await updateOneFieldMetadata({
expectToFail: false,
input: {
idToUpdate: createdFieldMetadataId,
updatePayload: {
defaultValue: expectedDefaultValue,
},
},
gqlFields: `
id
defaultValue
`,
});
expect(firstUpdate.updateOneField.defaultValue).toEqual(
expectedDefaultValue,
);
const updatedOptions = initialOptions.slice(1);
const { data: secondUpdate, errors } = await updateOneFieldMetadata({
expectToFail: false,
input: {
idToUpdate: createdFieldMetadataId,
updatePayload: {
defaultValue: null,
options: updatedOptions,
},
},
gqlFields: `
id
options
defaultValue
`,
});
expect(errors).toBeUndefined();
expect(secondUpdate.updateOneField.defaultValue).toBeNull();
expect(secondUpdate.updateOneField.options).toMatchObject(updatedOptions);
});
test.each(eachTestingContextFilter(successfulTestCases))(
'Update $title',
async ({ context: { input, expectedOptions } }) => {
const { ...updatePayload } = input;
const { data, errors } = await updateOneFieldMetadata({
input: {
idToUpdate: createdFieldMetadataId,
updatePayload,
},
gqlFields: `
id
options
defaultValue
`,
});
expect(data.updateOneField).toBeDefined();
const updatedOptions:
| FieldMetadataComplexOption[]
| FieldMetadataDefaultOption[]
| null = data.updateOneField.options;
expect(updatedOptions).toBeDefined();
if (!isDefined(updatedOptions))
throw new Error(
'Should never occur, type invariant post test assertion',
);
expect(errors).toBeUndefined();
updatedOptions.forEach((option) => expect(option.id).toBeDefined());
const optionsToCompare = expectedOptions ?? input.options ?? [];
expect(updatedOptions.length).toBe(optionsToCompare.length);
expect(updatedOptions).toMatchObject(optionsToCompare);
if (isDefined(input.defaultValue)) {
expect(data.updateOneField.defaultValue).toEqual(input.defaultValue);
}
},
);
},
);

View File

@@ -0,0 +1,7 @@
import { type UpdateCreateFieldMetadataSelectTestCase } from 'test/integration/metadata/suites/field-metadata/enum/types/update-create-field-metadata-enum-test-case';
import { type SuccessfulAndFailingTestCases } from 'twenty-shared/testing';
export type FieldMetadataEnumSuccessfulAndFailingTestCases =
SuccessfulAndFailingTestCases<
UpdateCreateFieldMetadataSelectTestCase['context']
>;

View File

@@ -0,0 +1,9 @@
import { type CreateOneFieldFactoryInput } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-query-factory.util';
import { type EachTestingContext } from 'twenty-shared/testing';
import { type FieldMetadataComplexOption } from 'twenty-shared/types';
export type UpdateCreateFieldMetadataSelectTestCase = EachTestingContext<{
input: Partial<Pick<CreateOneFieldFactoryInput, 'defaultValue'>> &
Required<Pick<CreateOneFieldFactoryInput, 'options'>>;
expectedOptions?: FieldMetadataComplexOption[];
}>;