Files
twenty/packages/twenty-server/test/integration/graphql/suites/page-layout/page-layout-widget-resolver.integration-spec.ts
T

512 lines
18 KiB
TypeScript

import { TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID } from 'test/integration/constants/test-page-layout-widget-ids.constants';
import { createPageLayoutWidgetOperationFactory } from 'test/integration/graphql/utils/create-page-layout-widget-operation-factory.util';
import { deletePageLayoutTabOperationFactory } from 'test/integration/graphql/utils/delete-page-layout-tab-operation-factory.util';
import { deletePageLayoutWidgetOperationFactory } from 'test/integration/graphql/utils/delete-page-layout-widget-operation-factory.util';
import { destroyPageLayoutWidgetOperationFactory } from 'test/integration/graphql/utils/destroy-page-layout-widget-operation-factory.util';
import { findPageLayoutWidgetOperationFactory } from 'test/integration/graphql/utils/find-page-layout-widget-operation-factory.util';
import { findPageLayoutWidgetsOperationFactory } from 'test/integration/graphql/utils/find-page-layout-widgets-operation-factory.util';
import {
assertGraphQLErrorResponse,
assertGraphQLSuccessfulResponse,
} from 'test/integration/graphql/utils/graphql-test-assertions.util';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import {
cleanupPageLayoutRecordsWithGraphQL,
createTestPageLayoutWithGraphQL,
} from 'test/integration/graphql/utils/page-layout-graphql.util';
import { createTestPageLayoutTabWithGraphQL } from 'test/integration/graphql/utils/page-layout-tab-graphql.util';
import {
cleanupPageLayoutWidgetRecordsWithGraphQL,
createTestPageLayoutWidgetWithGraphQL,
} from 'test/integration/graphql/utils/page-layout-widget-graphql.util';
import { restorePageLayoutWidgetOperationFactory } from 'test/integration/graphql/utils/restore-page-layout-widget-operation-factory.util';
import { updatePageLayoutWidgetOperationFactory } from 'test/integration/graphql/utils/update-page-layout-widget-operation-factory.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 { assertPageLayoutWidgetStructure } from 'test/integration/utils/page-layout-widget-test.util';
import { isDefined } from 'twenty-shared/utils';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { type PageLayoutWidgetEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-widget.entity';
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum';
import {
PageLayoutWidgetExceptionMessageKey,
generatePageLayoutWidgetExceptionMessage,
} from 'src/engine/core-modules/page-layout/exceptions/page-layout-widget.exception';
describe('Page Layout Widget Resolver', () => {
let testObjectMetadataId: string;
let testPageLayoutId: string;
let testPageLayoutTabId: string;
beforeAll(async () => {
const {
data: {
createOneObject: { id: objectMetadataId },
},
} = await createOneObjectMetadata({
input: {
nameSingular: 'myTestPageLayoutWidgetObject',
namePlural: 'myTestPageLayoutWidgetObjects',
labelSingular: 'My Test Page Layout Widget Object',
labelPlural: 'My Test Page Layout Widget Objects',
icon: 'IconWidget',
},
});
testObjectMetadataId = objectMetadataId;
const pageLayout = await createTestPageLayoutWithGraphQL({
name: 'Test Page Layout for Widgets',
type: PageLayoutType.RECORD_PAGE,
objectMetadataId: testObjectMetadataId,
});
testPageLayoutId = pageLayout.id;
const pageLayoutTab = await createTestPageLayoutTabWithGraphQL({
title: 'Test Tab for Widgets',
position: 0,
pageLayoutId: testPageLayoutId,
});
testPageLayoutTabId = pageLayoutTab.id;
});
afterAll(async () => {
await cleanupPageLayoutRecordsWithGraphQL();
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: testObjectMetadataId,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
input: { idToDelete: testObjectMetadataId },
});
});
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(async () => {
await cleanupPageLayoutWidgetRecordsWithGraphQL(testPageLayoutTabId);
});
describe('getPageLayoutWidgets', () => {
it('should return empty array when no page layout widgets exist', async () => {
const operation = findPageLayoutWidgetsOperationFactory({
pageLayoutTabId: testPageLayoutTabId,
});
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.getPageLayoutWidgets).toEqual([]);
});
it('should return all page layout widgets for a specific page layout tab', async () => {
const widgetTitle1 = 'Widget 1';
const widgetTitle2 = 'Widget 2';
const [{ id: widget1Id }, { id: widget2Id }] = await Promise.all([
createTestPageLayoutWidgetWithGraphQL({
title: widgetTitle1,
type: WidgetType.VIEW,
pageLayoutTabId: testPageLayoutTabId,
gridPosition: { row: 0, column: 0, rowSpan: 2, columnSpan: 3 },
}),
createTestPageLayoutWidgetWithGraphQL({
title: widgetTitle2,
type: WidgetType.GRAPH,
pageLayoutTabId: testPageLayoutTabId,
gridPosition: { row: 0, column: 3, rowSpan: 1, columnSpan: 2 },
}),
]);
const operation = findPageLayoutWidgetsOperationFactory({
pageLayoutTabId: testPageLayoutTabId,
});
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
expect(response.body.data.getPageLayoutWidgets).toHaveLength(2);
const widgets: PageLayoutWidgetEntity[] =
response.body.data.getPageLayoutWidgets ?? [];
const widget1 = widgets.find((widget) => widget.id === widget1Id);
const widget2 = widgets.find((widget) => widget.id === widget2Id);
if (!isDefined(widget1) || !isDefined(widget2)) {
throw new Error('Widget not found');
}
assertPageLayoutWidgetStructure(widget1, {
title: widgetTitle1,
type: WidgetType.VIEW,
pageLayoutTabId: testPageLayoutTabId,
});
assertPageLayoutWidgetStructure(widget2, {
title: widgetTitle2,
type: WidgetType.GRAPH,
pageLayoutTabId: testPageLayoutTabId,
});
});
});
describe('getPageLayoutWidget', () => {
it('should throw when page layout widget does not exist', async () => {
const operation = findPageLayoutWidgetOperationFactory({
pageLayoutWidgetId: TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID,
});
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generatePageLayoutWidgetExceptionMessage(
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_WIDGET_NOT_FOUND,
TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID,
),
);
});
it('should return page layout widget when it exists', async () => {
const widgetTitle = 'Widget';
const input = {
title: widgetTitle,
type: WidgetType.FIELDS,
pageLayoutTabId: testPageLayoutTabId,
objectMetadataId: testObjectMetadataId,
gridPosition: { row: 1, column: 1, rowSpan: 1, columnSpan: 2 },
configuration: null,
};
const widget = await createTestPageLayoutWidgetWithGraphQL(input);
const operation = findPageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
});
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
assertPageLayoutWidgetStructure(
response.body.data.getPageLayoutWidget,
input,
);
});
});
describe('createPageLayoutWidget', () => {
it('should create a new page layout widget with all properties', async () => {
const input = {
title: 'New Widget',
type: WidgetType.FIELDS,
pageLayoutTabId: testPageLayoutTabId,
objectMetadataId: testObjectMetadataId,
gridPosition: { row: 2, column: 1, rowSpan: 3, columnSpan: 4 },
configuration: null,
};
const operation = createPageLayoutWidgetOperationFactory({ data: input });
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
const createdWidget = response.body.data.createPageLayoutWidget;
assertPageLayoutWidgetStructure(createdWidget, input);
});
it('should create a page layout widget with minimum required fields', async () => {
const input = {
title: 'Widget',
type: WidgetType.VIEW,
pageLayoutTabId: testPageLayoutTabId,
gridPosition: { row: 0, column: 0, rowSpan: 1, columnSpan: 1 },
};
const operation = createPageLayoutWidgetOperationFactory({ data: input });
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
const createdWidget = response.body.data.createPageLayoutWidget;
assertPageLayoutWidgetStructure(createdWidget, input);
});
});
describe('updatePageLayoutWidget', () => {
it('should update an existing page layout widget', async () => {
const widget = await createTestPageLayoutWidgetWithGraphQL({
title: 'Widget',
type: WidgetType.VIEW,
pageLayoutTabId: testPageLayoutTabId,
gridPosition: { row: 0, column: 0, rowSpan: 1, columnSpan: 1 },
});
const updateInput = {
title: 'Updated Widget',
type: WidgetType.IFRAME,
gridPosition: { row: 1, column: 2, rowSpan: 2, columnSpan: 3 },
configuration: null,
};
const operation = updatePageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
data: updateInput,
});
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
assertPageLayoutWidgetStructure(
response.body.data.updatePageLayoutWidget,
updateInput,
);
});
it('should update only provided fields', async () => {
const widget = await createTestPageLayoutWidgetWithGraphQL({
title: 'Widget',
type: WidgetType.VIEW,
pageLayoutTabId: testPageLayoutTabId,
gridPosition: { row: 0, column: 0, rowSpan: 1, columnSpan: 1 },
});
const updateInput = {
title: 'Updated Widget',
};
const operation = updatePageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
data: updateInput,
});
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLSuccessfulResponse(response);
assertPageLayoutWidgetStructure(
response.body.data.updatePageLayoutWidget,
updateInput,
);
});
it('should throw error when updating non-existent page layout widget', async () => {
const operation = updatePageLayoutWidgetOperationFactory({
pageLayoutWidgetId: TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID,
data: { title: 'Non-existent Widget' },
});
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generatePageLayoutWidgetExceptionMessage(
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_WIDGET_NOT_FOUND,
TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID,
),
);
});
});
describe('deletePageLayoutWidget', () => {
it('should delete an existing page layout widget (soft delete)', async () => {
const input = {
title: 'Widget',
type: WidgetType.VIEW,
pageLayoutTabId: testPageLayoutTabId,
gridPosition: { row: 0, column: 0, rowSpan: 1, columnSpan: 1 },
};
const widget = await createTestPageLayoutWidgetWithGraphQL(input);
const deleteOperation = deletePageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
});
const deleteResponse = await makeGraphqlAPIRequest(deleteOperation);
assertGraphQLSuccessfulResponse(deleteResponse);
assertPageLayoutWidgetStructure(
deleteResponse.body.data.deletePageLayoutWidget,
input,
);
const getOperation = findPageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
});
const getResponse = await makeGraphqlAPIRequest(getOperation);
assertGraphQLErrorResponse(
getResponse,
ErrorCode.NOT_FOUND,
generatePageLayoutWidgetExceptionMessage(
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_WIDGET_NOT_FOUND,
widget.id,
),
);
});
it('should throw an error when deleting non-existent page layout widget', async () => {
const operation = deletePageLayoutWidgetOperationFactory({
pageLayoutWidgetId: TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID,
});
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generatePageLayoutWidgetExceptionMessage(
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_WIDGET_NOT_FOUND,
TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID,
),
);
});
});
describe('destroyPageLayoutWidget', () => {
it('should destroy an existing page layout widget (hard delete)', async () => {
const input = {
title: 'Widget',
type: WidgetType.VIEW,
pageLayoutTabId: testPageLayoutTabId,
gridPosition: { row: 0, column: 0, rowSpan: 1, columnSpan: 1 },
};
const widget = await createTestPageLayoutWidgetWithGraphQL(input);
const destroyOperation = destroyPageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
});
const destroyResponse = await makeGraphqlAPIRequest(destroyOperation);
assertGraphQLSuccessfulResponse(destroyResponse);
expect(destroyResponse.body.data.destroyPageLayoutWidget).toBe(true);
});
it('should throw an error when destroying non-existent page layout widget', async () => {
const operation = destroyPageLayoutWidgetOperationFactory({
pageLayoutWidgetId: TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID,
});
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generatePageLayoutWidgetExceptionMessage(
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_WIDGET_NOT_FOUND,
TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID,
),
);
});
});
describe('restorePageLayoutWidget', () => {
it('should restore a soft deleted page layout widget', async () => {
const input = {
title: 'Widget',
type: WidgetType.GRAPH,
pageLayoutTabId: testPageLayoutTabId,
gridPosition: { row: 2, column: 1, rowSpan: 1, columnSpan: 2 },
configuration: null,
};
const widget = await createTestPageLayoutWidgetWithGraphQL(input);
const deleteOperation = deletePageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
});
const deleteResponse = await makeGraphqlAPIRequest(deleteOperation);
assertGraphQLSuccessfulResponse(deleteResponse);
const restoreOperation = restorePageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
});
const restoreResponse = await makeGraphqlAPIRequest(restoreOperation);
assertGraphQLSuccessfulResponse(restoreResponse);
assertPageLayoutWidgetStructure(
restoreResponse.body.data.restorePageLayoutWidget,
input,
);
const getOperation = findPageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
});
const getResponse = await makeGraphqlAPIRequest(getOperation);
assertGraphQLSuccessfulResponse(getResponse);
assertPageLayoutWidgetStructure(
getResponse.body.data.getPageLayoutWidget,
input,
);
});
it('should throw an error when restoring non-existent page layout widget', async () => {
const operation = restorePageLayoutWidgetOperationFactory({
pageLayoutWidgetId: TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID,
});
const response = await makeGraphqlAPIRequest(operation);
assertGraphQLErrorResponse(
response,
ErrorCode.NOT_FOUND,
generatePageLayoutWidgetExceptionMessage(
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_WIDGET_NOT_FOUND,
TEST_NOT_EXISTING_PAGE_LAYOUT_WIDGET_ID,
),
);
});
it('should throw an error when restoring widget with deleted parent tab', async () => {
const separateTab = await createTestPageLayoutTabWithGraphQL({
title: 'Tab for Widget Restore Test',
pageLayoutId: testPageLayoutId,
position: 1,
});
const input = {
title: 'Widget with Deleted Tab',
type: WidgetType.GRAPH,
pageLayoutTabId: separateTab.id,
gridPosition: { row: 1, column: 1, rowSpan: 1, columnSpan: 1 },
configuration: null,
};
const widget = await createTestPageLayoutWidgetWithGraphQL(input);
const deleteWidgetOperation = deletePageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
});
await makeGraphqlAPIRequest(deleteWidgetOperation);
const deleteTabOperation = deletePageLayoutTabOperationFactory({
pageLayoutTabId: separateTab.id,
});
await makeGraphqlAPIRequest(deleteTabOperation);
const restoreOperation = restorePageLayoutWidgetOperationFactory({
pageLayoutWidgetId: widget.id,
});
const restoreResponse = await makeGraphqlAPIRequest(restoreOperation);
assertGraphQLErrorResponse(
restoreResponse,
ErrorCode.BAD_USER_INPUT,
generatePageLayoutWidgetExceptionMessage(
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
),
);
});
});
});