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 2s

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,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Delete core view should throw an error when deleting non-existent view 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View to delete not found",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Destroy core view should throw an error when destroying non-existent view 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View to destroy not found",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,237 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`successful find view with all sub-relations (e2e) Company View Structure Validation should successfully fetch complete view structure with all sub-relations in single GraphQL query 1`] = `
{
"anyFieldFilterValue": null,
"createdAt": Any<String>,
"deletedAt": null,
"icon": "IconTable",
"id": Any<String>,
"isCompact": false,
"key": "INDEX",
"mainGroupByFieldMetadataId": null,
"name": "All Companies",
"objectMetadataId": Any<String>,
"openRecordIn": "SIDE_PANEL",
"position": 0,
"type": "TABLE",
"updatedAt": Any<String>,
"viewFields": [
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 0,
"size": 180,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 1,
"size": 100,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 2,
"size": 150,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 3,
"size": 150,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 4,
"size": 150,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 5,
"size": 170,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 6,
"size": 170,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 7,
"size": 180,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 8,
"size": 180,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 9,
"size": 180,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 10,
"size": 180,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 11,
"size": 180,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 12,
"size": 180,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isSystemSideEffect": true,
"isVisible": true,
"position": 13,
"size": 180,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
],
"viewFilterGroups": [],
"viewFilters": [],
"viewGroups": [],
"viewSorts": [],
"workspaceId": Any<String>,
}
`;

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Update core view should throw error when updating non-existent view 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View to update not found",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`View Filter Resolver createViewFilter should reject a view filter with an operand incompatible with the field type 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"viewFilter": [
{
"errors": [
{
"code": "INVALID_VIEW_FILTER_DATA",
"message": "Operand "IS" is not supported on field type "TEXT". Supported operands: CONTAINS, DOES_NOT_CONTAIN, IS_EMPTY, IS_NOT_EMPTY.",
"userFriendlyMessage": "Filter operand is not supported for this field type",
},
],
"flatEntityMinimalInformation": {
"universalIdentifier": Any<String>,
},
"metadataName": "viewFilter",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 viewFilter",
"summary": {
"totalErrors": 1,
"viewFilter": 1,
},
"userFriendlyMessage": "Filter operand is not supported for this field type",
},
"message": "Multiple validation errors occurred while creating view filter",
"name": "GraphQLError",
}
`;
exports[`View Filter Resolver deleteViewFilter should throw an error when deleting non-existent view filter 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View filter to delete not found",
"name": "NotFoundError",
}
`;
exports[`View Filter Resolver destroyViewFilter should throw an error when destroying non-existent view filter 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View filter to destroy not found",
"name": "NotFoundError",
}
`;
exports[`View Filter Resolver updateViewFilter should throw an error when updating non-existent view filter 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View filter to update not found",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,34 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Group Resolver deleteViewGroup should throw an error when deleting non-existent view group 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View group to delete not found",
"name": "NotFoundError",
}
`;
exports[`View Group Resolver destroyViewGroup should throw an error when destroying non-existent view group 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View group to destroy not found",
"name": "NotFoundError",
}
`;
exports[`View Group Resolver updateViewGroup should throw an error when updating non-existent view group 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View group to update not found",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,111 @@
import { createOneSelectFieldMetadataForIntegrationTests } from 'test/integration/metadata/suites/field-metadata/utils/create-one-select-field-metadata-for-integration-tests.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 { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { assertViewStructure } from 'test/integration/utils/view-test.util';
import { ViewOpenRecordIn, ViewType } from 'twenty-shared/types';
describe('Create core view', () => {
let testObjectMetadataId: string;
let testSelectFieldMetadataId: string;
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myViewTestObject',
namePlural: 'myViewTestObjects',
labelSingular: 'My View Test Object',
labelPlural: 'My View Test Objects',
icon: 'Icon123',
},
});
testObjectMetadataId = objectMetadataId;
const { selectFieldMetadataId } =
await createOneSelectFieldMetadataForIntegrationTests({
input: {
objectMetadataId,
},
});
testSelectFieldMetadataId = selectFieldMetadataId;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testObjectMetadataId },
});
});
it('should create a new view with all properties', async () => {
const { data, errors } = await createOneView({
input: {
name: 'Kanban View',
objectMetadataId: testObjectMetadataId,
icon: 'IconDeal',
type: ViewType.KANBAN,
mainGroupByFieldMetadataId: testSelectFieldMetadataId,
position: 1,
isCompact: true,
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
},
expectToFail: false,
});
expect(errors).toBeUndefined();
assertViewStructure(data.createView, {
name: 'Kanban View',
objectMetadataId: testObjectMetadataId,
mainGroupByFieldMetadataId: testSelectFieldMetadataId,
type: ViewType.KANBAN,
key: null,
icon: 'IconDeal',
position: 1,
isCompact: true,
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
});
});
it('should create a view with minimum required fields', async () => {
const input = {
name: 'Minimal View',
objectMetadataId: testObjectMetadataId,
icon: 'IconList',
};
const { data, errors } = await createOneView({
input,
expectToFail: false,
});
expect(errors).toBeUndefined();
assertViewStructure(data.createView, {
name: input.name,
objectMetadataId: input.objectMetadataId,
icon: input.icon,
type: ViewType.TABLE,
mainGroupByFieldMetadataId: null,
key: null,
position: 0,
isCompact: false,
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
});
});
});

View File

@@ -0,0 +1,85 @@
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.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 { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { deleteOneView } from 'test/integration/metadata/suites/view/utils/delete-one-view.util';
import { findOneView } from 'test/integration/metadata/suites/view/utils/find-one-view.util';
const TEST_NOT_EXISTING_VIEW_ID = '20202020-0000-4000-8000-000000000000';
describe('Delete core view', () => {
let testObjectMetadataId: string;
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myViewTestObject',
namePlural: 'myViewTestObjects',
labelSingular: 'My View Test Object',
labelPlural: 'My View Test Objects',
icon: 'Icon123',
},
});
testObjectMetadataId = objectMetadataId;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testObjectMetadataId },
});
});
it('should delete an existing view', async () => {
const {
data: { createView: view },
} = await createOneView({
input: {
name: 'View to Delete',
objectMetadataId: testObjectMetadataId,
icon: '123Icon',
},
expectToFail: false,
});
const { data: deleteData, errors: deleteErrors } = await deleteOneView({
viewId: view.id,
expectToFail: false,
});
expect(deleteErrors).toBeUndefined();
expect(deleteData.deleteView).toBe(true);
const { data: getData } = await findOneView({
viewId: view.id,
expectToFail: false,
});
expect(getData.getView).toBeNull();
});
it('should throw an error when deleting non-existent view', async () => {
const { errors } = await deleteOneView({
viewId: TEST_NOT_EXISTING_VIEW_ID,
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});

View File

@@ -0,0 +1,94 @@
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.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 { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { deleteOneView } from 'test/integration/metadata/suites/view/utils/delete-one-view.util';
import { destroyOneView } from 'test/integration/metadata/suites/view/utils/destroy-one-view.util';
import { findOneView } from 'test/integration/metadata/suites/view/utils/find-one-view.util';
const TEST_NOT_EXISTING_VIEW_ID = '20202020-0000-4000-8000-000000000000';
describe('Destroy core view', () => {
let testObjectMetadataId: string;
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myViewTestObject',
namePlural: 'myViewTestObjects',
labelSingular: 'My View Test Object',
labelPlural: 'My View Test Objects',
icon: 'Icon123',
},
});
testObjectMetadataId = objectMetadataId;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testObjectMetadataId },
});
});
it('should destroy an existing view', async () => {
const {
data: { createView: view },
} = await createOneView({
input: {
icon: '123Icon',
name: 'View to Destroy',
objectMetadataId: testObjectMetadataId,
},
expectToFail: false,
});
const { data: deleteData, errors: deleteErrors } = await deleteOneView({
viewId: view.id,
expectToFail: false,
});
expect(deleteErrors).toBeUndefined();
expect(deleteData.deleteView).toBe(true);
const { data: destroyData, errors: destroyErrors } = await destroyOneView({
viewId: view.id,
expectToFail: false,
});
expect(destroyErrors).toBeUndefined();
expect(destroyData.destroyView).toBe(true);
const { data: getData } = await findOneView({
viewId: view.id,
expectToFail: false,
});
expect(getData.getView).toBeNull();
});
it('should throw an error when destroying non-existent view', async () => {
const { errors } = await destroyOneView({
viewId: TEST_NOT_EXISTING_VIEW_ID,
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});

View File

@@ -0,0 +1,87 @@
import {
VIEW_FIELD_GQL_FIELDS,
VIEW_FILTER_GQL_FIELDS,
VIEW_FILTER_GROUP_GQL_FIELDS,
VIEW_GQL_FIELDS,
VIEW_GROUP_GQL_FIELDS,
VIEW_SORT_GQL_FIELDS,
} from 'test/integration/constants/view-gql-fields.constants';
import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
import { findViews } from 'test/integration/metadata/suites/view/utils/find-views.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
import { ViewKey } from 'twenty-shared/types';
describe('successful find view with all sub-relations (e2e)', () => {
let companyObjectMetadataId: string;
const COMPREHENSIVE_VIEW_GQL_FIELDS = `
${VIEW_GQL_FIELDS}
viewFields {
${VIEW_FIELD_GQL_FIELDS}
}
viewFilters {
${VIEW_FILTER_GQL_FIELDS}
}
viewGroups {
${VIEW_GROUP_GQL_FIELDS}
}
viewSorts {
${VIEW_SORT_GQL_FIELDS}
}
viewFilterGroups {
${VIEW_FILTER_GROUP_GQL_FIELDS}
}
`;
beforeAll(async () => {
const { objects } = await findManyObjectMetadata({
expectToFail: false,
input: {
filter: {},
paging: {
first: 1000,
},
},
gqlFields: `
id
nameSingular
`,
});
const companyObject = objects.find((obj) => obj.nameSingular === 'company');
jestExpectToBeDefined(companyObject);
companyObjectMetadataId = companyObject.id;
});
describe('Company View Structure Validation', () => {
it('should successfully fetch complete view structure with all sub-relations in single GraphQL query', async () => {
const { data: viewsData } = await findViews({
objectMetadataId: companyObjectMetadataId,
gqlFields: COMPREHENSIVE_VIEW_GQL_FIELDS,
expectToFail: false,
});
const testView = viewsData.getViews.find(
(view) => view.key === ViewKey.INDEX,
);
jestExpectToBeDefined(testView);
const stableTestView = {
...testView,
viewFields: [...testView.viewFields].sort(
(a, b) => a.position - b.position,
),
};
// universalIdentifier stays normalized: the sample-app fields seeded on
// the company view get per-seed identifiers, so the engine-derived view
// field identifiers displaying them differ across seeds too.
expect(stableTestView).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny({ ...stableTestView }),
);
});
});
});

View File

@@ -0,0 +1,126 @@
import { VIEW_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
import { createOneSelectFieldMetadataForIntegrationTests } from 'test/integration/metadata/suites/field-metadata/utils/create-one-select-field-metadata-for-integration-tests.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 { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { updateOneView } from 'test/integration/metadata/suites/view/utils/update-one-view.util';
import { ViewType } from 'twenty-shared/types';
const TEST_NOT_EXISTING_VIEW_ID = '20202020-0000-4000-8000-000000000000';
describe('Update core view', () => {
let testObjectMetadataId: string;
let testSelectFieldMetadataId: string;
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myViewTestObject',
namePlural: 'myViewTestObjects',
labelSingular: 'My View Test Object',
labelPlural: 'My View Test Objects',
icon: 'Icon123',
},
});
const { selectFieldMetadataId } =
await createOneSelectFieldMetadataForIntegrationTests({
expectToFail: false,
input: {
objectMetadataId,
options: [
{
label: 'Option 1',
value: 'OPTION_1',
color: 'blue',
position: 0,
},
{ label: 'Option 2', value: 'OPTION_2', color: 'red', position: 1 },
{
label: 'Option 3',
value: 'OPTION_3',
color: 'green',
position: 2,
},
],
},
});
testObjectMetadataId = objectMetadataId;
testSelectFieldMetadataId = selectFieldMetadataId;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testObjectMetadataId },
});
});
it('should update an existing view', async () => {
const {
data: { createView: view },
} = await createOneView({
input: {
icon: '123Icon',
name: 'Original View',
type: ViewType.TABLE,
isCompact: false,
objectMetadataId: testObjectMetadataId,
},
expectToFail: false,
});
const updateInput = {
id: view.id,
name: 'Updated View',
type: ViewType.KANBAN,
mainGroupByFieldMetadataId: testSelectFieldMetadataId,
isCompact: true,
kanbanColumnWidth: 250,
};
const { data, errors } = await updateOneView({
viewId: view.id,
input: updateInput,
gqlFields: `${VIEW_GQL_FIELDS}
kanbanColumnWidth`,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(data.updateView).toMatchObject({
id: view.id,
name: 'Updated View',
type: ViewType.KANBAN,
isCompact: true,
kanbanColumnWidth: 250,
});
});
it('should throw error when updating non-existent view', async () => {
const { errors } = await updateOneView({
viewId: TEST_NOT_EXISTING_VIEW_ID,
input: { id: TEST_NOT_EXISTING_VIEW_ID, name: 'Non-existent View' },
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});

View File

@@ -0,0 +1,62 @@
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 { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
export type ViewFieldGroupTestSetup = {
testViewId: string;
testObjectMetadataId: string;
};
export const setupViewFieldGroupTest =
async (): Promise<ViewFieldGroupTestSetup> => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myFieldGroupTestObject',
namePlural: 'myFieldGroupTestObjects',
labelSingular: 'My Field Group Test Object',
labelPlural: 'My Field Group Test Objects',
icon: 'Icon123',
},
});
const {
data: {
createView: { id: testViewId },
},
} = await createOneView({
input: {
icon: 'icon123',
objectMetadataId,
name: 'TestViewForFieldGroups',
},
expectToFail: false,
});
return {
testViewId,
testObjectMetadataId: objectMetadataId,
};
};
export const cleanupViewFieldGroupTest = async (
objectMetadataId: string,
): Promise<void> => {
await updateOneObjectMetadata({
input: {
idToUpdate: objectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: objectMetadataId },
});
};

View File

@@ -0,0 +1,86 @@
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 { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { FieldMetadataType } from 'twenty-shared/types';
export type ViewFieldTestSetup = {
testViewId: string;
testObjectMetadataId: string;
testFieldMetadataId: string;
};
export const setupViewFieldTest = async (): Promise<ViewFieldTestSetup> => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myFieldTestObject',
namePlural: 'myFieldTestObjects',
labelSingular: 'My Field Test Object',
labelPlural: 'My Field Test Objects',
icon: 'Icon123',
},
});
const {
data: {
createOneField: { id: fieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'testField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId,
isLabelSyncedWithName: true,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
const {
data: {
createView: { id: testViewId },
},
} = await createOneView({
input: {
icon: 'icon123',
objectMetadataId,
name: 'TestViewForFields',
},
expectToFail: false,
});
return {
testViewId,
testObjectMetadataId: objectMetadataId,
testFieldMetadataId: fieldMetadataId,
};
};
export const cleanupViewFieldTest = async (
objectMetadataId: string,
): Promise<void> => {
await updateOneObjectMetadata({
input: {
idToUpdate: objectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: objectMetadataId },
});
};

View File

@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Group Resolver - Failing Create Many Operations should fail when some inputs reference non-existent views 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "Could not find view for given viewId",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Group Resolver - Failing Create Operation should fail to create view field group when non-existent view 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "Could not find view for given viewId",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Group Resolver - Failing Delete Operation should fail to delete view field group when non-existent view field group 1`] = `
{
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exceptionEventId": Any<String>,
"userFriendlyMessage": "A view field group error occurred.",
},
"message": "View field group to delete not found",
}
`;

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Group Resolver - Failing Destroy Operation should fail to destroy view field group when non-existent view field group 1`] = `
{
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exceptionEventId": Any<String>,
"userFriendlyMessage": "A view field group error occurred.",
},
"message": "View field group to destroy not found",
}
`;

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Group Resolver - Failing Update Operation should fail to update view field group when non-existent view field group 1`] = `
{
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exceptionEventId": Any<String>,
"userFriendlyMessage": "A view field group error occurred.",
},
"message": "View field group to update not found",
}
`;

View File

@@ -0,0 +1,94 @@
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.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 { createManyViewFieldGroups } from 'test/integration/metadata/suites/view-field-group/utils/create-many-view-field-groups.util';
import { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { v4 as uuidv4 } from 'uuid';
import { type CreateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/create-view-field-group.input';
describe('View Field Group Resolver - Failing Create Many Operations', () => {
let testSetup: {
testViewId: string;
testObjectMetadataId: string;
};
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myFieldGroupTestObject',
namePlural: 'myFieldGroupTestObjects',
labelSingular: 'My Field Group Test Object',
labelPlural: 'My Field Group Test Objects',
icon: 'Icon123',
},
});
const {
data: {
createView: { id: testViewId },
},
} = await createOneView({
input: {
icon: 'icon123',
objectMetadataId,
name: 'TestViewForFieldGroups',
},
expectToFail: false,
});
testSetup = {
testViewId,
testObjectMetadataId: objectMetadataId,
};
});
afterAll(async () => {
await updateOneObjectMetadata({
input: {
idToUpdate: testSetup.testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testSetup.testObjectMetadataId },
});
});
it('should fail when some inputs reference non-existent views', async () => {
const invalidViewId = uuidv4();
const inputs: CreateViewFieldGroupInput[] = [
{
name: 'Valid Group',
viewId: testSetup.testViewId,
position: 0,
isVisible: true,
},
{
name: 'Invalid Group',
viewId: invalidViewId,
position: 1,
isVisible: true,
},
];
const { errors } = await createManyViewFieldGroups({
inputs,
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({
errors,
});
});
});

View File

@@ -0,0 +1,97 @@
import { faker } from '@faker-js/faker';
import {
cleanupViewFieldGroupTest,
setupViewFieldGroupTest,
type ViewFieldGroupTestSetup,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-group-test.util';
import { createOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/create-one-view-field-group.util';
import { deleteOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/delete-one-view-field-group.util';
import { destroyOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/destroy-one-view-field-group.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { type CreateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/create-view-field-group.input';
const normalizeErrorMessage = (error: any) => {
const UUID_REGEX =
/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
return {
...error,
message: error.message?.replace(UUID_REGEX, '{{UUID}}') || error.message,
};
};
describe('View Field Group Resolver - Failing Create Operation', () => {
let testSetup: ViewFieldGroupTestSetup;
let createdViewFieldGroupIds: string[] = [];
beforeAll(async () => {
testSetup = await setupViewFieldGroupTest();
});
afterEach(async () => {
for (const viewFieldGroupId of createdViewFieldGroupIds) {
await deleteOneViewFieldGroup({
input: {
id: viewFieldGroupId,
},
expectToFail: false,
});
await destroyOneViewFieldGroup({
input: {
id: viewFieldGroupId,
},
expectToFail: false,
});
}
});
afterAll(async () => {
await cleanupViewFieldGroupTest(testSetup.testObjectMetadataId);
});
type CreateViewFieldGroupTestCase = (testSetup: ViewFieldGroupTestSetup) => {
input: CreateViewFieldGroupInput;
};
const createViewFieldGroupTestCases: EachTestingContext<CreateViewFieldGroupTestCase>[] =
[
{
title: 'non-existent view',
context: () => ({
input: {
name: 'Test Group',
viewId: faker.string.uuid(),
},
}),
},
];
it.each(eachTestingContextFilter(createViewFieldGroupTestCases))(
'should fail to create view field group when $title',
async ({ context }) => {
const { input } = context(testSetup);
const response = await createOneViewFieldGroup({
input,
expectToFail: true,
});
expect(response.errors).toBeDefined();
expect(response.errors.length).toBe(1);
const [firstError] = response.errors;
expect(firstError.extensions.code).not.toBe('INTERNAL_SERVER_ERROR');
const normalizedError = normalizeErrorMessage(firstError);
expect(normalizedError).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(normalizedError),
);
},
);
});

View File

@@ -0,0 +1,62 @@
import {
cleanupViewFieldGroupTest,
setupViewFieldGroupTest,
type ViewFieldGroupTestSetup,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-group-test.util';
import { deleteOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/delete-one-view-field-group.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { type DeleteViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/delete-view-field-group.input';
const TEST_NOT_EXISTING_VIEW_FIELD_GROUP_ID =
'20202020-0000-4000-8000-000000000002';
describe('View Field Group Resolver - Failing Delete Operation', () => {
let testSetup: ViewFieldGroupTestSetup;
beforeAll(async () => {
testSetup = await setupViewFieldGroupTest();
});
afterAll(async () => {
await cleanupViewFieldGroupTest(testSetup.testObjectMetadataId);
});
type DeleteViewFieldGroupTestCase = {
input: DeleteViewFieldGroupInput;
};
const deleteViewFieldGroupTestCases: EachTestingContext<DeleteViewFieldGroupTestCase>[] =
[
{
title: 'non-existent view field group',
context: {
input: {
id: TEST_NOT_EXISTING_VIEW_FIELD_GROUP_ID,
},
},
},
];
it.each(eachTestingContextFilter(deleteViewFieldGroupTestCases))(
'should fail to delete view field group when $title',
async ({ context }) => {
const response = await deleteOneViewFieldGroup({
input: context.input,
expectToFail: true,
});
expect(response.errors).toBeDefined();
expect(response.errors.length).toBe(1);
const [firstError] = response.errors;
expect(firstError).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(firstError),
);
},
);
});

View File

@@ -0,0 +1,62 @@
import {
cleanupViewFieldGroupTest,
setupViewFieldGroupTest,
type ViewFieldGroupTestSetup,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-group-test.util';
import { destroyOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/destroy-one-view-field-group.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { type DestroyViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/destroy-view-field-group.input';
const TEST_NOT_EXISTING_VIEW_FIELD_GROUP_ID =
'20202020-0000-4000-8000-000000000002';
describe('View Field Group Resolver - Failing Destroy Operation', () => {
let testSetup: ViewFieldGroupTestSetup;
beforeAll(async () => {
testSetup = await setupViewFieldGroupTest();
});
afterAll(async () => {
await cleanupViewFieldGroupTest(testSetup.testObjectMetadataId);
});
type DestroyViewFieldGroupTestCase = {
input: DestroyViewFieldGroupInput;
};
const destroyViewFieldGroupTestCases: EachTestingContext<DestroyViewFieldGroupTestCase>[] =
[
{
title: 'non-existent view field group',
context: {
input: {
id: TEST_NOT_EXISTING_VIEW_FIELD_GROUP_ID,
},
},
},
];
it.each(eachTestingContextFilter(destroyViewFieldGroupTestCases))(
'should fail to destroy view field group when $title',
async ({ context }) => {
const response = await destroyOneViewFieldGroup({
input: context.input,
expectToFail: true,
});
expect(response.errors).toBeDefined();
expect(response.errors.length).toBe(1);
const [firstError] = response.errors;
expect(firstError).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(firstError),
);
},
);
});

View File

@@ -0,0 +1,65 @@
import {
cleanupViewFieldGroupTest,
setupViewFieldGroupTest,
type ViewFieldGroupTestSetup,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-group-test.util';
import { updateOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/update-one-view-field-group.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { type UpdateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/update-view-field-group.input';
const TEST_NOT_EXISTING_VIEW_FIELD_GROUP_ID =
'20202020-0000-4000-8000-000000000002';
describe('View Field Group Resolver - Failing Update Operation', () => {
let testSetup: ViewFieldGroupTestSetup;
beforeAll(async () => {
testSetup = await setupViewFieldGroupTest();
});
afterAll(async () => {
await cleanupViewFieldGroupTest(testSetup.testObjectMetadataId);
});
type UpdateViewFieldGroupTestCase = {
input: UpdateViewFieldGroupInput;
};
const updateViewFieldGroupTestCases: EachTestingContext<UpdateViewFieldGroupTestCase>[] =
[
{
title: 'non-existent view field group',
context: {
input: {
id: TEST_NOT_EXISTING_VIEW_FIELD_GROUP_ID,
update: {
name: 'Updated Name',
},
},
},
},
];
it.each(eachTestingContextFilter(updateViewFieldGroupTestCases))(
'should fail to update view field group when $title',
async ({ context }) => {
const response = await updateOneViewFieldGroup({
input: context.input,
expectToFail: true,
});
expect(response.errors).toBeDefined();
expect(response.errors.length).toBe(1);
const [firstError] = response.errors;
expect(firstError).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(firstError),
);
},
);
});

View File

@@ -0,0 +1,188 @@
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 { createManyViewFieldGroups } from 'test/integration/metadata/suites/view-field-group/utils/create-many-view-field-groups.util';
import { deleteOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/delete-one-view-field-group.util';
import { destroyOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/destroy-one-view-field-group.util';
import { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { isDefined } from 'twenty-shared/utils';
import { type CreateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/create-view-field-group.input';
describe('View Field Group Resolver - Successful Create Many Operations', () => {
let testSetup: {
testViewId: string;
testObjectMetadataId: string;
};
let createdViewFieldGroupIds: string[] = [];
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myFieldGroupTestObject',
namePlural: 'myFieldGroupTestObjects',
labelSingular: 'My Field Group Test Object',
labelPlural: 'My Field Group Test Objects',
icon: 'Icon123',
},
});
const {
data: {
createView: { id: testViewId },
},
} = await createOneView({
input: {
icon: 'icon123',
objectMetadataId,
name: 'TestViewForFieldGroups',
},
expectToFail: false,
});
testSetup = {
testViewId,
testObjectMetadataId: objectMetadataId,
};
});
afterAll(async () => {
await updateOneObjectMetadata({
input: {
idToUpdate: testSetup.testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testSetup.testObjectMetadataId },
});
});
afterEach(async () => {
for (const viewFieldGroupId of createdViewFieldGroupIds) {
if (isDefined(viewFieldGroupId)) {
const {
data: { deleteViewFieldGroup },
} = await deleteOneViewFieldGroup({
expectToFail: false,
input: {
id: viewFieldGroupId,
},
});
expect(deleteViewFieldGroup.deletedAt).not.toBeNull();
await destroyOneViewFieldGroup({
expectToFail: false,
input: {
id: viewFieldGroupId,
},
});
}
}
createdViewFieldGroupIds = [];
});
it('should successfully create multiple view field groups in batch', async () => {
const inputs: CreateViewFieldGroupInput[] = [
{
name: 'First Group',
viewId: testSetup.testViewId,
position: 0,
isVisible: true,
},
{
name: 'Second Group',
viewId: testSetup.testViewId,
position: 1,
isVisible: false,
},
{
name: 'Third Group',
viewId: testSetup.testViewId,
position: 2,
isVisible: true,
},
];
const {
data: { createManyViewFieldGroups: createdViewFieldGroups },
errors,
} = await createManyViewFieldGroups({
inputs,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(createdViewFieldGroups).toBeDefined();
expect(createdViewFieldGroups).toHaveLength(3);
createdViewFieldGroups.forEach((viewFieldGroup, index) => {
expect(viewFieldGroup).toMatchObject({
name: inputs[index].name,
viewId: testSetup.testViewId,
position: inputs[index].position,
isVisible: inputs[index].isVisible,
});
createdViewFieldGroupIds.push(viewFieldGroup.id);
});
});
it('should successfully create single view field group using batch endpoint', async () => {
const inputs: CreateViewFieldGroupInput[] = [
{
name: 'Single Batch Group',
viewId: testSetup.testViewId,
position: 5,
isVisible: true,
},
];
const {
data: { createManyViewFieldGroups: createdViewFieldGroups },
errors,
} = await createManyViewFieldGroups({
inputs,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(createdViewFieldGroups).toBeDefined();
expect(createdViewFieldGroups).toHaveLength(1);
const viewFieldGroup = createdViewFieldGroups[0];
expect(viewFieldGroup).toMatchObject({
name: 'Single Batch Group',
viewId: testSetup.testViewId,
position: 5,
isVisible: true,
});
createdViewFieldGroupIds.push(viewFieldGroup.id);
});
it('should return empty array when creating zero view field groups', async () => {
const inputs: CreateViewFieldGroupInput[] = [];
const {
data: { createManyViewFieldGroups: createdViewFieldGroups },
errors,
} = await createManyViewFieldGroups({
inputs,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(createdViewFieldGroups).toBeDefined();
expect(createdViewFieldGroups).toHaveLength(0);
});
});

View File

@@ -0,0 +1,127 @@
import {
type ViewFieldGroupTestSetup,
cleanupViewFieldGroupTest,
setupViewFieldGroupTest,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-group-test.util';
import { createOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/create-one-view-field-group.util';
import { deleteOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/delete-one-view-field-group.util';
import { destroyOneViewFieldGroup } from 'test/integration/metadata/suites/view-field-group/utils/destroy-one-view-field-group.util';
import { assertViewFieldGroupStructure } from 'test/integration/utils/view-test.util';
import {
type EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import { isDefined } from 'twenty-shared/utils';
import { type CreateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/create-view-field-group.input';
import { type ViewFieldGroupDTO } from 'src/engine/metadata-modules/view-field-group/dtos/view-field-group.dto';
type TestContext = {
viewFieldGroupInput: (
testSetup: ViewFieldGroupTestSetup,
) => CreateViewFieldGroupInput;
expected: Partial<ViewFieldGroupDTO>;
};
describe('View Field Group Resolver - Successful Create Operations', () => {
let testSetup: ViewFieldGroupTestSetup;
let createdViewFieldGroupId: string | undefined;
beforeAll(async () => {
testSetup = await setupViewFieldGroupTest();
});
afterAll(async () => {
await cleanupViewFieldGroupTest(testSetup.testObjectMetadataId);
});
afterEach(async () => {
if (isDefined(createdViewFieldGroupId)) {
const {
data: { deleteViewFieldGroup },
} = await deleteOneViewFieldGroup({
expectToFail: false,
input: {
id: createdViewFieldGroupId,
},
});
expect(deleteViewFieldGroup.deletedAt).not.toBeNull();
await destroyOneViewFieldGroup({
expectToFail: false,
input: {
id: createdViewFieldGroupId,
},
});
createdViewFieldGroupId = undefined;
}
});
const successfulTestCases: EachTestingContext<TestContext>[] = [
{
title: 'visible group with position',
context: {
viewFieldGroupInput: (testSetup) => ({
name: 'Test Group',
viewId: testSetup.testViewId,
position: 1,
isVisible: true,
}),
expected: {
name: 'Test Group',
position: 1,
isVisible: true,
},
},
},
{
title: 'hidden group with position',
context: {
viewFieldGroupInput: (testSetup) => ({
name: 'Hidden Group',
viewId: testSetup.testViewId,
position: 2,
isVisible: false,
}),
expected: {
name: 'Hidden Group',
position: 2,
isVisible: false,
},
},
},
{
title: 'group with minimum required properties',
context: {
viewFieldGroupInput: (testSetup) => ({
name: 'Minimal Group',
viewId: testSetup.testViewId,
}),
expected: {
name: 'Minimal Group',
position: 0,
isVisible: true,
},
},
},
];
test.each(eachTestingContextFilter(successfulTestCases))(
'Create $title',
async ({ context: { viewFieldGroupInput, expected } }) => {
const response = await createOneViewFieldGroup({
input: viewFieldGroupInput(testSetup),
expectToFail: false,
});
expect(response.errors).toBeUndefined();
expect(response.data.createViewFieldGroup).toBeDefined();
createdViewFieldGroupId = response.data.createViewFieldGroup.id;
assertViewFieldGroupStructure(response.data.createViewFieldGroup, {
viewId: testSetup.testViewId,
...expected,
});
},
);
});

View File

@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Resolver - Failing Create Many Operations should accumulate multiple validation errors when some inputs are invalid 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "Could not find fieldMetadata for given fieldMetadataId",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,62 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Resolver - Failing Create Operation Should fail to create a conflicting view field on view and field metadata 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"viewField": [
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "View field with same fieldMetadataUniversalIdentifier and viewUniversalIdentifier already exists",
"userFriendlyMessage": "View field already exists",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 viewField",
"summary": {
"totalErrors": 1,
"viewField": 1,
},
"userFriendlyMessage": "View field already exists",
},
"message": "Multiple validation errors occurred while creating view fields",
"name": "GraphQLError",
}
`;
exports[`View Field Resolver - Failing Create Operation should fail to create view field when non-existent field metadata 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "Could not find fieldMetadata for given fieldMetadataId",
"name": "NotFoundError",
}
`;
exports[`View Field Resolver - Failing Create Operation should fail to create view field when non-existent view metadata 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "Could not find view for given viewId",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Resolver - Failing Delete Operation should fail to delete view field when non-existent view field 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View field to delete not found",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Resolver - Failing Destroy Operation should fail to destroy view field when non-existent view field 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View field to destroy not found",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Resolver - Failing Update Operation should fail to update view field when non-existent view field 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "View field to update not found",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,185 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`View Field Resolver - Successful object metadata identifier update side effect on view field Should not allow creating a view field with a position lower than the label identifier view field 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"viewField": [
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "View field position cannot be lower than label identifier view field position",
"userFriendlyMessage": "View field position cannot be lower than label identifier view field position",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 viewField",
"summary": {
"totalErrors": 1,
"viewField": 1,
},
"userFriendlyMessage": "View field position cannot be lower than label identifier view field position",
},
"message": "Multiple validation errors occurred while creating view fields",
"name": "GraphQLError",
}
`;
exports[`View Field Resolver - Successful object metadata identifier update side effect on view field Should not allow deleting a label identifier view field 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"viewField": [
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Label identifier view field cannot be deleted",
"userFriendlyMessage": "Label identifier view field cannot be deleted",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "update",
},
],
},
"message": "Validation failed for 1 viewField",
"summary": {
"totalErrors": 1,
"viewField": 1,
},
"userFriendlyMessage": "Label identifier view field cannot be deleted",
},
"message": "Multiple validation errors occurred while deleting view field",
"name": "GraphQLError",
}
`;
exports[`View Field Resolver - Successful object metadata identifier update side effect on view field Should not allow destroying a label identifier view field 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"viewField": [
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Label identifier view field cannot be deleted",
"userFriendlyMessage": "Label identifier view field cannot be deleted",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"type": "delete",
},
],
},
"message": "Validation failed for 1 viewField",
"summary": {
"totalErrors": 1,
"viewField": 1,
},
"userFriendlyMessage": "Label identifier view field cannot be deleted",
},
"message": "Multiple validation errors occurred while deleting view field",
"name": "GraphQLError",
}
`;
exports[`View Field Resolver - Successful object metadata identifier update side effect on view field Should not allow updating a label identifier view field visibility to false 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"viewField": [
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Label identifier view field has to be visible",
"userFriendlyMessage": "Label identifier view field has to be visible",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "update",
},
],
},
"message": "Validation failed for 1 viewField",
"summary": {
"totalErrors": 1,
"viewField": 1,
},
"userFriendlyMessage": "Label identifier view field has to be visible",
},
"message": "Multiple validation errors occurred while updating view field",
"name": "GraphQLError",
}
`;
exports[`View Field Resolver - Successful object metadata identifier update side effect on view field Should not allow updating labelIdentifier view field with a position higher than existing other view field 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"viewField": [
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Label identifier view field has to be in the lowest position",
"userFriendlyMessage": "Label identifier view field has to be in the lowest position",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "update",
},
],
},
"message": "Validation failed for 1 viewField",
"summary": {
"totalErrors": 1,
"viewField": 1,
},
"userFriendlyMessage": "Label identifier view field has to be in the lowest position",
},
"message": "Multiple validation errors occurred while updating view field",
"name": "GraphQLError",
}
`;

View File

@@ -0,0 +1,152 @@
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.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 { createManyViewFields } from 'test/integration/metadata/suites/view-field/utils/create-many-view-fields.util';
import { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { FieldMetadataType } from 'twenty-shared/types';
import { v4 as uuidv4 } from 'uuid';
import { type CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
describe('View Field Resolver - Failing Create Many Operations', () => {
let testSetup: {
testViewId: string;
testObjectMetadataId: string;
firstTestFieldMetadataId: string;
secondTestFieldMetadataId: string;
};
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myFieldTestObject',
namePlural: 'myFieldTestObjects',
labelSingular: 'My Field Test Object',
labelPlural: 'My Field Test Objects',
icon: 'Icon123',
},
});
const {
data: {
createOneField: { id: firstTestFieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'testField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId,
isLabelSyncedWithName: true,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
const {
data: {
createOneField: { id: secondTestFieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'secondTestField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId,
isLabelSyncedWithName: false,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
const {
data: {
createView: { id: testViewId },
},
} = await createOneView({
input: {
icon: 'icon123',
objectMetadataId,
name: 'TestViewForFields',
},
expectToFail: false,
});
testSetup = {
testViewId,
testObjectMetadataId: objectMetadataId,
firstTestFieldMetadataId,
secondTestFieldMetadataId,
};
});
afterAll(async () => {
await updateOneObjectMetadata({
input: {
idToUpdate: testSetup.testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testSetup.testObjectMetadataId },
});
});
it('should accumulate multiple validation errors when some inputs are invalid', async () => {
const invalidViewId = uuidv4();
const invalidFieldMetadataId = uuidv4();
const inputs: CreateViewFieldInput[] = [
{
fieldMetadataId: invalidFieldMetadataId,
viewId: testSetup.testViewId,
position: 0,
isVisible: true,
size: 150,
},
{
fieldMetadataId: testSetup.firstTestFieldMetadataId,
viewId: invalidViewId,
position: 1,
isVisible: true,
size: 200,
},
{
fieldMetadataId: invalidFieldMetadataId,
viewId: invalidViewId,
position: 2,
isVisible: true,
size: 180,
},
];
const { errors } = await createManyViewFields({
inputs,
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({
errors,
});
});
});

View File

@@ -0,0 +1,165 @@
import { faker } from '@faker-js/faker';
import {
cleanupViewFieldTest,
setupViewFieldTest,
type ViewFieldTestSetup,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-test.util';
import { createOneViewField } from 'test/integration/metadata/suites/view-field/utils/create-one-view-field.util';
import { deleteOneViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-view-field.util';
import { destroyOneViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-view-field.util';
import { findViewFields } from 'test/integration/metadata/suites/view-field/utils/find-view-fields.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { type CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
const normalizeErrorMessage = (error: any) => {
const UUID_REGEX =
/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
return {
...error,
message: error.message?.replace(UUID_REGEX, '{{UUID}}') || error.message,
};
};
describe('View Field Resolver - Failing Create Operation', () => {
let testSetup: ViewFieldTestSetup;
let createdFlatViewFieldIds: string[] = [];
beforeAll(async () => {
testSetup = await setupViewFieldTest();
});
afterEach(async () => {
for (const viewFieldId of createdFlatViewFieldIds) {
await deleteOneViewField({
input: {
id: viewFieldId,
},
expectToFail: false,
});
await destroyOneViewField({
input: {
id: viewFieldId,
},
expectToFail: false,
});
}
});
afterAll(async () => {
await cleanupViewFieldTest(testSetup.testObjectMetadataId);
});
type CreateViewFieldTestCase = (testSetup: ViewFieldTestSetup) => {
input: CreateViewFieldInput;
};
const createViewFieldTestCases: EachTestingContext<CreateViewFieldTestCase>[] =
[
{
title: 'non-existent field metadata',
context: (testSetup) => ({
input: {
viewId: testSetup.testViewId,
fieldMetadataId: faker.string.uuid(),
},
}),
},
{
title: 'non-existent view metadata',
context: (testSetup) => ({
input: {
viewId: faker.string.uuid(),
fieldMetadataId: testSetup.testFieldMetadataId,
},
}),
},
];
it.each(eachTestingContextFilter(createViewFieldTestCases))(
'should fail to create view field when $title',
async ({ context }) => {
const { input } = context(testSetup);
const response = await createOneViewField({
input,
expectToFail: true,
});
expect(response.errors).toBeDefined();
expect(response.errors.length).toBe(1);
const [firstError] = response.errors;
expect(firstError.extensions.code).not.toBe('INTERNAL_SERVER_ERROR');
const normalizedError = normalizeErrorMessage(firstError);
expect(normalizedError).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(normalizedError),
);
},
);
it('Should fail to create a conflicting view field on view and field metadata', async () => {
const viewFieldId = '20202020-7ace-42ee-aecf-2b1c1bd34bce';
const {
data: {
createViewField: { id: createdFlatViewFieldId },
},
} = await createOneViewField({
input: {
id: viewFieldId,
fieldMetadataId: testSetup.testFieldMetadataId,
viewId: testSetup.testViewId,
},
expectToFail: false,
});
createdFlatViewFieldIds.push(createdFlatViewFieldId);
const {
data: { getViewFields },
} = await findViewFields({
viewId: testSetup.testViewId,
expectToFail: false,
gqlFields: `
id
fieldMetadataId
viewId
`,
});
expect(getViewFields).toStrictEqual([
{
id: viewFieldId,
fieldMetadataId: testSetup.testFieldMetadataId,
viewId: testSetup.testViewId,
},
]);
const response = await createOneViewField({
input: {
fieldMetadataId: testSetup.testFieldMetadataId,
viewId: testSetup.testViewId,
},
expectToFail: true,
});
expect(response.errors).toBeDefined();
expect(response.errors.length).toBe(1);
const [firstError] = response.errors;
expect(firstError.extensions.code).not.toBe('INTERNAL_SERVER_ERROR');
const normalizedError = normalizeErrorMessage(firstError);
expect(normalizedError).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(normalizedError),
);
});
});

View File

@@ -0,0 +1,61 @@
import {
cleanupViewFieldTest,
setupViewFieldTest,
type ViewFieldTestSetup,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-test.util';
import { deleteOneViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-view-field.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { type DeleteViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/delete-view-field.input';
const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001';
describe('View Field Resolver - Failing Delete Operation', () => {
let testSetup: ViewFieldTestSetup;
beforeAll(async () => {
testSetup = await setupViewFieldTest();
});
afterAll(async () => {
await cleanupViewFieldTest(testSetup.testObjectMetadataId);
});
type DeleteViewFieldTestCase = {
input: DeleteViewFieldInput;
};
const deleteViewFieldTestCases: EachTestingContext<DeleteViewFieldTestCase>[] =
[
{
title: 'non-existent view field',
context: {
input: {
id: TEST_NOT_EXISTING_VIEW_FIELD_ID,
},
},
},
];
it.each(eachTestingContextFilter(deleteViewFieldTestCases))(
'should fail to delete view field when $title',
async ({ context }) => {
const response = await deleteOneViewField({
input: context.input,
expectToFail: true,
});
expect(response.errors).toBeDefined();
expect(response.errors.length).toBe(1);
const [firstError] = response.errors;
expect(firstError).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(firstError),
);
},
);
});

View File

@@ -0,0 +1,61 @@
import {
cleanupViewFieldTest,
setupViewFieldTest,
type ViewFieldTestSetup,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-test.util';
import { destroyOneViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-view-field.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { type DestroyViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/destroy-view-field.input';
const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001';
describe('View Field Resolver - Failing Destroy Operation', () => {
let testSetup: ViewFieldTestSetup;
beforeAll(async () => {
testSetup = await setupViewFieldTest();
});
afterAll(async () => {
await cleanupViewFieldTest(testSetup.testObjectMetadataId);
});
type DestroyViewFieldTestCase = {
input: DestroyViewFieldInput;
};
const destroyViewFieldTestCases: EachTestingContext<DestroyViewFieldTestCase>[] =
[
{
title: 'non-existent view field',
context: {
input: {
id: TEST_NOT_EXISTING_VIEW_FIELD_ID,
},
},
},
];
it.each(eachTestingContextFilter(destroyViewFieldTestCases))(
'should fail to destroy view field when $title',
async ({ context }) => {
const response = await destroyOneViewField({
input: context.input,
expectToFail: true,
});
expect(response.errors).toBeDefined();
expect(response.errors.length).toBe(1);
const [firstError] = response.errors;
expect(firstError).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(firstError),
);
},
);
});

View File

@@ -0,0 +1,64 @@
import {
cleanupViewFieldTest,
setupViewFieldTest,
type ViewFieldTestSetup,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-test.util';
import { updateOneViewField } from 'test/integration/metadata/suites/view-field/utils/update-one-view-field.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { type UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001';
describe('View Field Resolver - Failing Update Operation', () => {
let testSetup: ViewFieldTestSetup;
beforeAll(async () => {
testSetup = await setupViewFieldTest();
});
afterAll(async () => {
await cleanupViewFieldTest(testSetup.testObjectMetadataId);
});
type UpdateViewFieldTestCase = {
input: UpdateViewFieldInput;
};
const updateViewFieldTestCases: EachTestingContext<UpdateViewFieldTestCase>[] =
[
{
title: 'non-existent view field',
context: {
input: {
id: TEST_NOT_EXISTING_VIEW_FIELD_ID,
update: {
position: 1,
},
},
},
},
];
it.each(eachTestingContextFilter(updateViewFieldTestCases))(
'should fail to update view field when $title',
async ({ context }) => {
const response = await updateOneViewField({
input: context.input,
expectToFail: true,
});
expect(response.errors).toBeDefined();
expect(response.errors.length).toBe(1);
const [firstError] = response.errors;
expect(firstError).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(firstError),
);
},
);
});

View File

@@ -0,0 +1,246 @@
import {
type ViewFieldTestSetup,
cleanupViewFieldTest,
setupViewFieldTest,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-test.util';
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
import { createOneViewField } from 'test/integration/metadata/suites/view-field/utils/create-one-view-field.util';
import { deleteOneViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-view-field.util';
import { destroyOneViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-view-field.util';
import { findViewFields } from 'test/integration/metadata/suites/view-field/utils/find-view-fields.util';
import { updateOneViewField } from 'test/integration/metadata/suites/view-field/utils/update-one-view-field.util';
import { FieldMetadataType } from 'twenty-shared/types';
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
describe('View Field Resolver - Successful object metadata identifier update side effect on view field', () => {
let testSetup: ViewFieldTestSetup & {
testLabelIdentifierViewFieldId: string;
};
beforeAll(async () => {
const { testFieldMetadataId, testObjectMetadataId, testViewId } =
await setupViewFieldTest();
await updateOneObjectMetadata({
input: {
idToUpdate: testObjectMetadataId,
updatePayload: {
labelIdentifierFieldMetadataId: testFieldMetadataId,
},
},
expectToFail: false,
});
const {
data: { getViewFields },
} = await findViewFields({
viewId: testViewId,
expectToFail: false,
gqlFields: `
id
fieldMetadataId
viewId
`,
});
const testLabelIdentifierViewFieldId = getViewFields[0].id;
testSetup = {
testFieldMetadataId,
testObjectMetadataId,
testViewId,
testLabelIdentifierViewFieldId,
};
});
afterAll(async () => {
await cleanupViewFieldTest(testSetup.testObjectMetadataId);
});
it('should create a view field on label identifier object metadata update if it does not exist on view', async () => {
const { testFieldMetadataId, testViewId } = testSetup;
const {
data: { getViewFields },
} = await findViewFields({
viewId: testViewId,
expectToFail: false,
gqlFields: `
id
fieldMetadataId
viewId
`,
});
expect(getViewFields).toMatchObject<
Pick<FlatViewField, 'id' | 'fieldMetadataId' | 'viewId'>[]
>([
{
id: testSetup.testLabelIdentifierViewFieldId,
fieldMetadataId: testFieldMetadataId,
viewId: testViewId,
},
]);
});
it('Should not allow deleting a label identifier view field', async () => {
const { errors } = await deleteOneViewField({
input: { id: testSetup.testLabelIdentifierViewFieldId },
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({
errors,
});
});
it('Should not allow destroying a label identifier view field', async () => {
const { errors } = await destroyOneViewField({
input: { id: testSetup.testLabelIdentifierViewFieldId },
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({
errors,
});
});
it('Should not allow updating a label identifier view field visibility to false', async () => {
const { errors } = await updateOneViewField({
input: {
id: testSetup.testLabelIdentifierViewFieldId,
update: { isVisible: false },
},
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({
errors,
});
});
it('Should not allow creating a view field with a position lower than the label identifier view field', async () => {
const {
data: {
createOneField: { id: fieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'nonLabelIdentifierFieldFailing',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId: testSetup.testObjectMetadataId,
isLabelSyncedWithName: false,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
const { errors } = await createOneViewField({
input: {
fieldMetadataId: fieldMetadataId,
viewId: testSetup.testViewId,
position: -42,
},
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({
errors,
});
});
it('Should not allow updating labelIdentifier view field with a position higher than existing other view field', async () => {
const {
data: {
createOneField: { id: fieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'nonLabelIdentifierFieldSuccessful',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId: testSetup.testObjectMetadataId,
isLabelSyncedWithName: false,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
await createOneViewField({
input: {
fieldMetadataId: fieldMetadataId,
viewId: testSetup.testViewId,
position: 42,
},
expectToFail: false,
});
const { errors } = await updateOneViewField({
input: {
id: testSetup.testLabelIdentifierViewFieldId,
update: {
position: 43,
},
},
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({
errors,
});
});
it('Should update labelIdentifier view field with a position higher than existing other view field', async () => {
const {
data: {
createOneField: { id: fieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'justafield',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId: testSetup.testObjectMetadataId,
isLabelSyncedWithName: false,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
await createOneViewField({
input: {
fieldMetadataId: fieldMetadataId,
viewId: testSetup.testViewId,
position: 42,
},
expectToFail: false,
});
await updateOneViewField({
input: {
id: testSetup.testLabelIdentifierViewFieldId,
update: {
position: 41,
},
},
expectToFail: false,
});
});
});

View File

@@ -0,0 +1,265 @@
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 { createManyViewFields } from 'test/integration/metadata/suites/view-field/utils/create-many-view-fields.util';
import { deleteOneViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-view-field.util';
import { destroyOneViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-view-field.util';
import { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
describe('View Field Resolver - Successful Create Many Operations', () => {
let testSetup: {
testViewId: string;
testObjectMetadataId: string;
firstTestFieldMetadataId: string;
secondTestFieldMetadataId: string;
thirdTestFieldMetadataId: string;
};
let createdViewFieldIds: string[] = [];
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myFieldTestObject',
namePlural: 'myFieldTestObjects',
labelSingular: 'My Field Test Object',
labelPlural: 'My Field Test Objects',
icon: 'Icon123',
},
});
const {
data: {
createOneField: { id: firstTestFieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'testField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId,
isLabelSyncedWithName: true,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
const {
data: {
createOneField: { id: secondTestFieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'secondTestField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId,
isLabelSyncedWithName: false,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
const {
data: {
createOneField: { id: thirdTestFieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'thirdTestField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId,
isLabelSyncedWithName: false,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
const {
data: {
createView: { id: testViewId },
},
} = await createOneView({
input: {
icon: 'icon123',
objectMetadataId,
name: 'TestViewForFields',
},
expectToFail: false,
});
testSetup = {
testViewId,
testObjectMetadataId: objectMetadataId,
firstTestFieldMetadataId,
secondTestFieldMetadataId,
thirdTestFieldMetadataId,
};
});
afterAll(async () => {
await updateOneObjectMetadata({
input: {
idToUpdate: testSetup.testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testSetup.testObjectMetadataId },
});
});
afterEach(async () => {
for (const viewFieldId of createdViewFieldIds) {
if (isDefined(viewFieldId)) {
const {
data: { deleteViewField },
} = await deleteOneViewField({
expectToFail: false,
input: {
id: viewFieldId,
},
});
expect(deleteViewField.deletedAt).not.toBeNull();
await destroyOneViewField({
expectToFail: false,
input: {
id: viewFieldId,
},
});
}
}
createdViewFieldIds = [];
});
it('should successfully create multiple view fields in batch', async () => {
const inputs: CreateViewFieldInput[] = [
{
fieldMetadataId: testSetup.firstTestFieldMetadataId,
viewId: testSetup.testViewId,
position: 0,
isVisible: true,
size: 150,
},
{
fieldMetadataId: testSetup.secondTestFieldMetadataId,
viewId: testSetup.testViewId,
position: 1,
isVisible: false,
size: 200,
},
{
fieldMetadataId: testSetup.thirdTestFieldMetadataId,
viewId: testSetup.testViewId,
position: 2,
isVisible: true,
size: 180,
},
];
const {
data: { createManyViewFields: createdViewFields },
errors,
} = await createManyViewFields({
inputs,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(createdViewFields).toBeDefined();
expect(createdViewFields).toHaveLength(3);
createdViewFields.forEach((viewField, index) => {
expect(viewField).toMatchObject({
fieldMetadataId: inputs[index].fieldMetadataId,
viewId: testSetup.testViewId,
position: inputs[index].position,
isVisible: inputs[index].isVisible,
size: inputs[index].size,
});
createdViewFieldIds.push(viewField.id);
});
});
it('should successfully create single view field using batch endpoint', async () => {
const inputs: CreateViewFieldInput[] = [
{
fieldMetadataId: testSetup.firstTestFieldMetadataId,
viewId: testSetup.testViewId,
position: 5,
isVisible: true,
size: 250,
},
];
const {
data: { createManyViewFields: createdViewFields },
errors,
} = await createManyViewFields({
inputs,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(createdViewFields).toBeDefined();
expect(createdViewFields).toHaveLength(1);
const viewField = createdViewFields[0];
expect(viewField).toMatchObject({
fieldMetadataId: testSetup.firstTestFieldMetadataId,
viewId: testSetup.testViewId,
position: 5,
isVisible: true,
size: 250,
});
createdViewFieldIds.push(viewField.id);
});
it('should return empty array when creating zero view fields', async () => {
const inputs: CreateViewFieldInput[] = [];
const {
data: { createManyViewFields: createdViewFields },
errors,
} = await createManyViewFields({
inputs,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(createdViewFields).toBeDefined();
expect(createdViewFields).toHaveLength(0);
});
});

View File

@@ -0,0 +1,145 @@
import {
type ViewFieldTestSetup,
cleanupViewFieldTest,
setupViewFieldTest,
} from 'test/integration/graphql/suites/view/utils/setup-view-field-test.util';
import { createOneViewField } from 'test/integration/metadata/suites/view-field/utils/create-one-view-field.util';
import { deleteOneViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-view-field.util';
import { destroyOneViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-view-field.util';
import { assertViewFieldStructure } from 'test/integration/utils/view-test.util';
import {
type EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import { isDefined } from 'twenty-shared/utils';
import { type CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
import { type ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
type TestContext = {
viewFieldInput: (testSetup: ViewFieldTestSetup) => CreateViewFieldInput;
expected: Partial<ViewFieldDTO>;
};
describe('View Field Resolver - Successful Create Operations', () => {
let testSetup: ViewFieldTestSetup;
let createdViewFieldId: string | undefined;
beforeAll(async () => {
testSetup = await setupViewFieldTest();
});
afterAll(async () => {
await cleanupViewFieldTest(testSetup.testObjectMetadataId);
});
afterEach(async () => {
if (isDefined(createdViewFieldId)) {
const {
data: { deleteViewField },
} = await deleteOneViewField({
expectToFail: false,
input: {
id: createdViewFieldId,
},
});
expect(deleteViewField.deletedAt).not.toBeNull();
await destroyOneViewField({
expectToFail: false,
input: {
id: createdViewFieldId,
},
});
createdViewFieldId = undefined;
}
});
const successfulTestCases: EachTestingContext<TestContext>[] = [
{
title: 'visible field with position and size',
context: {
viewFieldInput: (testSetup) => ({
fieldMetadataId: testSetup.testFieldMetadataId,
viewId: testSetup.testViewId,
position: 1,
isVisible: true,
size: 200,
}),
expected: {
position: 1,
isVisible: true,
size: 200,
},
},
},
{
title: 'hidden field with position and size',
context: {
viewFieldInput: (testSetup) => ({
fieldMetadataId: testSetup.testFieldMetadataId,
viewId: testSetup.testViewId,
position: 2,
isVisible: false,
size: 100,
}),
expected: {
position: 2,
isVisible: false,
size: 100,
},
},
},
{
title: 'field with minimum required properties',
context: {
viewFieldInput: (testSetup) => ({
fieldMetadataId: testSetup.testFieldMetadataId,
viewId: testSetup.testViewId,
}),
expected: {
position: 0,
isVisible: true,
size: 0,
},
},
},
{
title: 'field with maximum size',
context: {
viewFieldInput: (testSetup) => ({
fieldMetadataId: testSetup.testFieldMetadataId,
viewId: testSetup.testViewId,
position: 3,
isVisible: true,
size: 1000,
}),
expected: {
position: 3,
isVisible: true,
size: 1000,
},
},
},
];
test.each(eachTestingContextFilter(successfulTestCases))(
'Create $title',
async ({ context: { viewFieldInput, expected } }) => {
const response = await createOneViewField({
input: viewFieldInput(testSetup),
expectToFail: false,
});
expect(response.errors).toBeUndefined();
expect(response.data.createViewField).toBeDefined();
createdViewFieldId = response.data.createViewField.id;
assertViewFieldStructure(response.data.createViewField, {
fieldMetadataId: testSetup.testFieldMetadataId,
viewId: testSetup.testViewId,
...expected,
});
},
);
});

View File

@@ -0,0 +1,525 @@
import { createViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/create-view-filter-group-operation-factory.util';
import { deleteViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/delete-view-filter-group-operation-factory.util';
import { destroyViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/destroy-view-filter-group-operation-factory.util';
import { findViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/find-view-filter-group-operation-factory.util';
import { findViewFilterGroupsOperationFactory } from 'test/integration/graphql/utils/find-view-filter-groups-operation-factory.util';
import {
assertGraphQLErrorResponse,
assertGraphQLSuccessfulResponse,
} from 'test/integration/graphql/utils/graphql-test-assertions.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { updateViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/update-view-filter-group-operation-factory.util';
import {
createViewFilterGroupData,
updateViewFilterGroupData,
} from 'test/integration/graphql/utils/view-data-factory.util';
import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-graphql.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 { destroyOneView } from 'test/integration/metadata/suites/view/utils/destroy-one-view.util';
import { assertViewFilterGroupStructure } from 'test/integration/utils/view-test.util';
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
import { ViewFilterGroupLogicalOperator } from 'twenty-shared/types';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { type ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-group/dtos/view-filter-group.dto';
import {
generateViewFilterGroupExceptionMessage,
ViewFilterGroupExceptionMessageKey,
} from 'src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception';
const TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID =
'20202020-0000-4000-8000-000000000002';
describe('View Filter Group Resolver', () => {
let testViewId: string;
let testObjectMetadataId: string;
let createdViewFilterGroup: string[] = [];
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myFilterGroupTestObject',
namePlural: 'myFilterGroupTestObjects',
labelSingular: 'My Filter Group Test Object',
labelPlural: 'My Filter Group Test Objects',
icon: 'Icon123',
},
});
testObjectMetadataId = objectMetadataId;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testObjectMetadataId },
});
});
beforeEach(async () => {
const view = await createTestViewWithGraphQL({
name: 'Test View for Filter Groups',
objectMetadataId: testObjectMetadataId,
});
testViewId = view.id;
});
afterEach(async () => {
for (const filterGroupId of createdViewFilterGroup) {
const destroyOperation = destroyViewFilterGroupOperationFactory({
viewFilterGroupId: filterGroupId,
});
await makeMetadataAPIRequest(destroyOperation);
}
createdViewFilterGroup = [];
await destroyOneView({
viewId: testViewId,
expectToFail: false,
});
});
describe('getViewFilterGroups', () => {
it('should return empty array when no view filter groups exist', async () => {
const operation = findViewFilterGroupsOperationFactory({
viewId: testViewId,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.getViewFilterGroups).toEqual([]);
});
it('should return all filter groups for workspace when no viewId provided', async () => {
const filterGroupData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.AND,
});
const createOperation = createViewFilterGroupOperationFactory({
data: filterGroupData,
});
const createResponse = await makeMetadataAPIRequest(createOperation);
const createdFilterGroupId =
createResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(createdFilterGroupId);
const getOperation = findViewFilterGroupsOperationFactory();
const response = await makeMetadataAPIRequest(getOperation);
assertGraphQLSuccessfulResponse(response);
const foundFilterGroup = response.body.data.getViewFilterGroups.find(
(el: ViewFilterGroupDTO) => el.id === createdFilterGroupId,
);
jestExpectToBeDefined(foundFilterGroup);
expect(foundFilterGroup).toMatchObject({
logicalOperator: ViewFilterGroupLogicalOperator.AND,
viewId: testViewId,
});
});
it('should return view filter groups for a specific view', async () => {
const filterGroupData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.OR,
});
const createOperation = createViewFilterGroupOperationFactory({
data: filterGroupData,
});
const createResponse = await makeMetadataAPIRequest(createOperation);
const createdFilterGroupId =
createResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(createdFilterGroupId);
const getOperation = findViewFilterGroupsOperationFactory({
viewId: testViewId,
});
const response = await makeMetadataAPIRequest(getOperation);
assertGraphQLSuccessfulResponse(response);
const foundFilterGroup = response.body.data.getViewFilterGroups.find(
(group: ViewFilterGroupDTO) => group.id === createdFilterGroupId,
);
jestExpectToBeDefined(foundFilterGroup);
assertViewFilterGroupStructure(foundFilterGroup, {
logicalOperator: ViewFilterGroupLogicalOperator.OR,
viewId: testViewId,
});
});
it('should return nested filter groups with parent relationships', async () => {
const parentData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.AND,
});
const parentOperation = createViewFilterGroupOperationFactory({
data: parentData,
});
const parentResponse = await makeMetadataAPIRequest(parentOperation);
const parentId = parentResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(parentId);
const childData = createViewFilterGroupData(testViewId, {
parentViewFilterGroupId: parentId,
logicalOperator: ViewFilterGroupLogicalOperator.OR,
});
const childOperation = createViewFilterGroupOperationFactory({
data: childData,
});
const childResponse = await makeMetadataAPIRequest(childOperation);
const childId = childResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(childId);
const getOperation = findViewFilterGroupsOperationFactory({
viewId: testViewId,
});
const response = await makeMetadataAPIRequest(getOperation);
assertGraphQLSuccessfulResponse(response);
const parentGroup = response.body.data.getViewFilterGroups.find(
(group: ViewFilterGroupDTO) => group.id === parentId,
);
const childGroup = response.body.data.getViewFilterGroups.find(
(group: ViewFilterGroupDTO) => group.id === childId,
);
jestExpectToBeDefined(parentGroup);
jestExpectToBeDefined(childGroup);
assertViewFilterGroupStructure(parentGroup, {
logicalOperator: ViewFilterGroupLogicalOperator.AND,
parentViewFilterGroupId: null,
});
assertViewFilterGroupStructure(childGroup, {
parentViewFilterGroupId: parentId,
logicalOperator: ViewFilterGroupLogicalOperator.OR,
});
});
});
describe('getViewFilterGroup', () => {
it('should return null when filter group does not exist', async () => {
const operation = findViewFilterGroupOperationFactory({
viewFilterGroupId: TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.getViewFilterGroup).toBeNull();
});
it('should return filter group when it exists', async () => {
const filterGroupData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.NOT,
});
const createOperation = createViewFilterGroupOperationFactory({
data: filterGroupData,
});
const createResponse = await makeMetadataAPIRequest(createOperation);
const filterGroupId = createResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(filterGroupId);
const getOperation = findViewFilterGroupOperationFactory({
viewFilterGroupId: filterGroupId,
});
const response = await makeMetadataAPIRequest(getOperation);
assertGraphQLSuccessfulResponse(response);
assertViewFilterGroupStructure(response.body.data.getViewFilterGroup, {
id: filterGroupId,
logicalOperator: ViewFilterGroupLogicalOperator.NOT,
viewId: testViewId,
});
});
});
describe('createViewFilterGroup', () => {
it('should create a new filter group with AND operator', async () => {
const filterGroupData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.AND,
});
const operation = createViewFilterGroupOperationFactory({
data: filterGroupData,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
createdViewFilterGroup.push(response.body.data.createViewFilterGroup.id);
assertViewFilterGroupStructure(response.body.data.createViewFilterGroup, {
logicalOperator: ViewFilterGroupLogicalOperator.AND,
viewId: testViewId,
});
});
it('should create a filter group with OR operator', async () => {
const filterGroupData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.OR,
});
const operation = createViewFilterGroupOperationFactory({
data: filterGroupData,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
createdViewFilterGroup.push(response.body.data.createViewFilterGroup.id);
assertViewFilterGroupStructure(response.body.data.createViewFilterGroup, {
logicalOperator: ViewFilterGroupLogicalOperator.OR,
});
});
it('should create a filter group with NOT operator', async () => {
const filterGroupData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.NOT,
});
const operation = createViewFilterGroupOperationFactory({
data: filterGroupData,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
createdViewFilterGroup.push(response.body.data.createViewFilterGroup.id);
assertViewFilterGroupStructure(response.body.data.createViewFilterGroup, {
logicalOperator: ViewFilterGroupLogicalOperator.NOT,
});
});
it('should create a nested filter group with parent relationship', async () => {
const parentData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.AND,
});
const parentOperation = createViewFilterGroupOperationFactory({
data: parentData,
});
const parentResponse = await makeMetadataAPIRequest(parentOperation);
const parentId = parentResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(parentId);
const childData = createViewFilterGroupData(testViewId, {
parentViewFilterGroupId: parentId,
logicalOperator: ViewFilterGroupLogicalOperator.OR,
});
const childOperation = createViewFilterGroupOperationFactory({
data: childData,
});
const childResponse = await makeMetadataAPIRequest(childOperation);
assertGraphQLSuccessfulResponse(childResponse);
createdViewFilterGroup.push(
childResponse.body.data.createViewFilterGroup.id,
);
assertViewFilterGroupStructure(
childResponse.body.data.createViewFilterGroup,
{
parentViewFilterGroupId: parentId,
logicalOperator: ViewFilterGroupLogicalOperator.OR,
},
);
});
});
describe('updateViewFilterGroup', () => {
it('should update an existing filter group', async () => {
const filterGroupData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.AND,
});
const createOperation = createViewFilterGroupOperationFactory({
data: filterGroupData,
});
const createResponse = await makeMetadataAPIRequest(createOperation);
const filterGroupId = createResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(filterGroupId);
const updateInput = updateViewFilterGroupData({
logicalOperator: ViewFilterGroupLogicalOperator.OR,
});
const updateOperation = updateViewFilterGroupOperationFactory({
viewFilterGroupId: filterGroupId,
data: updateInput,
});
const response = await makeMetadataAPIRequest(updateOperation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.updateViewFilterGroup).toMatchObject({
id: filterGroupId,
logicalOperator: ViewFilterGroupLogicalOperator.OR,
});
});
it('should update parent relationship of filter group', async () => {
const parentData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.AND,
});
const parentOperation = createViewFilterGroupOperationFactory({
data: parentData,
});
const parentResponse = await makeMetadataAPIRequest(parentOperation);
const parentId = parentResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(parentId);
const childData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.OR,
});
const childOperation = createViewFilterGroupOperationFactory({
data: childData,
});
const childResponse = await makeMetadataAPIRequest(childOperation);
const childId = childResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(childId);
const updateInput = updateViewFilterGroupData({
parentViewFilterGroupId: parentId,
});
const updateOperation = updateViewFilterGroupOperationFactory({
viewFilterGroupId: childId,
data: updateInput,
});
const response = await makeMetadataAPIRequest(updateOperation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.updateViewFilterGroup).toMatchObject({
id: childId,
parentViewFilterGroupId: parentId,
});
});
it('should throw error when updating non-existent filter group', async () => {
const updateInput = updateViewFilterGroupData({
logicalOperator: ViewFilterGroupLogicalOperator.AND,
});
const operation = updateViewFilterGroupOperationFactory({
viewFilterGroupId: TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID,
data: updateInput,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generateViewFilterGroupExceptionMessage(
ViewFilterGroupExceptionMessageKey.VIEW_FILTER_GROUP_NOT_FOUND,
),
);
});
});
describe('deleteViewFilterGroup', () => {
it('should delete an existing filter group', async () => {
const filterGroupData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.AND,
});
const createOperation = createViewFilterGroupOperationFactory({
data: filterGroupData,
});
const createResponse = await makeMetadataAPIRequest(createOperation);
const filterGroupId = createResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(filterGroupId);
const deleteOperation = deleteViewFilterGroupOperationFactory({
viewFilterGroupId: filterGroupId,
});
const response = await makeMetadataAPIRequest(deleteOperation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.deleteViewFilterGroup).toBe(true);
const getOperation = findViewFilterGroupOperationFactory({
viewFilterGroupId: filterGroupId,
});
const getResponse = await makeMetadataAPIRequest(getOperation);
expect(getResponse.body.data.getViewFilterGroup).toBeNull();
});
it('should throw an error when deleting non-existent filter group', async () => {
const operation = deleteViewFilterGroupOperationFactory({
viewFilterGroupId: TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generateViewFilterGroupExceptionMessage(
ViewFilterGroupExceptionMessageKey.VIEW_FILTER_GROUP_NOT_FOUND,
),
);
});
});
describe('destroyViewFilterGroup', () => {
it('should destroy an existing filter group', async () => {
const filterGroupData = createViewFilterGroupData(testViewId, {
logicalOperator: ViewFilterGroupLogicalOperator.AND,
});
const createOperation = createViewFilterGroupOperationFactory({
data: filterGroupData,
});
const createResponse = await makeMetadataAPIRequest(createOperation);
const filterGroupId = createResponse.body.data.createViewFilterGroup.id;
createdViewFilterGroup.push(filterGroupId);
const destroyOperation = destroyViewFilterGroupOperationFactory({
viewFilterGroupId: filterGroupId,
});
const response = await makeMetadataAPIRequest(destroyOperation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.destroyViewFilterGroup).toBe(true);
});
it('should throw an error when destroying non-existent filter group', async () => {
const operation = destroyViewFilterGroupOperationFactory({
viewFilterGroupId: TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generateViewFilterGroupExceptionMessage(
ViewFilterGroupExceptionMessageKey.VIEW_FILTER_GROUP_NOT_FOUND,
),
);
});
});
});

View File

@@ -0,0 +1,326 @@
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-graphql.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 { createOneViewFilter } from 'test/integration/metadata/suites/view-filter/utils/create-one-view-filter.util';
import { deleteOneViewFilter } from 'test/integration/metadata/suites/view-filter/utils/delete-one-view-filter.util';
import { destroyOneViewFilter } from 'test/integration/metadata/suites/view-filter/utils/destroy-one-view-filter.util';
import { findViewFilters } from 'test/integration/metadata/suites/view-filter/utils/find-view-filters.util';
import { updateOneViewFilter } from 'test/integration/metadata/suites/view-filter/utils/update-one-view-filter.util';
import { destroyOneView } from 'test/integration/metadata/suites/view/utils/destroy-one-view.util';
import { FieldMetadataType, ViewFilterOperand } from 'twenty-shared/types';
const TEST_NOT_EXISTING_VIEW_FILTER_ID = '20202020-52c5-4152-8c09-76a845fb8ece';
describe('View Filter Resolver', () => {
let testViewId: string;
let testObjectMetadataId: string;
let testFieldMetadataId: string;
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myFilterTestObject',
namePlural: 'myFilterTestObjects',
labelSingular: 'My Filter Test Object',
labelPlural: 'My Filter Test Objects',
icon: 'Icon123',
},
});
testObjectMetadataId = objectMetadataId;
const {
data: {
createOneField: { id: fieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'testField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId: testObjectMetadataId,
isLabelSyncedWithName: true,
},
});
testFieldMetadataId = fieldMetadataId;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testObjectMetadataId },
});
});
beforeEach(async () => {
const view = await createTestViewWithGraphQL({
name: 'Test View for Filters',
objectMetadataId: testObjectMetadataId,
});
testViewId = view.id;
});
afterEach(async () => {
await destroyOneView({
viewId: testViewId,
expectToFail: false,
});
});
describe('getViewFilters', () => {
it('should return empty array when no view filters exist', async () => {
const { data, errors } = await findViewFilters({
viewId: testViewId,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(data.getViewFilters).toEqual([]);
});
it('should return view filters for a specific view', async () => {
await createOneViewFilter({
input: {
fieldMetadataId: testFieldMetadataId,
viewId: testViewId,
operand: ViewFilterOperand.CONTAINS,
value: 'test',
},
expectToFail: false,
});
const { data, errors } = await findViewFilters({
viewId: testViewId,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(data.getViewFilters).toHaveLength(1);
expect(data.getViewFilters[0]).toMatchObject({
fieldMetadataId: testFieldMetadataId,
operand: ViewFilterOperand.CONTAINS,
value: 'test',
viewId: testViewId,
});
});
});
describe('createViewFilter', () => {
it('should create a new view filter with string value', async () => {
const { data, errors } = await createOneViewFilter({
input: {
fieldMetadataId: testFieldMetadataId,
viewId: testViewId,
operand: ViewFilterOperand.CONTAINS,
value: 'test value',
},
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(data.createViewFilter).toMatchObject({
fieldMetadataId: testFieldMetadataId,
operand: ViewFilterOperand.CONTAINS,
value: 'test value',
viewId: testViewId,
});
});
it('should create a view filter with a numeric value payload', async () => {
const { data, errors } = await createOneViewFilter({
input: {
fieldMetadataId: testFieldMetadataId,
viewId: testViewId,
operand: ViewFilterOperand.CONTAINS,
value: 100,
},
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(data.createViewFilter).toMatchObject({
fieldMetadataId: testFieldMetadataId,
operand: ViewFilterOperand.CONTAINS,
value: 100,
viewId: testViewId,
});
});
it('should create a view filter with a boolean value payload', async () => {
const { data, errors } = await createOneViewFilter({
input: {
fieldMetadataId: testFieldMetadataId,
viewId: testViewId,
operand: ViewFilterOperand.CONTAINS,
value: true,
},
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(data.createViewFilter).toMatchObject({
fieldMetadataId: testFieldMetadataId,
operand: ViewFilterOperand.CONTAINS,
value: true,
viewId: testViewId,
});
});
it('should reject a view filter with an operand incompatible with the field type', async () => {
const { errors } = await createOneViewFilter({
input: {
fieldMetadataId: testFieldMetadataId,
viewId: testViewId,
operand: ViewFilterOperand.IS,
value: 'test',
},
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});
describe('updateViewFilter', () => {
it('should update an existing view filter', async () => {
const { data: createData } = await createOneViewFilter({
input: {
fieldMetadataId: testFieldMetadataId,
viewId: testViewId,
operand: ViewFilterOperand.CONTAINS,
value: 'original',
},
expectToFail: false,
});
const viewFilterId = createData.createViewFilter.id;
const { data, errors } = await updateOneViewFilter({
input: {
id: viewFilterId,
update: {
operand: ViewFilterOperand.DOES_NOT_CONTAIN,
value: 'updated',
},
},
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(data.updateViewFilter).toMatchObject({
id: viewFilterId,
operand: ViewFilterOperand.DOES_NOT_CONTAIN,
value: 'updated',
});
});
it('should throw an error when updating non-existent view filter', async () => {
const { errors } = await updateOneViewFilter({
input: {
id: TEST_NOT_EXISTING_VIEW_FILTER_ID,
update: {},
},
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});
describe('deleteViewFilter', () => {
it('should delete an existing view filter', async () => {
const { data: createData } = await createOneViewFilter({
input: {
fieldMetadataId: testFieldMetadataId,
viewId: testViewId,
operand: ViewFilterOperand.CONTAINS,
value: 'to delete',
},
expectToFail: false,
});
const viewFilterId = createData.createViewFilter.id;
const { data, errors } = await deleteOneViewFilter({
input: { id: viewFilterId },
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(data.deleteViewFilter).toMatchObject({
id: viewFilterId,
});
expect(data.deleteViewFilter.deletedAt).toBeDefined();
});
it('should throw an error when deleting non-existent view filter', async () => {
const { errors } = await deleteOneViewFilter({
input: { id: TEST_NOT_EXISTING_VIEW_FILTER_ID },
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});
describe('destroyViewFilter', () => {
it('should destroy an existing view filter', async () => {
const { data: createData } = await createOneViewFilter({
input: {
fieldMetadataId: testFieldMetadataId,
viewId: testViewId,
operand: ViewFilterOperand.CONTAINS,
value: 'to destroy',
},
expectToFail: false,
});
const viewFilterId = createData.createViewFilter.id;
await deleteOneViewFilter({
input: {
id: viewFilterId,
},
expectToFail: false,
});
const { data, errors } = await destroyOneViewFilter({
input: { id: viewFilterId },
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(data.destroyViewFilter).toMatchObject({
id: viewFilterId,
});
});
it('should throw an error when destroying non-existent view filter', async () => {
const { errors } = await destroyOneViewFilter({
input: { id: TEST_NOT_EXISTING_VIEW_FILTER_ID },
expectToFail: true,
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});
});

View File

@@ -0,0 +1,250 @@
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-graphql.util';
import { createOneSelectFieldMetadataForIntegrationTests } from 'test/integration/metadata/suites/field-metadata/utils/create-one-select-field-metadata-for-integration-tests.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 { createOneViewGroup } from 'test/integration/metadata/suites/view-group/utils/create-one-view-group.util';
import { deleteOneViewGroup } from 'test/integration/metadata/suites/view-group/utils/delete-one-view-group.util';
import { destroyOneViewGroup } from 'test/integration/metadata/suites/view-group/utils/destroy-one-view-group.util';
import { updateOneViewGroup } from 'test/integration/metadata/suites/view-group/utils/update-one-view-group.util';
import { destroyOneView } from 'test/integration/metadata/suites/view/utils/destroy-one-view.util';
import { assertViewGroupStructure } from 'test/integration/utils/view-test.util';
const TEST_NOT_EXISTING_VIEW_GROUP_ID = '20202020-0000-4000-8000-000000000003';
describe('View Group Resolver', () => {
let testViewId: string;
let testObjectMetadataId: string;
let testFieldMetadataId: string;
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myGroupTestObject',
namePlural: 'myGroupTestObjects',
labelSingular: 'My Group Test Object',
labelPlural: 'My Group Test Objects',
icon: 'Icon123',
},
});
testObjectMetadataId = objectMetadataId;
const { selectFieldMetadataId } =
await createOneSelectFieldMetadataForIntegrationTests({
input: {
objectMetadataId: testObjectMetadataId,
},
});
testFieldMetadataId = selectFieldMetadataId;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testObjectMetadataId },
});
});
beforeEach(async () => {
const view = await createTestViewWithGraphQL({
name: 'Test View for Groups',
objectMetadataId: testObjectMetadataId,
mainGroupByFieldMetadataId: testFieldMetadataId,
});
testViewId = view.id;
});
afterEach(async () => {
await destroyOneView({
viewId: testViewId,
expectToFail: false,
});
});
describe('createViewGroup', () => {
it('should create a new view group', async () => {
const { data } = await createOneViewGroup({
expectToFail: false,
input: {
viewId: testViewId,
isVisible: false,
fieldValue: 'inactive',
position: 1,
},
});
assertViewGroupStructure(data.createViewGroup, {
isVisible: false,
fieldValue: 'inactive',
position: 1,
viewId: testViewId,
});
});
it('should create a view group with null fieldValue', async () => {
const { data } = await createOneViewGroup({
expectToFail: false,
input: {
viewId: testViewId,
isVisible: true,
fieldValue: '',
position: 2,
},
});
assertViewGroupStructure(data.createViewGroup, {
isVisible: true,
fieldValue: '',
position: 2,
});
});
});
describe('updateViewGroup', () => {
it('should update an existing view group', async () => {
const { data: createData } = await createOneViewGroup({
expectToFail: false,
input: {
viewId: testViewId,
isVisible: true,
fieldValue: 'original',
position: 0,
},
});
const viewGroup = createData.createViewGroup;
const { data } = await updateOneViewGroup({
expectToFail: false,
input: {
id: viewGroup.id,
update: {
isVisible: false,
fieldValue: 'updated',
position: 5,
},
},
});
expect(data.updateViewGroup).toMatchObject({
id: viewGroup.id,
isVisible: false,
fieldValue: 'updated',
position: 5,
});
});
it('should throw an error when updating non-existent view group', async () => {
const { errors } = await updateOneViewGroup({
expectToFail: true,
input: {
id: TEST_NOT_EXISTING_VIEW_GROUP_ID,
update: {
isVisible: false,
},
},
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});
describe('deleteViewGroup', () => {
it('should delete an existing view group', async () => {
const { data: createData } = await createOneViewGroup({
expectToFail: false,
input: {
viewId: testViewId,
isVisible: true,
fieldValue: 'to delete',
position: 0,
},
});
const viewGroup = createData.createViewGroup;
const { data } = await deleteOneViewGroup({
expectToFail: false,
input: {
id: viewGroup.id,
},
});
expect(data.deleteViewGroup).toMatchObject({
id: viewGroup.id,
});
expect(data.deleteViewGroup.deletedAt).toBeDefined();
});
it('should throw an error when deleting non-existent view group', async () => {
const { errors } = await deleteOneViewGroup({
expectToFail: true,
input: {
id: TEST_NOT_EXISTING_VIEW_GROUP_ID,
},
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});
describe('destroyViewGroup', () => {
it('should destroy an existing view group', async () => {
const { data: createData } = await createOneViewGroup({
expectToFail: false,
input: {
viewId: testViewId,
isVisible: true,
fieldValue: 'to destroy',
position: 0,
},
});
const viewGroup = createData.createViewGroup;
await deleteOneViewGroup({
input: {
id: viewGroup.id,
},
expectToFail: false,
});
const { data } = await destroyOneViewGroup({
expectToFail: false,
input: {
id: viewGroup.id,
},
});
expect(data.destroyViewGroup).toMatchObject({
id: viewGroup.id,
});
});
it('should throw an error when destroying non-existent view group', async () => {
const { errors } = await destroyOneViewGroup({
expectToFail: true,
input: {
id: TEST_NOT_EXISTING_VIEW_GROUP_ID,
},
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});
});

View File

@@ -0,0 +1,268 @@
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
import { createOneSelectFieldMetadataForIntegrationTests } from 'test/integration/metadata/suites/field-metadata/utils/create-one-select-field-metadata-for-integration-tests.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 { createManyViewGroups } from 'test/integration/metadata/suites/view-group/utils/create-many-view-groups.util';
import { deleteOneViewGroup } from 'test/integration/metadata/suites/view-group/utils/delete-one-view-group.util';
import { destroyOneViewGroup } from 'test/integration/metadata/suites/view-group/utils/destroy-one-view-group.util';
import { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input';
describe('View Group Resolver - Successful Create Many Operations - v2', () => {
let testSetup: {
testViewId: string;
testObjectMetadataId: string;
firstTestFieldMetadataId: string;
secondTestFieldMetadataId: string;
thirdTestFieldMetadataId: string;
};
let createdViewGroupIds: string[] = [];
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myGroupTestObjectV2',
namePlural: 'myGroupTestObjectsV2',
labelSingular: 'My Group Test Object v2',
labelPlural: 'My Group Test Objects v2',
icon: 'Icon123',
},
});
const {
data: {
createOneField: { id: firstTestFieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'testField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId,
isLabelSyncedWithName: true,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
const {
data: {
createOneField: { id: secondTestFieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'secondTestField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId,
isLabelSyncedWithName: false,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
const {
data: {
createOneField: { id: thirdTestFieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'thirdTestField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId,
isLabelSyncedWithName: false,
},
gqlFields: `
id
name
label
isLabelSyncedWithName
`,
});
const { selectFieldMetadataId } =
await createOneSelectFieldMetadataForIntegrationTests({
input: {
objectMetadataId,
},
});
const {
data: {
createView: { id: testViewId },
},
} = await createOneView({
input: {
icon: 'icon123',
objectMetadataId,
name: 'TestViewForGroups',
mainGroupByFieldMetadataId: selectFieldMetadataId,
},
expectToFail: false,
});
testSetup = {
testViewId,
testObjectMetadataId: objectMetadataId,
firstTestFieldMetadataId,
secondTestFieldMetadataId,
thirdTestFieldMetadataId,
};
});
afterAll(async () => {
await updateOneObjectMetadata({
input: {
idToUpdate: testSetup.testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testSetup.testObjectMetadataId },
});
});
afterEach(async () => {
for (const viewGroupId of createdViewGroupIds) {
if (isDefined(viewGroupId)) {
const {
data: { deleteViewGroup },
} = await deleteOneViewGroup({
expectToFail: false,
input: {
id: viewGroupId,
},
});
expect(deleteViewGroup.deletedAt).not.toBeNull();
await destroyOneViewGroup({
expectToFail: false,
input: {
id: viewGroupId,
},
});
}
}
createdViewGroupIds = [];
});
it('should successfully create multiple view groups in batch', async () => {
const inputs: CreateViewGroupInput[] = [
{
viewId: testSetup.testViewId,
position: 0,
isVisible: true,
fieldValue: 'Group A',
},
{
viewId: testSetup.testViewId,
position: 1,
isVisible: false,
fieldValue: 'Group B',
},
{
viewId: testSetup.testViewId,
position: 2,
isVisible: true,
fieldValue: 'Group C',
},
];
const {
data: { createManyViewGroups: createdViewGroups },
errors,
} = await createManyViewGroups({
inputs,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(createdViewGroups).toBeDefined();
expect(createdViewGroups).toHaveLength(3);
createdViewGroups.forEach((viewGroup, index) => {
expect(viewGroup).toMatchObject({
viewId: testSetup.testViewId,
position: inputs[index].position,
isVisible: inputs[index].isVisible,
fieldValue: inputs[index].fieldValue,
});
createdViewGroupIds.push(viewGroup.id);
});
});
it('should successfully create single view group using batch endpoint', async () => {
const inputs: CreateViewGroupInput[] = [
{
viewId: testSetup.testViewId,
position: 5,
isVisible: true,
fieldValue: 'Single Group',
},
];
const {
data: { createManyViewGroups: createdViewGroups },
errors,
} = await createManyViewGroups({
inputs,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(createdViewGroups).toBeDefined();
expect(createdViewGroups).toHaveLength(1);
const viewGroup = createdViewGroups[0];
expect(viewGroup).toMatchObject({
viewId: testSetup.testViewId,
position: 5,
isVisible: true,
fieldValue: 'Single Group',
});
createdViewGroupIds.push(viewGroup.id);
});
it('should return empty array when creating zero view groups', async () => {
const inputs: CreateViewGroupInput[] = [];
const {
data: { createManyViewGroups: createdViewGroups },
errors,
} = await createManyViewGroups({
inputs,
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(createdViewGroups).toBeDefined();
expect(createdViewGroups).toHaveLength(0);
});
});

View File

@@ -0,0 +1,235 @@
import { createOneSelectFieldMetadataForIntegrationTests } from 'test/integration/metadata/suites/field-metadata/utils/create-one-select-field-metadata-for-integration-tests.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 { createManyViewGroups } from 'test/integration/metadata/suites/view-group/utils/create-many-view-groups.util';
import { deleteOneViewGroup } from 'test/integration/metadata/suites/view-group/utils/delete-one-view-group.util';
import { destroyOneViewGroup } from 'test/integration/metadata/suites/view-group/utils/destroy-one-view-group.util';
import { updateManyViewGroups } from 'test/integration/metadata/suites/view-group/utils/update-many-view-groups.util';
import { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { isDefined } from 'twenty-shared/utils';
import { type CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input';
describe('View Group Resolver - Successful Update Many Operations - v2', () => {
let testSetup: {
testViewId: string;
testObjectMetadataId: string;
};
let createdViewGroupIds: string[] = [];
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'myUpdateManyGroupTestObjectV2',
namePlural: 'myUpdateManyGroupTestObjectsV2',
labelSingular: 'My Update Many Group Test Object v2',
labelPlural: 'My Update Many Group Test Objects v2',
icon: 'Icon123',
},
});
const { selectFieldMetadataId } =
await createOneSelectFieldMetadataForIntegrationTests({
input: {
objectMetadataId,
},
});
const {
data: {
createView: { id: testViewId },
},
} = await createOneView({
input: {
icon: 'icon123',
objectMetadataId,
name: 'TestViewForUpdateManyGroups',
mainGroupByFieldMetadataId: selectFieldMetadataId,
},
expectToFail: false,
});
testSetup = {
testViewId,
testObjectMetadataId: objectMetadataId,
};
});
afterAll(async () => {
await updateOneObjectMetadata({
input: {
idToUpdate: testSetup.testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testSetup.testObjectMetadataId },
});
});
afterEach(async () => {
for (const viewGroupId of createdViewGroupIds) {
if (isDefined(viewGroupId)) {
const {
data: { deleteViewGroup },
} = await deleteOneViewGroup({
expectToFail: false,
input: {
id: viewGroupId,
},
});
expect(deleteViewGroup.deletedAt).not.toBeNull();
await destroyOneViewGroup({
expectToFail: false,
input: {
id: viewGroupId,
},
});
}
}
createdViewGroupIds = [];
});
it('should batch-update positions of multiple view groups at once', async () => {
const createInputs: CreateViewGroupInput[] = [
{
viewId: testSetup.testViewId,
position: 0,
isVisible: true,
fieldValue: 'Group A',
},
{
viewId: testSetup.testViewId,
position: 1,
isVisible: true,
fieldValue: 'Group B',
},
{
viewId: testSetup.testViewId,
position: 2,
isVisible: true,
fieldValue: 'Group C',
},
];
const {
data: { createManyViewGroups: createdGroups },
} = await createManyViewGroups({
inputs: createInputs,
expectToFail: false,
});
createdViewGroupIds = createdGroups.map(
(viewGroup: { id: string }) => viewGroup.id,
);
const {
data: { updateManyViewGroups: updatedGroups },
errors,
} = await updateManyViewGroups({
inputs: [
{ id: createdGroups[0].id, update: { position: 2 } },
{ id: createdGroups[1].id, update: { position: 0 } },
{ id: createdGroups[2].id, update: { position: 1 } },
],
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(updatedGroups).toBeDefined();
expect(updatedGroups).toHaveLength(3);
expect(updatedGroups[0]).toMatchObject({
id: createdGroups[0].id,
position: 2,
});
expect(updatedGroups[1]).toMatchObject({
id: createdGroups[1].id,
position: 0,
});
expect(updatedGroups[2]).toMatchObject({
id: createdGroups[2].id,
position: 1,
});
});
it('should batch-update visibility of multiple view groups at once', async () => {
const createInputs: CreateViewGroupInput[] = [
{
viewId: testSetup.testViewId,
position: 0,
isVisible: true,
fieldValue: 'Visible Group',
},
{
viewId: testSetup.testViewId,
position: 1,
isVisible: true,
fieldValue: 'To Be Hidden Group',
},
];
const {
data: { createManyViewGroups: createdGroups },
} = await createManyViewGroups({
inputs: createInputs,
expectToFail: false,
});
createdViewGroupIds = createdGroups.map(
(viewGroup: { id: string }) => viewGroup.id,
);
const {
data: { updateManyViewGroups: updatedGroups },
errors,
} = await updateManyViewGroups({
inputs: [
{ id: createdGroups[0].id, update: { isVisible: false } },
{
id: createdGroups[1].id,
update: { isVisible: false, position: 5 },
},
],
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(updatedGroups).toBeDefined();
expect(updatedGroups).toHaveLength(2);
expect(updatedGroups[0]).toMatchObject({
id: createdGroups[0].id,
isVisible: false,
});
expect(updatedGroups[1]).toMatchObject({
id: createdGroups[1].id,
isVisible: false,
position: 5,
});
});
it('should return empty array for empty inputs', async () => {
const {
data: { updateManyViewGroups: updatedGroups },
errors,
} = await updateManyViewGroups({
inputs: [],
expectToFail: false,
});
expect(errors).toBeUndefined();
expect(updatedGroups).toBeDefined();
expect(updatedGroups).toHaveLength(0);
});
});

View File

@@ -0,0 +1,291 @@
import { createViewSortOperationFactory } from 'test/integration/graphql/utils/create-view-sort-operation-factory.util';
import { deleteViewSortOperationFactory } from 'test/integration/graphql/utils/delete-view-sort-operation-factory.util';
import { destroyViewSortOperationFactory } from 'test/integration/graphql/utils/destroy-view-sort-operation-factory.util';
import { findViewSortsOperationFactory } from 'test/integration/graphql/utils/find-view-sorts-operation-factory.util';
import {
assertGraphQLErrorResponse,
assertGraphQLSuccessfulResponse,
} from 'test/integration/graphql/utils/graphql-test-assertions.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { updateViewSortOperationFactory } from 'test/integration/graphql/utils/update-view-sort-operation-factory.util';
import {
createViewSortData,
updateViewSortData,
} from 'test/integration/graphql/utils/view-data-factory.util';
import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-graphql.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 { destroyOneView } from 'test/integration/metadata/suites/view/utils/destroy-one-view.util';
import { assertViewSortStructure } from 'test/integration/utils/view-test.util';
import { FieldMetadataType, ViewSortDirection } from 'twenty-shared/types';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import {
generateViewSortExceptionMessage,
ViewSortExceptionMessageKey,
} from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
const TEST_NOT_EXISTING_VIEW_SORT_ID = '20202020-0000-4000-8000-000000000004';
describe('View Sort Resolver', () => {
let testViewId: string;
let testObjectMetadataId: string;
let testFieldMetadataId: string;
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'mySortTestObject',
namePlural: 'mySortTestObjects',
labelSingular: 'My Sort Test Object',
labelPlural: 'My Sort Test Objects',
icon: 'Icon123',
},
});
testObjectMetadataId = objectMetadataId;
const {
data: {
createOneField: { id: fieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'testField',
label: 'Test Field',
type: FieldMetadataType.TEXT,
objectMetadataId: testObjectMetadataId,
isLabelSyncedWithName: true,
},
});
testFieldMetadataId = fieldMetadataId;
});
afterAll(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: testObjectMetadataId },
});
});
beforeEach(async () => {
const view = await createTestViewWithGraphQL({
name: 'Test View for Sorts',
objectMetadataId: testObjectMetadataId,
});
testViewId = view.id;
});
afterEach(async () => {
await destroyOneView({
viewId: testViewId,
expectToFail: false,
});
});
describe('getViewSorts', () => {
it('should return empty array when no view sorts exist', async () => {
const operation = findViewSortsOperationFactory({ viewId: testViewId });
const response = await makeMetadataAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.getViewSorts).toEqual([]);
});
it('should return view sorts for a specific view', async () => {
const sortData = createViewSortData(testViewId, {
direction: ViewSortDirection.ASC,
fieldMetadataId: testFieldMetadataId,
});
const createOperation = createViewSortOperationFactory({
data: sortData,
});
await makeMetadataAPIRequest(createOperation);
const getOperation = findViewSortsOperationFactory({
viewId: testViewId,
});
const response = await makeMetadataAPIRequest(getOperation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.getViewSorts).toHaveLength(1);
assertViewSortStructure(response.body.data.getViewSorts[0], {
fieldMetadataId: testFieldMetadataId,
direction: ViewSortDirection.ASC,
viewId: testViewId,
});
});
});
describe('createViewSort', () => {
it('should create a new view sort with ASC direction', async () => {
const sortData = createViewSortData(testViewId, {
direction: ViewSortDirection.ASC,
fieldMetadataId: testFieldMetadataId,
});
const operation = createViewSortOperationFactory({ data: sortData });
const response = await makeMetadataAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
assertViewSortStructure(response.body.data.createViewSort, {
fieldMetadataId: testFieldMetadataId,
direction: ViewSortDirection.ASC,
viewId: testViewId,
});
});
it('should create a view sort with DESC direction', async () => {
const sortData = createViewSortData(testViewId, {
direction: ViewSortDirection.DESC,
fieldMetadataId: testFieldMetadataId,
});
const operation = createViewSortOperationFactory({ data: sortData });
const response = await makeMetadataAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
assertViewSortStructure(response.body.data.createViewSort, {
fieldMetadataId: testFieldMetadataId,
direction: ViewSortDirection.DESC,
viewId: testViewId,
});
});
});
describe('updateViewSort', () => {
it('should update an existing view sort', async () => {
const sortData = createViewSortData(testViewId, {
direction: ViewSortDirection.ASC,
fieldMetadataId: testFieldMetadataId,
});
const createOperation = createViewSortOperationFactory({
data: sortData,
});
const createResponse = await makeMetadataAPIRequest(createOperation);
const viewSort = createResponse.body.data.createViewSort;
const updateInput = updateViewSortData({
direction: ViewSortDirection.DESC,
});
const updateOperation = updateViewSortOperationFactory({
viewSortId: viewSort.id,
data: updateInput,
});
const response = await makeMetadataAPIRequest(updateOperation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.updateViewSort).toMatchObject({
id: viewSort.id,
direction: 'DESC',
});
});
it('should throw an error when updating non-existent view sort', async () => {
const operation = updateViewSortOperationFactory({
viewSortId: TEST_NOT_EXISTING_VIEW_SORT_ID,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND,
),
);
});
});
describe('deleteViewSort', () => {
it('should delete an existing view sort', async () => {
const sortData = createViewSortData(testViewId, {
fieldMetadataId: testFieldMetadataId,
});
const createOperation = createViewSortOperationFactory({
data: sortData,
});
const createResponse = await makeMetadataAPIRequest(createOperation);
const viewSort = createResponse.body.data.createViewSort;
const deleteOperation = deleteViewSortOperationFactory({
viewSortId: viewSort.id,
});
const response = await makeMetadataAPIRequest(deleteOperation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.deleteViewSort).toBe(true);
});
it('should throw an error when deleting non-existent view sort', async () => {
const operation = deleteViewSortOperationFactory({
viewSortId: TEST_NOT_EXISTING_VIEW_SORT_ID,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND,
),
);
});
});
describe('destroyViewSort', () => {
it('should destroy an existing view sort', async () => {
const sortData = createViewSortData(testViewId, {
fieldMetadataId: testFieldMetadataId,
});
const createOperation = createViewSortOperationFactory({
data: sortData,
});
const createResponse = await makeMetadataAPIRequest(createOperation);
const viewSort = createResponse.body.data.createViewSort;
const destroyOperation = destroyViewSortOperationFactory({
viewSortId: viewSort.id,
});
const response = await makeMetadataAPIRequest(destroyOperation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.destroyViewSort).toBe(true);
});
it('should throw an error when destroying non-existent view sort', async () => {
const operation = destroyViewSortOperationFactory({
viewSortId: TEST_NOT_EXISTING_VIEW_SORT_ID,
});
const response = await makeMetadataAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND,
),
);
});
});
});