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

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

View File

@@ -0,0 +1,26 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Page layout creation should fail when name is empty string 1`] = `
{
"extensions": {
"code": "BAD_USER_INPUT",
"userFriendlyMessage": "An error occurred.",
},
"message": "name should not be empty",
"name": "UserInputError",
}
`;
exports[`Page layout creation should fail when name is missing 1`] = `
{
"extensions": {
"code": "BAD_USER_INPUT",
"http": {
"status": 400,
},
"userFriendlyMessage": "An error occurred.",
},
"message": "Field "name" of required type "String!" was not provided.",
"name": "GraphQLError",
}
`;

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Page layout deletion should fail when destroying a non-existent page layout 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "Page layout to destroy not found",
"name": "NotFoundError",
}
`;

View File

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

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Page layout with tabs update should fail when updating a non-existent page layout 1`] = `
{
"extensions": {
"code": "NOT_FOUND",
"userFriendlyMessage": "An error occurred.",
},
"message": "Page layout with ID "5b66ba47-198c-4b0d-8583-cd4745f8f9f3" not found",
"name": "NotFoundError",
}
`;

View File

@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Page layout creation should succeed should create a page layout with DASHBOARD type 1`] = `
{
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"name": "Dashboard Layout",
"objectMetadataId": null,
"tabs": null,
"type": "DASHBOARD",
"updatedAt": Any<String>,
}
`;
exports[`Page layout creation should succeed should create a page layout with minimal input 1`] = `
{
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"name": "Test Page Layout",
"objectMetadataId": null,
"tabs": null,
"type": "RECORD_PAGE",
"updatedAt": Any<String>,
}
`;

View File

@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Page layout update should succeed should update page layout name 1`] = `
{
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"name": "Updated Page Layout Name",
"objectMetadataId": null,
"type": "RECORD_PAGE",
"updatedAt": Any<String>,
}
`;
exports[`Page layout update should succeed should update page layout type 1`] = `
{
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"name": "Original Page Layout",
"objectMetadataId": null,
"type": "DASHBOARD",
"updatedAt": Any<String>,
}
`;

View File

@@ -0,0 +1,163 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Page layout widget restore via bulk update should succeed should create, delete, and restore a widget via bulk update 1`] = `
{
"createdAt": Any<String>,
"defaultTabToFocusOnMobileAndSidePanelId": null,
"deletedAt": null,
"id": Any<String>,
"name": "Test Layout For Widget Restore",
"objectMetadataId": null,
"tabs": [
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"icon": null,
"id": Any<String>,
"layoutMode": "GRID",
"pageLayoutId": Any<String>,
"position": 0,
"title": "Test Tab For Widget Restore",
"updatedAt": Any<String>,
"widgets": [
{
"configuration": {
"aggregateFieldMetadataId": Any<String>,
"aggregateOperation": "SUM",
"configurationType": "AGGREGATE_CHART",
"description": null,
"displayDataLabel": true,
"filter": null,
"firstDayOfTheWeek": 1,
"label": null,
"numberFormat": null,
"prefix": null,
"suffix": null,
"timezone": "UTC",
},
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"objectMetadataId": Any<String>,
"pageLayoutTabId": Any<String>,
"position": {
"column": 0,
"columnSpan": 1,
"layoutMode": "GRID",
"row": 0,
"rowSpan": 1,
},
"title": "Restored Graph Widget",
"type": "GRAPH",
"updatedAt": Any<String>,
},
],
},
],
"type": "RECORD_PAGE",
"updatedAt": Any<String>,
}
`;
exports[`Page layout widget restore via bulk update should succeed should handle mixed create, update, and restore in a single call 1`] = `
{
"createdAt": Any<String>,
"defaultTabToFocusOnMobileAndSidePanelId": null,
"deletedAt": null,
"id": Any<String>,
"name": "Test Layout For Widget Restore",
"objectMetadataId": null,
"tabs": [
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"icon": null,
"id": Any<String>,
"layoutMode": "GRID",
"pageLayoutId": Any<String>,
"position": 0,
"title": "Test Tab For Widget Restore",
"updatedAt": Any<String>,
"widgets": [
{
"configuration": {
"configurationType": "IFRAME",
"url": "https://updated.example.com",
},
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"objectMetadataId": null,
"pageLayoutTabId": Any<String>,
"position": {
"column": 0,
"columnSpan": 2,
"layoutMode": "GRID",
"row": 0,
"rowSpan": 2,
},
"title": "Updated Widget Title",
"type": "IFRAME",
"updatedAt": Any<String>,
},
{
"configuration": {
"aggregateFieldMetadataId": Any<String>,
"aggregateOperation": "SUM",
"configurationType": "AGGREGATE_CHART",
"description": null,
"displayDataLabel": true,
"filter": null,
"firstDayOfTheWeek": 1,
"label": null,
"numberFormat": null,
"prefix": null,
"suffix": null,
"timezone": "UTC",
},
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"objectMetadataId": Any<String>,
"pageLayoutTabId": Any<String>,
"position": {
"column": 0,
"columnSpan": 1,
"layoutMode": "GRID",
"row": 1,
"rowSpan": 1,
},
"title": "Restored Widget",
"type": "GRAPH",
"updatedAt": Any<String>,
},
{
"configuration": {
"configurationType": "IFRAME",
"url": "https://new.example.com",
},
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"objectMetadataId": null,
"pageLayoutTabId": Any<String>,
"position": {
"column": 0,
"columnSpan": 1,
"layoutMode": "GRID",
"row": 2,
"rowSpan": 1,
},
"title": "Brand New Widget",
"type": "IFRAME",
"updatedAt": Any<String>,
},
],
},
],
"type": "RECORD_PAGE",
"updatedAt": Any<String>,
}
`;

View File

@@ -0,0 +1,101 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Page layout with tabs creation via update should succeed should create a new tab with a widget via page layout update 1`] = `
{
"createdAt": Any<String>,
"defaultTabToFocusOnMobileAndSidePanelId": null,
"deletedAt": null,
"id": Any<String>,
"name": "Layout With New Tab",
"objectMetadataId": null,
"tabs": [
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"icon": null,
"id": Any<String>,
"layoutMode": "GRID",
"pageLayoutId": Any<String>,
"position": 0,
"title": "Existing Tab",
"updatedAt": Any<String>,
"widgets": [
{
"configuration": {
"aggregateFieldMetadataId": Any<String>,
"aggregateOperation": "COUNT",
"color": null,
"configurationType": "PIE_CHART",
"dateGranularity": "DAY",
"description": null,
"displayDataLabel": false,
"displayLegend": true,
"filter": null,
"firstDayOfTheWeek": 1,
"groupByFieldMetadataId": Any<String>,
"groupBySubFieldName": null,
"manualSortOrder": null,
"numberFormat": null,
"orderBy": "VALUE_DESC",
"showCenterMetric": true,
"timezone": "UTC",
},
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"objectMetadataId": Any<String>,
"pageLayoutTabId": Any<String>,
"position": {
"column": 0,
"columnSpan": 1,
"layoutMode": "GRID",
"row": 0,
"rowSpan": 1,
},
"title": "Existing Tab Widget",
"type": "GRAPH",
"updatedAt": Any<String>,
},
],
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"icon": null,
"id": Any<String>,
"layoutMode": "GRID",
"pageLayoutId": Any<String>,
"position": 1,
"title": "New Tab Created Via Update",
"updatedAt": Any<String>,
"widgets": [
{
"configuration": {
"configurationType": "IFRAME",
"url": "https://example.com",
},
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"objectMetadataId": null,
"pageLayoutTabId": Any<String>,
"position": {
"column": 0,
"columnSpan": 1,
"layoutMode": "GRID",
"row": 0,
"rowSpan": 1,
},
"title": "New Tab Iframe Widget",
"type": "IFRAME",
"updatedAt": Any<String>,
},
],
},
],
"type": "RECORD_PAGE",
"updatedAt": Any<String>,
}
`;

View File

@@ -0,0 +1,166 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Page layout with tabs update should succeed should update page layout with multiple tabs and different widget types 1`] = `
{
"createdAt": Any<String>,
"defaultTabToFocusOnMobileAndSidePanelId": null,
"deletedAt": null,
"id": Any<String>,
"name": "Multi-Tab Layout",
"objectMetadataId": null,
"tabs": [
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"icon": null,
"id": Any<String>,
"layoutMode": "GRID",
"pageLayoutId": Any<String>,
"position": 0,
"title": "First Tab",
"updatedAt": Any<String>,
"widgets": [
{
"configuration": {
"aggregateFieldMetadataId": Any<String>,
"aggregateOperation": "COUNT",
"color": null,
"configurationType": "PIE_CHART",
"dateGranularity": "DAY",
"description": null,
"displayDataLabel": false,
"displayLegend": true,
"filter": null,
"firstDayOfTheWeek": 1,
"groupByFieldMetadataId": Any<String>,
"groupBySubFieldName": null,
"manualSortOrder": null,
"numberFormat": null,
"orderBy": "VALUE_DESC",
"showCenterMetric": true,
"timezone": "UTC",
},
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"objectMetadataId": Any<String>,
"pageLayoutTabId": Any<String>,
"position": {
"column": 0,
"columnSpan": 1,
"layoutMode": "GRID",
"row": 0,
"rowSpan": 1,
},
"title": "Pie Chart Widget",
"type": "GRAPH",
"updatedAt": Any<String>,
},
],
},
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"icon": null,
"id": Any<String>,
"layoutMode": "GRID",
"pageLayoutId": Any<String>,
"position": 1,
"title": "Second Tab",
"updatedAt": Any<String>,
"widgets": [
{
"configuration": {
"configurationType": "IFRAME",
"url": "https://example.com",
},
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"objectMetadataId": null,
"pageLayoutTabId": Any<String>,
"position": {
"column": 0,
"columnSpan": 1,
"layoutMode": "GRID",
"row": 0,
"rowSpan": 1,
},
"title": "Iframe Widget",
"type": "IFRAME",
"updatedAt": Any<String>,
},
],
},
],
"type": "RECORD_PAGE",
"updatedAt": Any<String>,
}
`;
exports[`Page layout with tabs update should succeed should update page layout with single tab and widget 1`] = `
{
"createdAt": Any<String>,
"defaultTabToFocusOnMobileAndSidePanelId": null,
"deletedAt": null,
"id": Any<String>,
"name": "Updated Page Layout",
"objectMetadataId": null,
"tabs": [
{
"applicationId": Any<String>,
"createdAt": Any<String>,
"deletedAt": null,
"icon": null,
"id": Any<String>,
"layoutMode": "GRID",
"pageLayoutId": Any<String>,
"position": 0,
"title": "Updated Tab 1",
"updatedAt": Any<String>,
"widgets": [
{
"configuration": {
"aggregateFieldMetadataId": Any<String>,
"aggregateOperation": "COUNT",
"color": null,
"configurationType": "PIE_CHART",
"dateGranularity": "DAY",
"description": null,
"displayDataLabel": false,
"displayLegend": true,
"filter": null,
"firstDayOfTheWeek": 1,
"groupByFieldMetadataId": Any<String>,
"groupBySubFieldName": null,
"manualSortOrder": null,
"numberFormat": null,
"orderBy": "VALUE_DESC",
"showCenterMetric": true,
"timezone": "UTC",
},
"createdAt": Any<String>,
"deletedAt": null,
"id": Any<String>,
"objectMetadataId": Any<String>,
"pageLayoutTabId": Any<String>,
"position": {
"column": 0,
"columnSpan": 1,
"layoutMode": "GRID",
"row": 0,
"rowSpan": 1,
},
"title": "Pie Chart Widget",
"type": "GRAPH",
"updatedAt": Any<String>,
},
],
},
],
"type": "RECORD_PAGE",
"updatedAt": Any<String>,
}
`;

View File

@@ -0,0 +1,24 @@
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
import { type CreatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout.input';
describe('Page layout creation should fail', () => {
it('when name is missing', async () => {
const { errors } = await createOnePageLayout({
expectToFail: true,
input: {} as CreatePageLayoutInput,
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
it('when name is empty string', async () => {
const { errors } = await createOnePageLayout({
expectToFail: true,
input: { name: '' },
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});

View File

@@ -0,0 +1,14 @@
import { faker } from '@faker-js/faker';
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
describe('Page layout deletion should fail', () => {
it('when destroying a non-existent page layout', async () => {
const { errors } = await destroyOnePageLayout({
expectToFail: true,
input: { id: faker.string.uuid() },
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});

View File

@@ -0,0 +1,17 @@
import { faker } from '@faker-js/faker';
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
import { updateOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/update-one-page-layout.util';
describe('Page layout update should fail', () => {
it('when updating a non-existent page layout', async () => {
const { errors } = await updateOnePageLayout({
expectToFail: true,
input: {
id: faker.string.uuid(),
name: 'Updated Name',
},
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
});

View File

@@ -0,0 +1,269 @@
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 { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util';
import {
fetchTestFieldMetadataIds,
type TestFieldMetadataIds,
} from 'test/integration/metadata/suites/page-layout-widget/utils/fetch-test-field-metadata-ids.util';
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
import { updateOnePageLayoutWithTabsAndWidgets } from 'test/integration/metadata/suites/page-layout/utils/update-one-page-layout-with-tabs-and-widgets.util';
import {
AggregateOperations,
FieldMetadataType,
PageLayoutTabLayoutMode,
PageLayoutType,
ViewFilterOperand,
WidgetType,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
describe('Page layout with tabs update should fail', () => {
it('when updating a non-existent page layout', async () => {
const tabId = 'bb4bd7f2-2f89-429c-9d85-8c20a6f776ea';
const widgetId = 'cf89849b-2106-4b51-8945-359ffeb88141';
const { errors } = await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: true,
input: {
id: '5b66ba47-198c-4b0d-8583-cd4745f8f9f3',
name: 'Updated Name',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: [
{
id: tabId,
title: 'Tab 1',
position: 0,
widgets: [
{
id: widgetId,
pageLayoutTabId: tabId,
title: 'Widget 1',
type: WidgetType.FIELDS,
objectMetadataId: null,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID as const,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.IFRAME,
},
},
],
},
],
},
});
expectOneNotInternalServerErrorSnapshot({ errors });
});
describe('chart filter validation failures', () => {
let testFieldMetadataIds: TestFieldMetadataIds;
let testPageLayoutId: string | undefined;
let testPageLayoutTabId: string | undefined;
let testFilterFieldMetadataId: string | undefined;
let testFilterFieldDeleted = false;
beforeAll(async () => {
testFieldMetadataIds = await fetchTestFieldMetadataIds();
});
beforeEach(async () => {
const filterFieldName = `deletedFilterField${Date.now()}`;
const { data: createdFieldData } = await createOneFieldMetadata({
expectToFail: false,
input: {
objectMetadataId: testFieldMetadataIds.objectMetadataId,
type: FieldMetadataType.TEXT,
label: 'Deleted Filter Field',
name: filterFieldName,
isLabelSyncedWithName: false,
},
});
testFilterFieldMetadataId = createdFieldData.createOneField.id;
const { data: layoutData } = await createOnePageLayout({
expectToFail: false,
input: {
name: 'Deleted Filter Field Layout',
type: PageLayoutType.RECORD_PAGE,
},
});
testPageLayoutId = layoutData.createPageLayout.id;
const { data: tabData } = await createOnePageLayoutTab({
expectToFail: false,
input: {
title: 'Deleted Filter Field Tab',
pageLayoutId: testPageLayoutId,
},
});
testPageLayoutTabId = tabData.createPageLayoutTab.id;
testFilterFieldDeleted = false;
});
afterEach(async () => {
if (isDefined(testFilterFieldMetadataId) && !testFilterFieldDeleted) {
await updateOneFieldMetadata({
expectToFail: false,
input: {
idToUpdate: testFilterFieldMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneFieldMetadata({
expectToFail: false,
input: {
idToDelete: testFilterFieldMetadataId,
},
});
}
if (isDefined(testPageLayoutTabId)) {
await destroyOnePageLayoutTab({
expectToFail: false,
input: { id: testPageLayoutTabId },
});
}
if (isDefined(testPageLayoutId)) {
await destroyOnePageLayout({
expectToFail: false,
input: { id: testPageLayoutId },
});
}
testFilterFieldMetadataId = undefined;
testPageLayoutTabId = undefined;
testPageLayoutId = undefined;
testFilterFieldDeleted = false;
});
it('when saving layout tabs and widgets with a deleted chart filter field', async () => {
if (
!isDefined(testPageLayoutId) ||
!isDefined(testPageLayoutTabId) ||
!isDefined(testFilterFieldMetadataId)
) {
throw new Error('Test setup incomplete');
}
const chartWidgetId = v4();
const chartTitle = 'Opportunities by Name';
const chartConfiguration = {
configurationType: WidgetConfigurationType.PIE_CHART,
aggregateFieldMetadataId: testFieldMetadataIds.fieldMetadataId1,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: testFieldMetadataIds.fieldMetadataId2,
filter: {
recordFilters: [
{
fieldMetadataId: testFilterFieldMetadataId,
operand: ViewFilterOperand.CONTAINS,
value: 'acme',
},
],
recordFilterGroups: [],
},
} satisfies AllPageLayoutWidgetConfiguration;
const tabs = [
{
id: testPageLayoutTabId,
title: 'Deleted Filter Field Tab',
position: 0,
widgets: [
{
id: chartWidgetId,
pageLayoutTabId: testPageLayoutTabId,
title: chartTitle,
type: WidgetType.GRAPH,
objectMetadataId: testFieldMetadataIds.objectMetadataId,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID as const,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: chartConfiguration,
},
],
},
];
await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: 'Deleted Filter Field Layout',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs,
},
});
await updateOneFieldMetadata({
expectToFail: false,
input: {
idToUpdate: testFilterFieldMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneFieldMetadata({
expectToFail: false,
input: {
idToDelete: testFilterFieldMetadataId,
},
});
testFilterFieldDeleted = true;
const { errors } = await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: true,
input: {
id: testPageLayoutId,
name: 'Deleted Filter Field Layout',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs,
},
});
expect(errors).toBeDefined();
expect(errors).toHaveLength(1);
const [firstError] = errors!;
expect(firstError.extensions.code).toBe('BAD_USER_INPUT');
expect(firstError.message).toContain(`Chart "${chartTitle}":`);
expect(firstError.message).toContain(
'Please remove or replace this filter rule.',
);
expect(String(firstError.extensions.userFriendlyMessage)).toContain(
`Chart "${chartTitle}":`,
);
});
});
});

View File

@@ -0,0 +1,67 @@
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
type EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import { PageLayoutType } from 'twenty-shared/types';
type TestContext = {
input: {
name: string;
type?: PageLayoutType;
};
};
const SUCCESSFUL_TEST_CASES: EachTestingContext<TestContext>[] = [
{
title: 'create a page layout with minimal input',
context: {
input: {
name: 'Test Page Layout',
},
},
},
{
title: 'create a page layout with DASHBOARD type',
context: {
input: {
name: 'Dashboard Layout',
type: PageLayoutType.DASHBOARD,
},
},
},
];
describe('Page layout creation should succeed', () => {
let createdPageLayoutId: string;
afterEach(async () => {
if (createdPageLayoutId) {
await destroyOnePageLayout({
expectToFail: false,
input: { id: createdPageLayoutId },
});
createdPageLayoutId = '';
}
});
it.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
'should $title',
async ({ context: { input } }) => {
const { data } = await createOnePageLayout({
expectToFail: false,
input,
});
createdPageLayoutId = data?.createPageLayout?.id;
expect(data.createPageLayout).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny({ ...data.createPageLayout }),
);
},
);
});

View File

@@ -0,0 +1,20 @@
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
describe('Page layout deletion should succeed', () => {
it('should hard delete a page layout', async () => {
const { data: createData } = await createOnePageLayout({
expectToFail: false,
input: { name: 'Page Layout To Destroy' },
});
const pageLayoutId = createData.createPageLayout.id;
const { data: destroyData } = await destroyOnePageLayout({
expectToFail: false,
input: { id: pageLayoutId },
});
expect(destroyData.destroyPageLayout).toBe(true);
});
});

View File

@@ -0,0 +1,76 @@
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
import { updateOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/update-one-page-layout.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
type EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import { PageLayoutType } from 'twenty-shared/types';
type TestContext = {
input: {
name?: string;
type?: PageLayoutType;
};
};
const SUCCESSFUL_TEST_CASES: EachTestingContext<TestContext>[] = [
{
title: 'update page layout name',
context: {
input: {
name: 'Updated Page Layout Name',
},
},
},
{
title: 'update page layout type',
context: {
input: {
type: PageLayoutType.DASHBOARD,
},
},
},
];
describe('Page layout update should succeed', () => {
let testPageLayoutId: string;
beforeEach(async () => {
const { data } = await createOnePageLayout({
expectToFail: false,
input: {
name: 'Original Page Layout',
type: PageLayoutType.RECORD_PAGE,
},
});
testPageLayoutId = data.createPageLayout.id;
});
afterEach(async () => {
await destroyOnePageLayout({
expectToFail: false,
input: { id: testPageLayoutId },
});
});
it.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))(
'should $title',
async ({ context: { input } }) => {
const { data } = await updateOnePageLayout({
expectToFail: false,
input: {
id: testPageLayoutId,
...input,
},
});
expect(data.updatePageLayout).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny({ ...data.updatePageLayout }),
);
},
);
});

View File

@@ -0,0 +1,432 @@
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util';
import { fetchTestFieldMetadataIds } from 'test/integration/metadata/suites/page-layout-widget/utils/fetch-test-field-metadata-ids.util';
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
import { updateOnePageLayoutWithTabsAndWidgets } from 'test/integration/metadata/suites/page-layout/utils/update-one-page-layout-with-tabs-and-widgets.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
AggregateOperations,
PageLayoutTabLayoutMode,
PageLayoutType,
WidgetType,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
describe('Page layout widget restore via bulk update should succeed', () => {
let testFieldMetadataIds: {
objectMetadataId: string;
fieldMetadataId1: string;
fieldMetadataId2: string;
};
let testPageLayoutId: string;
let testTabId: string;
beforeAll(async () => {
const allFieldMetadataIds = await fetchTestFieldMetadataIds();
testFieldMetadataIds = {
objectMetadataId: allFieldMetadataIds.objectMetadataId,
fieldMetadataId1: allFieldMetadataIds.fieldMetadataId1,
fieldMetadataId2: allFieldMetadataIds.fieldMetadataId2,
};
const { data: layoutData } = await createOnePageLayout({
expectToFail: false,
input: {
name: 'Test Layout For Widget Restore',
type: PageLayoutType.RECORD_PAGE,
},
});
testPageLayoutId = layoutData.createPageLayout.id;
const { data: tabData } = await createOnePageLayoutTab({
expectToFail: false,
input: {
title: 'Test Tab For Widget Restore',
pageLayoutId: testPageLayoutId,
},
});
testTabId = tabData.createPageLayoutTab.id;
});
afterAll(async () => {
await destroyOnePageLayoutTab({
expectToFail: false,
input: { id: testTabId },
});
await destroyOnePageLayout({
expectToFail: false,
input: { id: testPageLayoutId },
});
});
it('should create, delete, and restore a widget via bulk update', async () => {
const widgetId = v4();
const { data: createData } = await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: 'Test Layout For Widget Restore',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: [
{
id: testTabId,
title: 'Test Tab For Widget Restore',
position: 0,
widgets: [
{
id: widgetId,
pageLayoutTabId: testTabId,
title: 'Graph Widget',
type: WidgetType.GRAPH,
objectMetadataId: testFieldMetadataIds.objectMetadataId,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.PIE_CHART,
aggregateFieldMetadataId:
testFieldMetadataIds.fieldMetadataId1,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: testFieldMetadataIds.fieldMetadataId2,
},
},
],
},
],
},
});
const createdTab = createData.updatePageLayoutWithTabsAndWidgets.tabs![0];
expect(createdTab.widgets).toHaveLength(1);
expect(createdTab.widgets![0].id).toBe(widgetId);
const { data: deleteData } = await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: 'Test Layout For Widget Restore',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: [
{
id: testTabId,
title: 'Test Tab For Widget Restore',
position: 0,
widgets: [],
},
],
},
});
const deletedTab = deleteData.updatePageLayoutWithTabsAndWidgets.tabs![0];
expect(deletedTab.widgets).toHaveLength(0);
const { data: restoreData } = await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: 'Test Layout For Widget Restore',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: [
{
id: testTabId,
title: 'Test Tab For Widget Restore',
position: 0,
widgets: [
{
id: widgetId,
pageLayoutTabId: testTabId,
title: 'Restored Graph Widget',
type: WidgetType.GRAPH,
objectMetadataId: testFieldMetadataIds.objectMetadataId,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId:
testFieldMetadataIds.fieldMetadataId1,
aggregateOperation: AggregateOperations.SUM,
displayDataLabel: true,
},
},
],
},
],
},
});
const restoredTab = restoreData.updatePageLayoutWithTabsAndWidgets.tabs![0];
expect(restoredTab.widgets).toHaveLength(1);
expect(restoredTab.widgets![0]).toMatchObject({
id: widgetId,
title: 'Restored Graph Widget',
deletedAt: null,
});
expect(restoreData.updatePageLayoutWithTabsAndWidgets).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny({
...restoreData.updatePageLayoutWithTabsAndWidgets,
}),
);
});
it('should handle mixed create, update, and restore in a single call', async () => {
const widgetToUpdateId = v4();
const widgetToRestoreId = v4();
await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: 'Test Layout For Widget Restore',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: [
{
id: testTabId,
title: 'Test Tab For Widget Restore',
position: 0,
widgets: [
{
id: widgetToUpdateId,
pageLayoutTabId: testTabId,
title: 'Widget To Update',
type: WidgetType.IFRAME,
objectMetadataId: null,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.IFRAME,
url: 'https://example.com',
},
},
{
id: widgetToRestoreId,
pageLayoutTabId: testTabId,
title: 'Widget To Restore',
type: WidgetType.GRAPH,
objectMetadataId: testFieldMetadataIds.objectMetadataId,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 1,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId:
testFieldMetadataIds.fieldMetadataId1,
aggregateOperation: AggregateOperations.COUNT,
displayDataLabel: false,
},
},
],
},
],
},
});
await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: 'Test Layout For Widget Restore',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: [
{
id: testTabId,
title: 'Test Tab For Widget Restore',
position: 0,
widgets: [
{
id: widgetToUpdateId,
pageLayoutTabId: testTabId,
title: 'Widget To Update',
type: WidgetType.IFRAME,
objectMetadataId: null,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.IFRAME,
url: 'https://example.com',
},
},
],
},
],
},
});
const newWidgetId = v4();
const { data: mixedData } = await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: 'Test Layout For Widget Restore',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: [
{
id: testTabId,
title: 'Test Tab For Widget Restore',
position: 0,
widgets: [
{
id: widgetToUpdateId,
pageLayoutTabId: testTabId,
title: 'Updated Widget Title',
type: WidgetType.IFRAME,
objectMetadataId: null,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 2,
columnSpan: 2,
},
configuration: {
configurationType: WidgetConfigurationType.IFRAME,
url: 'https://updated.example.com',
},
},
{
id: widgetToRestoreId,
pageLayoutTabId: testTabId,
title: 'Restored Widget',
type: WidgetType.GRAPH,
objectMetadataId: testFieldMetadataIds.objectMetadataId,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 1,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId:
testFieldMetadataIds.fieldMetadataId1,
aggregateOperation: AggregateOperations.SUM,
displayDataLabel: true,
},
},
{
id: newWidgetId,
pageLayoutTabId: testTabId,
title: 'Brand New Widget',
type: WidgetType.IFRAME,
objectMetadataId: null,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 2,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.IFRAME,
url: 'https://new.example.com',
},
},
],
},
],
},
});
const mixedTab = mixedData.updatePageLayoutWithTabsAndWidgets.tabs![0];
expect(mixedTab.widgets).toHaveLength(3);
const updatedWidget = mixedTab.widgets!.find(
(w: { id: string }) => w.id === widgetToUpdateId,
);
const restoredWidget = mixedTab.widgets!.find(
(w: { id: string }) => w.id === widgetToRestoreId,
);
const newWidget = mixedTab.widgets!.find(
(w: { id: string }) => w.id === newWidgetId,
);
expect(updatedWidget).toMatchObject({
title: 'Updated Widget Title',
deletedAt: null,
});
expect(restoredWidget).toMatchObject({
title: 'Restored Widget',
deletedAt: null,
});
expect(newWidget).toMatchObject({
title: 'Brand New Widget',
deletedAt: null,
});
const sortedMixedResult = {
...mixedData.updatePageLayoutWithTabsAndWidgets,
tabs: (mixedData.updatePageLayoutWithTabsAndWidgets.tabs ?? []).map(
(tab) => ({
...tab,
widgets: [...(tab.widgets ?? [])].sort(
(firstWidget, secondWidget) => {
const firstPosition =
firstWidget.position?.layoutMode ===
PageLayoutTabLayoutMode.GRID
? firstWidget.position
: undefined;
const secondPosition =
secondWidget.position?.layoutMode ===
PageLayoutTabLayoutMode.GRID
? secondWidget.position
: undefined;
return (
(firstPosition?.row ?? 0) - (secondPosition?.row ?? 0) ||
(firstPosition?.column ?? 0) - (secondPosition?.column ?? 0)
);
},
),
}),
),
};
expect(sortedMixedResult).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(sortedMixedResult),
);
});
});

View File

@@ -0,0 +1,162 @@
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util';
import { fetchTestFieldMetadataIds } from 'test/integration/metadata/suites/page-layout-widget/utils/fetch-test-field-metadata-ids.util';
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
import { updateOnePageLayoutWithTabsAndWidgets } from 'test/integration/metadata/suites/page-layout/utils/update-one-page-layout-with-tabs-and-widgets.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
AggregateOperations,
PageLayoutTabLayoutMode,
PageLayoutType,
WidgetType,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
describe('Page layout with tabs creation via update should succeed', () => {
let testFieldMetadataIds: {
objectMetadataId: string;
fieldMetadataId1: string;
fieldMetadataId2: string;
};
let testPageLayoutId: string;
let existingTabId: string;
let newTabId: string;
beforeAll(async () => {
const allFieldMetadataIds = await fetchTestFieldMetadataIds();
testFieldMetadataIds = {
objectMetadataId: allFieldMetadataIds.objectMetadataId,
fieldMetadataId1: allFieldMetadataIds.fieldMetadataId1,
fieldMetadataId2: allFieldMetadataIds.fieldMetadataId2,
};
const { data: layoutData } = await createOnePageLayout({
expectToFail: false,
input: {
name: 'Test Page Layout',
type: PageLayoutType.RECORD_PAGE,
},
});
testPageLayoutId = layoutData.createPageLayout.id;
const { data: tabData } = await createOnePageLayoutTab({
expectToFail: false,
input: {
title: 'Initial Tab',
pageLayoutId: testPageLayoutId,
},
});
existingTabId = tabData.createPageLayoutTab.id;
});
afterAll(async () => {
if (newTabId) {
await destroyOnePageLayoutTab({
expectToFail: null,
input: { id: newTabId },
});
}
await destroyOnePageLayoutTab({
expectToFail: false,
input: { id: existingTabId },
});
await destroyOnePageLayout({
expectToFail: false,
input: { id: testPageLayoutId },
});
});
it('should create a new tab with a widget via page layout update', async () => {
const existingTabWidgetId = v4();
newTabId = v4();
const newTabWidgetId = v4();
const { data } = await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: 'Layout With New Tab',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: [
{
id: existingTabId,
title: 'Existing Tab',
position: 0,
widgets: [
{
id: existingTabWidgetId,
pageLayoutTabId: existingTabId,
title: 'Existing Tab Widget',
type: WidgetType.GRAPH,
objectMetadataId: testFieldMetadataIds.objectMetadataId,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.PIE_CHART,
aggregateFieldMetadataId:
testFieldMetadataIds.fieldMetadataId1,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: testFieldMetadataIds.fieldMetadataId2,
},
},
],
},
{
id: newTabId,
title: 'New Tab Created Via Update',
position: 1,
widgets: [
{
id: newTabWidgetId,
pageLayoutTabId: newTabId,
title: 'New Tab Iframe Widget',
type: WidgetType.IFRAME,
objectMetadataId: null,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.IFRAME,
url: 'https://example.com',
},
},
],
},
],
},
});
const result = data.updatePageLayoutWithTabsAndWidgets;
expect(result.tabs).toHaveLength(2);
for (const tab of result.tabs!) {
expect(tab.widgets!.length).toBeGreaterThanOrEqual(1);
}
expect(result).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny({
...result,
}),
);
});
});

View File

@@ -0,0 +1,330 @@
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
import { fetchTestFieldMetadataIds } from 'test/integration/metadata/suites/page-layout-widget/utils/fetch-test-field-metadata-ids.util';
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
import { updateOnePageLayoutWithTabsAndWidgets } from 'test/integration/metadata/suites/page-layout/utils/update-one-page-layout-with-tabs-and-widgets.util';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import {
type EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import {
AggregateOperations,
PageLayoutTabLayoutMode,
PageLayoutType,
type PageLayoutWidgetGridPosition,
WidgetType,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
const MOCK_IFRAME_CONFIGURATION = {
configurationType: WidgetConfigurationType.IFRAME,
url: 'https://example.com',
} as const satisfies AllPageLayoutWidgetConfiguration;
type TestContext = {
layoutName: string;
buildTabs: (params: {
tabId1: string;
tabId2: string;
pieChartWidgetId: string;
iframeWidgetId: string;
}) => Array<{
id: string;
title: string;
position: number;
widgets: Array<{
id: string;
pageLayoutTabId: string;
title: string;
type: WidgetType;
objectMetadataId: string | null;
position: PageLayoutWidgetGridPosition;
configuration: AllPageLayoutWidgetConfiguration;
}>;
}>;
};
describe('Page layout with tabs update should succeed', () => {
let testFieldMetadataIds: {
objectMetadataId: string;
fieldMetadataId1: string;
fieldMetadataId2: string;
fieldMetadataId3: string;
fieldMetadataId3SubFieldName: string;
};
let testPageLayoutId: string;
let testTabId1: string;
let testTabId2: string;
const testCases: EachTestingContext<TestContext>[] = [
{
title: 'update page layout with single tab and widget',
context: {
layoutName: 'Updated Page Layout',
buildTabs: ({ tabId1, pieChartWidgetId }) => [
{
id: tabId1,
title: 'Updated Tab 1',
position: 0,
widgets: [
{
id: pieChartWidgetId,
pageLayoutTabId: tabId1,
title: 'Pie Chart Widget',
type: WidgetType.GRAPH,
objectMetadataId: testFieldMetadataIds.objectMetadataId,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.PIE_CHART,
aggregateFieldMetadataId:
testFieldMetadataIds.fieldMetadataId1,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: testFieldMetadataIds.fieldMetadataId2,
},
},
],
},
],
},
},
{
title: 'update page layout with multiple tabs and different widget types',
context: {
layoutName: 'Multi-Tab Layout',
buildTabs: ({ tabId1, tabId2, pieChartWidgetId, iframeWidgetId }) => [
{
id: tabId1,
title: 'First Tab',
position: 0,
widgets: [
{
id: pieChartWidgetId,
pageLayoutTabId: tabId1,
title: 'Pie Chart Widget',
type: WidgetType.GRAPH,
objectMetadataId: testFieldMetadataIds.objectMetadataId,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.PIE_CHART,
aggregateFieldMetadataId:
testFieldMetadataIds.fieldMetadataId1,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: testFieldMetadataIds.fieldMetadataId2,
},
},
],
},
{
id: tabId2,
title: 'Second Tab',
position: 1,
widgets: [
{
id: iframeWidgetId,
pageLayoutTabId: tabId2,
title: 'Iframe Widget',
type: WidgetType.IFRAME,
objectMetadataId: null,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: MOCK_IFRAME_CONFIGURATION,
},
],
},
],
},
},
];
beforeAll(async () => {
testFieldMetadataIds = await fetchTestFieldMetadataIds();
});
beforeEach(async () => {
const { data: layoutData } = await createOnePageLayout({
expectToFail: false,
input: {
name: 'Original Page Layout',
type: PageLayoutType.RECORD_PAGE,
},
});
testPageLayoutId = layoutData.createPageLayout.id;
const { data: tabData1 } = await createOnePageLayoutTab({
expectToFail: false,
input: {
title: 'Tab 1',
pageLayoutId: testPageLayoutId,
},
});
testTabId1 = tabData1.createPageLayoutTab.id;
const { data: tabData2 } = await createOnePageLayoutTab({
expectToFail: false,
input: {
title: 'Tab 2',
pageLayoutId: testPageLayoutId,
},
});
testTabId2 = tabData2.createPageLayoutTab.id;
});
afterEach(async () => {
await destroyOnePageLayout({
expectToFail: false,
input: { id: testPageLayoutId },
});
});
it.each(eachTestingContextFilter(testCases))(
'should $title',
async ({ context: { layoutName, buildTabs } }) => {
const pieChartWidgetId = v4();
const iframeWidgetId = v4();
const tabParams = {
tabId1: testTabId1,
tabId2: testTabId2,
pieChartWidgetId,
iframeWidgetId,
};
const { data } = await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: layoutName,
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: buildTabs(tabParams),
},
});
expect(data.updatePageLayoutWithTabsAndWidgets).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny({
...data.updatePageLayoutWithTabsAndWidgets,
}),
);
},
);
it('should move a widget to another tab when saving layout tabs and widgets', async () => {
const widgetId = v4();
await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: 'Layout Before Widget Move',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: [
{
id: testTabId1,
title: 'Source Tab',
position: 0,
widgets: [
{
id: widgetId,
pageLayoutTabId: testTabId1,
title: 'Iframe Widget',
type: WidgetType.IFRAME,
objectMetadataId: null,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: MOCK_IFRAME_CONFIGURATION,
},
],
},
{
id: testTabId2,
title: 'Destination Tab',
position: 1,
widgets: [],
},
],
},
});
const { data } = await updateOnePageLayoutWithTabsAndWidgets({
expectToFail: false,
input: {
id: testPageLayoutId,
name: 'Layout After Widget Move',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: null,
tabs: [
{
id: testTabId1,
title: 'Source Tab',
position: 0,
widgets: [],
},
{
id: testTabId2,
title: 'Destination Tab',
position: 1,
widgets: [
{
id: widgetId,
pageLayoutTabId: testTabId2,
title: 'Iframe Widget',
type: WidgetType.IFRAME,
objectMetadataId: null,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: MOCK_IFRAME_CONFIGURATION,
},
],
},
],
},
});
const updatedTabs = data.updatePageLayoutWithTabsAndWidgets.tabs ?? [];
const sourceTab = updatedTabs.find((tab) => tab.id === testTabId1);
const destinationTab = updatedTabs.find((tab) => tab.id === testTabId2);
expect(sourceTab?.widgets).toHaveLength(0);
expect(destinationTab?.widgets).toHaveLength(1);
expect(destinationTab?.widgets?.[0]).toMatchObject({
id: widgetId,
pageLayoutTabId: testTabId2,
title: 'Iframe Widget',
});
});
});

View File

@@ -0,0 +1,41 @@
import gql from 'graphql-tag';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { type CreatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout.input';
export type CreateOnePageLayoutFactoryInput = CreatePageLayoutInput;
const DEFAULT_PAGE_LAYOUT_GQL_FIELDS = `
id
name
type
objectMetadataId
createdAt
updatedAt
deletedAt
tabs {
id
title
position
pageLayoutId
createdAt
updatedAt
deletedAt
}
`;
export const createOnePageLayoutQueryFactory = ({
input,
gqlFields = DEFAULT_PAGE_LAYOUT_GQL_FIELDS,
}: PerformMetadataQueryParams<CreateOnePageLayoutFactoryInput>) => ({
query: gql`
mutation CreatePageLayout($input: CreatePageLayoutInput!) {
createPageLayout(input: $input) {
${gqlFields}
}
}
`,
variables: {
input,
},
});

View File

@@ -0,0 +1,43 @@
import {
type CreateOnePageLayoutFactoryInput,
createOnePageLayoutQueryFactory,
} from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout-query-factory.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
export const createOnePageLayout = async ({
input,
gqlFields,
expectToFail = false,
token,
}: PerformMetadataQueryParams<CreateOnePageLayoutFactoryInput>): CommonResponseBody<{
createPageLayout: PageLayoutDTO;
}> => {
const graphqlOperation = createOnePageLayoutQueryFactory({
input,
gqlFields,
});
const response = await makeMetadataAPIRequest(graphqlOperation, token);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'Page layout creation should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'Page layout creation has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,19 @@
import gql from 'graphql-tag';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
export type DestroyOnePageLayoutFactoryInput = {
id: string;
};
export const destroyOnePageLayoutQueryFactory = ({
input,
}: PerformMetadataQueryParams<DestroyOnePageLayoutFactoryInput>) => ({
query: gql`
mutation DestroyPageLayout($id: String!) {
destroyPageLayout(id: $id)
}
`,
variables: {
id: input.id,
},
});

View File

@@ -0,0 +1,39 @@
import {
type DestroyOnePageLayoutFactoryInput,
destroyOnePageLayoutQueryFactory,
} from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout-query-factory.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
export const destroyOnePageLayout = async ({
input,
expectToFail = false,
token,
}: PerformMetadataQueryParams<DestroyOnePageLayoutFactoryInput>): CommonResponseBody<{
destroyPageLayout: boolean;
}> => {
const graphqlOperation = destroyOnePageLayoutQueryFactory({
input,
});
const response = await makeMetadataAPIRequest(graphqlOperation, token);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'Page layout destroy should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'Page layout destroy has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,41 @@
import gql from 'graphql-tag';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
export type FindOnePageLayoutFactoryInput = {
id: string;
};
const DEFAULT_PAGE_LAYOUT_GQL_FIELDS = `
id
name
type
objectMetadataId
createdAt
updatedAt
deletedAt
tabs {
id
title
position
pageLayoutId
createdAt
updatedAt
deletedAt
}
`;
export const findOnePageLayoutQueryFactory = ({
input,
gqlFields = DEFAULT_PAGE_LAYOUT_GQL_FIELDS,
}: PerformMetadataQueryParams<FindOnePageLayoutFactoryInput>) => ({
query: gql`
query GetPageLayout($id: String!) {
getPageLayout(id: $id) {
${gqlFields}
}
}
`,
variables: {
id: input.id,
},
});

View File

@@ -0,0 +1,43 @@
import {
type FindOnePageLayoutFactoryInput,
findOnePageLayoutQueryFactory,
} from 'test/integration/metadata/suites/page-layout/utils/find-one-page-layout-query-factory.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
export const findOnePageLayout = async ({
input,
gqlFields,
expectToFail = false,
token,
}: PerformMetadataQueryParams<FindOnePageLayoutFactoryInput>): CommonResponseBody<{
getPageLayout: PageLayoutDTO | null;
}> => {
const graphqlOperation = findOnePageLayoutQueryFactory({
input,
gqlFields,
});
const response = await makeMetadataAPIRequest(graphqlOperation, token);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'Find page layout should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'Find page layout has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,32 @@
import gql from 'graphql-tag';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
export type FindPageLayoutsFactoryInput = {
objectMetadataId?: string;
} | void;
const DEFAULT_PAGE_LAYOUT_GQL_FIELDS = `
id
name
type
objectMetadataId
createdAt
updatedAt
deletedAt
`;
export const findPageLayoutsQueryFactory = ({
input,
gqlFields = DEFAULT_PAGE_LAYOUT_GQL_FIELDS,
}: PerformMetadataQueryParams<FindPageLayoutsFactoryInput>) => ({
query: gql`
query GetPageLayouts($objectMetadataId: String) {
getPageLayouts(objectMetadataId: $objectMetadataId) {
${gqlFields}
}
}
`,
variables: {
objectMetadataId: input?.objectMetadataId,
},
});

View File

@@ -0,0 +1,43 @@
import {
type FindPageLayoutsFactoryInput,
findPageLayoutsQueryFactory,
} from 'test/integration/metadata/suites/page-layout/utils/find-page-layouts-query-factory.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
export const findPageLayouts = async ({
input,
gqlFields,
expectToFail = false,
token,
}: PerformMetadataQueryParams<FindPageLayoutsFactoryInput>): CommonResponseBody<{
getPageLayouts: PageLayoutDTO[];
}> => {
const graphqlOperation = findPageLayoutsQueryFactory({
input,
gqlFields,
});
const response = await makeMetadataAPIRequest(graphqlOperation, token);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'Find page layouts should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'Find page layouts has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,39 @@
import gql from 'graphql-tag';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { type UpdatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout.input';
export type UpdateOnePageLayoutFactoryInput = {
id: string;
} & UpdatePageLayoutInput;
const DEFAULT_PAGE_LAYOUT_GQL_FIELDS = `
id
name
type
objectMetadataId
createdAt
updatedAt
deletedAt
`;
export const updateOnePageLayoutQueryFactory = ({
input,
gqlFields = DEFAULT_PAGE_LAYOUT_GQL_FIELDS,
}: PerformMetadataQueryParams<UpdateOnePageLayoutFactoryInput>) => ({
query: gql`
mutation UpdatePageLayout($id: String!, $input: UpdatePageLayoutInput!) {
updatePageLayout(id: $id, input: $input) {
${gqlFields}
}
}
`,
variables: {
id: input.id,
input: {
name: input.name,
type: input.type,
objectMetadataId: input.objectMetadataId,
},
},
});

View File

@@ -0,0 +1,71 @@
import { WIDGET_POSITION_GQL_FIELDS } from 'test/integration/metadata/suites/page-layout-widget/constants/widget-position-gql-fields.constant';
import gql from 'graphql-tag';
import { WIDGET_CONFIGURATION_GQL_FIELDS } from 'test/integration/metadata/suites/page-layout-widget/constants/widget-configuration-gql-fields.constant';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { type UpdatePageLayoutWithTabsInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-with-tabs.input';
export type UpdateOnePageLayoutWithTabsAndWidgetsFactoryInput = {
id: string;
} & UpdatePageLayoutWithTabsInput;
const DEFAULT_PAGE_LAYOUT_GQL_FIELDS = `
id
name
type
objectMetadataId
defaultTabToFocusOnMobileAndSidePanelId
createdAt
updatedAt
deletedAt
tabs {
id
applicationId
title
icon
position
layoutMode
pageLayoutId
createdAt
updatedAt
deletedAt
widgets {
id
title
type
pageLayoutTabId
objectMetadataId
position {
${WIDGET_POSITION_GQL_FIELDS}
}
configuration {
${WIDGET_CONFIGURATION_GQL_FIELDS}
}
createdAt
updatedAt
deletedAt
}
}
`;
export const updateOnePageLayoutWithTabsAndWidgetsQueryFactory = ({
input,
gqlFields = DEFAULT_PAGE_LAYOUT_GQL_FIELDS,
}: PerformMetadataQueryParams<UpdateOnePageLayoutWithTabsAndWidgetsFactoryInput>) => ({
query: gql`
mutation UpdatePageLayoutWithTabsAndWidgets($id: String!, $input: UpdatePageLayoutWithTabsInput!) {
updatePageLayoutWithTabsAndWidgets(id: $id, input: $input) {
${gqlFields}
}
}
`,
variables: {
id: input.id,
input: {
name: input.name,
type: input.type,
objectMetadataId: input.objectMetadataId,
tabs: input.tabs,
},
},
});

View File

@@ -0,0 +1,45 @@
import {
type UpdateOnePageLayoutWithTabsAndWidgetsFactoryInput,
updateOnePageLayoutWithTabsAndWidgetsQueryFactory,
} from 'test/integration/metadata/suites/page-layout/utils/update-one-page-layout-with-tabs-and-widgets-query-factory.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
export const updateOnePageLayoutWithTabsAndWidgets = async ({
input,
gqlFields,
expectToFail = false,
token,
}: PerformMetadataQueryParams<UpdateOnePageLayoutWithTabsAndWidgetsFactoryInput>): CommonResponseBody<{
updatePageLayoutWithTabsAndWidgets: PageLayoutDTO;
}> => {
const graphqlOperation = updateOnePageLayoutWithTabsAndWidgetsQueryFactory({
input,
gqlFields,
});
const response = await makeMetadataAPIRequest(graphqlOperation, token);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage:
'Page layout with tabs and widgets update should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage:
'Page layout with tabs and widgets update has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,43 @@
import {
type UpdateOnePageLayoutFactoryInput,
updateOnePageLayoutQueryFactory,
} from 'test/integration/metadata/suites/page-layout/utils/update-one-page-layout-query-factory.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
export const updateOnePageLayout = async ({
input,
gqlFields,
expectToFail = false,
token,
}: PerformMetadataQueryParams<UpdateOnePageLayoutFactoryInput>): CommonResponseBody<{
updatePageLayout: PageLayoutDTO;
}> => {
const graphqlOperation = updateOnePageLayoutQueryFactory({
input,
gqlFields,
});
const response = await makeMetadataAPIRequest(graphqlOperation, token);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'Page layout update should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'Page layout update has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};