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,282 @@
import { faker } from '@faker-js/faker';
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 { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input';
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
import { findViewGroups } from 'test/integration/metadata/suites/view-group/utils/find-view-groups.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 {
type FieldMetadataComplexOption,
type FieldMetadataDefaultOption,
FieldMetadataType,
type EnumFieldMetadataType,
ViewType,
} from 'twenty-shared/types';
type Option = FieldMetadataDefaultOption | FieldMetadataComplexOption;
const generateOption = (index: number): Option => ({
label: `Option ${index}`,
value: `OPTION_${index}`,
color: 'green',
position: index,
});
const generateOptions = (length: number) =>
Array.from({ length }, (_value, index) => generateOption(index));
const testFieldMetadataTypes: EnumFieldMetadataType[] = [
FieldMetadataType.SELECT,
FieldMetadataType.MULTI_SELECT,
];
describe('update-one-view-view-groups-side-effect-v2', () => {
let objectMetadataIdToDelete: string;
const createObjectWithTwoSelectFieldsAndKanbanViewAndGroupedTableView =
async (initialOptions: Option[]) => {
const singular = 'viewSideEffect';
const plural = 'viewSideEffects';
const {
data: { createOneObject },
} = await createOneObjectMetadata({
expectToFail: false,
input: getMockCreateObjectInput({
labelSingular: singular,
labelPlural: plural,
nameSingular: singular,
namePlural: plural,
isLabelSyncedWithName: false,
}),
});
objectMetadataIdToDelete = createOneObject.id;
const {
data: { createOneField: createOneStatusField },
} = await createOneFieldMetadata<typeof FieldMetadataType.SELECT>({
expectToFail: false,
input: {
objectMetadataId: createOneObject.id,
type: FieldMetadataType.SELECT,
name: 'statusField',
label: 'Status Field',
isLabelSyncedWithName: true,
options: initialOptions,
},
gqlFields: 'id options',
});
const phaseFieldOptions = generateOptions(2);
const {
data: { createOneField: createOnePhaseField },
} = await createOneFieldMetadata<typeof FieldMetadataType.SELECT>({
expectToFail: false,
input: {
objectMetadataId: createOneObject.id,
type: FieldMetadataType.SELECT,
name: 'phaseField',
label: 'Phase Field',
isLabelSyncedWithName: true,
options: phaseFieldOptions,
},
gqlFields: 'id options',
});
const {
data: { createView: view },
} = await createOneView({
input: {
id: faker.string.uuid(),
icon: 'IconKanban',
name: 'Kanban View',
objectMetadataId: createOneObject.id,
type: ViewType.KANBAN,
mainGroupByFieldMetadataId: createOneStatusField.id,
},
expectToFail: false,
gqlFields: 'id',
});
const {
data: { createView: groupedTableView },
} = await createOneView({
input: {
id: faker.string.uuid(),
icon: 'IconKanban',
name: 'Grouped Table View',
objectMetadataId: createOneObject.id,
type: ViewType.TABLE,
mainGroupByFieldMetadataId: createOneStatusField.id,
},
expectToFail: false,
gqlFields: 'id',
});
return {
objectMetadataId: createOneObject.id,
statusFieldMetadataId: createOneStatusField.id,
phaseFieldMetadataId: createOnePhaseField.id,
statusFieldOptions: createOneStatusField.options ?? [],
viewId: view.id,
groupedTableViewId: groupedTableView.id,
phaseFieldOptions,
};
};
afterEach(async () => {
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: objectMetadataIdToDelete,
updatePayload: {
isActive: false,
},
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: objectMetadataIdToDelete },
});
});
describe.each(testFieldMetadataTypes)('%s field type', () => {
it('should delete all view groups when mainGroupByFieldMetadataId is removed', async () => {
const initialOptions = generateOptions(3);
const { groupedTableViewId } =
await createObjectWithTwoSelectFieldsAndKanbanViewAndGroupedTableView(
initialOptions,
);
const {
data: { getViewGroups: initialViewGroups },
} = await findViewGroups({
viewId: groupedTableViewId,
gqlFields: 'id fieldValue',
expectToFail: false,
});
expect(initialViewGroups.length).toBe(4);
await updateOneView({
viewId: groupedTableViewId,
input: {
id: groupedTableViewId,
mainGroupByFieldMetadataId: null,
},
gqlFields: 'id mainGroupByFieldMetadataId',
expectToFail: false,
});
const {
data: { getViewGroups: updatedViewGroups },
} = await findViewGroups({
viewId: groupedTableViewId,
gqlFields: 'id fieldValue',
expectToFail: false,
});
expect(updatedViewGroups.length).toBe(0);
});
it('should delete and recreate view groups when mainGroupByFieldMetadataId changes', async () => {
const initialOptions = generateOptions(3);
const { phaseFieldOptions, phaseFieldMetadataId, viewId } =
await createObjectWithTwoSelectFieldsAndKanbanViewAndGroupedTableView(
initialOptions,
);
const {
data: { getViewGroups: initialViewGroups },
} = await findViewGroups({
viewId,
gqlFields: 'id fieldValue',
expectToFail: false,
});
expect(initialViewGroups.length).toBe(initialOptions.length + 1); // null option
await updateOneView({
viewId,
input: {
id: viewId,
mainGroupByFieldMetadataId: phaseFieldMetadataId,
},
gqlFields: 'id mainGroupByFieldMetadataId',
expectToFail: false,
});
const {
data: { getViewGroups: updatedViewGroups },
} = await findViewGroups({
viewId,
gqlFields: 'id fieldValue',
expectToFail: false,
});
expect(updatedViewGroups.length).toBe(phaseFieldOptions.length + 1);
const actualFieldValues = updatedViewGroups
.map((viewGroup) => viewGroup.fieldValue)
.sort();
const expectedFieldValues = [...phaseFieldOptions, { value: '' }]
.map((option) => option.value)
.sort();
expect(actualFieldValues).toEqual(expectedFieldValues);
});
it('should preserve view groups when mainGroupByFieldMetadataId does not change', async () => {
const initialOptions = generateOptions(3);
const { statusFieldOptions, viewId } =
await createObjectWithTwoSelectFieldsAndKanbanViewAndGroupedTableView(
initialOptions,
);
const {
data: { getViewGroups: initialViewGroups },
} = await findViewGroups({
viewId,
gqlFields: 'id fieldValue',
expectToFail: false,
});
expect(initialViewGroups.length).toBe(statusFieldOptions.length + 1);
await updateOneView({
viewId,
input: {
id: viewId,
name: 'Renamed Kanban View',
},
gqlFields: 'id name mainGroupByFieldMetadataId',
expectToFail: false,
});
const {
data: { getViewGroups: updatedViewGroups },
} = await findViewGroups({
viewId,
gqlFields: 'id fieldValue',
expectToFail: false,
});
expect(updatedViewGroups.length).toBe(statusFieldOptions.length + 1);
const actualFieldValues = updatedViewGroups
.map((viewGroup) => viewGroup.fieldValue)
.sort();
const expectedFieldValues = [...statusFieldOptions, { value: '' }]
.map((option) => option.value)
.sort();
expect(actualFieldValues).toEqual(expectedFieldValues);
});
});
});

View File

@@ -0,0 +1,498 @@
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-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 { createOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/create-one-page-layout-widget.util';
import { destroyOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/destroy-one-page-layout-widget.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 { 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 { findViewGroups } from 'test/integration/metadata/suites/view-group/utils/find-view-groups.util';
import { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
import { destroyOneView } from 'test/integration/metadata/suites/view/utils/destroy-one-view.util';
import { upsertViewWidget } from 'test/integration/metadata/suites/view/utils/upsert-view-widget.util';
import {
FieldMetadataType,
PageLayoutTabLayoutMode,
ViewCalendarLayout,
ViewType,
WidgetType,
} from 'twenty-shared/types';
import { FieldDisplayMode } from 'src/engine/metadata-modules/page-layout-widget/enums/field-display-mode.enum';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
const VIEW_SETTINGS_GQL_FIELDS = `
id
type
mainGroupByFieldMetadataId
shouldHideEmptyGroups
kanbanAggregateOperation
kanbanAggregateOperationFieldMetadataId
calendarLayout
calendarFieldMetadataId
calendarEndFieldMetadataId
`;
describe('upsertViewWidget view settings', () => {
let objectMetadataId: string;
let selectFieldMetadataId: string;
let dateFieldMetadataId: string;
let pageLayoutId: string;
let pageLayoutTabId: string;
let widgetId: string;
let viewId: string;
beforeAll(async () => {
const {
data: {
createOneObject: { id: createdObjectMetadataId },
},
} = await createOneObjectMetadata({
expectToFail: false,
input: {
nameSingular: 'widgetViewSettingsTestObject',
namePlural: 'widgetViewSettingsTestObjects',
labelSingular: 'Widget View Settings Test Object',
labelPlural: 'Widget View Settings Test Objects',
icon: 'IconTestTube',
},
});
objectMetadataId = createdObjectMetadataId;
const {
data: {
createOneField: { id: createdSelectFieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'stage',
type: FieldMetadataType.SELECT,
label: 'Stage',
objectMetadataId,
options: [
{ label: 'New', value: 'NEW', color: 'blue', position: 0 },
{ label: 'Ongoing', value: 'ONGOING', color: 'red', position: 1 },
{ label: 'Done', value: 'DONE', color: 'green', position: 2 },
],
},
gqlFields: 'id',
});
selectFieldMetadataId = createdSelectFieldMetadataId;
const {
data: {
createOneField: { id: createdDateFieldMetadataId },
},
} = await createOneFieldMetadata({
expectToFail: false,
input: {
name: 'eventDate',
type: FieldMetadataType.DATE,
label: 'Event Date',
objectMetadataId,
},
gqlFields: 'id',
});
dateFieldMetadataId = createdDateFieldMetadataId;
const { data: layoutData } = await createOnePageLayout({
expectToFail: false,
input: { name: 'Test Page Layout For View Settings' },
});
pageLayoutId = layoutData.createPageLayout.id;
const { data: tabData } = await createOnePageLayoutTab({
expectToFail: false,
input: {
title: 'Test Tab For View Settings',
pageLayoutId,
},
});
pageLayoutTabId = tabData.createPageLayoutTab.id;
const { data: viewData } = await createOneView({
expectToFail: false,
input: {
name: 'testWidgetViewSettingsView',
objectMetadataId,
icon: 'IconTable',
type: ViewType.TABLE_WIDGET,
},
});
viewId = viewData.createView.id;
const { data: widgetData } = await createOnePageLayoutWidget({
expectToFail: false,
input: {
title: 'Test Record Table Widget For View Settings',
type: WidgetType.RECORD_TABLE,
pageLayoutTabId,
objectMetadataId,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.RECORD_TABLE,
viewId,
},
},
});
widgetId = widgetData.createPageLayoutWidget.id;
});
afterAll(async () => {
await destroyOnePageLayoutWidget({
expectToFail: false,
input: { id: widgetId },
});
await destroyOneView({
expectToFail: false,
viewId,
});
await destroyOnePageLayoutTab({
expectToFail: false,
input: { id: pageLayoutTabId },
});
await destroyOnePageLayout({
expectToFail: false,
input: { id: pageLayoutId },
});
await updateOneObjectMetadata({
expectToFail: false,
input: {
idToUpdate: objectMetadataId,
updatePayload: { isActive: false },
},
});
await deleteOneObjectMetadata({
expectToFail: false,
input: { idToDelete: objectMetadataId },
});
});
it('should reject a non-widget view type', async () => {
const { errors } = await upsertViewWidget({
expectToFail: true,
input: {
widgetId,
view: {
type: ViewType.KANBAN,
mainGroupByFieldMetadataId: selectFieldMetadataId,
},
},
});
expect(errors?.[0]?.message).toContain(
'Widget views must use a widget view type',
);
});
it('should switch the widget view to LIST_WIDGET', async () => {
const { data } = await upsertViewWidget({
expectToFail: false,
input: {
widgetId,
view: {
type: ViewType.LIST_WIDGET,
},
},
gqlFields: VIEW_SETTINGS_GQL_FIELDS,
});
expect(data.upsertViewWidget.type).toBe(ViewType.LIST_WIDGET);
});
it('should reject switching to KANBAN_WIDGET without a main group by field', async () => {
const { errors } = await upsertViewWidget({
expectToFail: true,
input: {
widgetId,
view: {
type: ViewType.KANBAN_WIDGET,
},
},
});
expect(JSON.stringify(errors)).toContain(
'Kanban view must have a main group by field',
);
});
it('should switch the widget view to KANBAN_WIDGET and auto-create view groups', async () => {
const { data } = await upsertViewWidget({
expectToFail: false,
input: {
widgetId,
view: {
type: ViewType.KANBAN_WIDGET,
mainGroupByFieldMetadataId: selectFieldMetadataId,
},
},
gqlFields: VIEW_SETTINGS_GQL_FIELDS,
});
expect(data.upsertViewWidget.type).toBe(ViewType.KANBAN_WIDGET);
expect(data.upsertViewWidget.mainGroupByFieldMetadataId).toBe(
selectFieldMetadataId,
);
const { data: viewGroupsData } = await findViewGroups({
expectToFail: false,
viewId,
});
const fieldValues = viewGroupsData.getViewGroups.map(
(viewGroup) => viewGroup.fieldValue,
);
expect(fieldValues).toEqual(
expect.arrayContaining(['NEW', 'ONGOING', 'DONE']),
);
});
it('should reject switching to CALENDAR_WIDGET without a calendar field', async () => {
const { errors } = await upsertViewWidget({
expectToFail: true,
input: {
widgetId,
view: {
type: ViewType.CALENDAR_WIDGET,
mainGroupByFieldMetadataId: null,
},
},
});
expect(JSON.stringify(errors)).toContain(
'Calendar view must have a calendar field',
);
});
it.each([ViewCalendarLayout.DAY, ViewCalendarLayout.WEEK])(
'should allow the %s layout on a CALENDAR_WIDGET view',
async (calendarLayout) => {
const { data } = await upsertViewWidget({
expectToFail: false,
input: {
widgetId,
view: {
type: ViewType.CALENDAR_WIDGET,
calendarLayout,
calendarFieldMetadataId: dateFieldMetadataId,
mainGroupByFieldMetadataId: null,
},
},
gqlFields: VIEW_SETTINGS_GQL_FIELDS,
});
expect(data.upsertViewWidget.type).toBe(ViewType.CALENDAR_WIDGET);
expect(data.upsertViewWidget.calendarLayout).toBe(calendarLayout);
},
);
it('should switch the widget view to CALENDAR_WIDGET with a date field and layout', async () => {
const { data } = await upsertViewWidget({
expectToFail: false,
input: {
widgetId,
view: {
type: ViewType.CALENDAR_WIDGET,
calendarLayout: ViewCalendarLayout.MONTH,
calendarFieldMetadataId: dateFieldMetadataId,
mainGroupByFieldMetadataId: null,
},
},
gqlFields: VIEW_SETTINGS_GQL_FIELDS,
});
expect(data.upsertViewWidget.type).toBe(ViewType.CALENDAR_WIDGET);
expect(data.upsertViewWidget.calendarLayout).toBe(ViewCalendarLayout.MONTH);
expect(data.upsertViewWidget.calendarFieldMetadataId).toBe(
dateFieldMetadataId,
);
const { data: viewGroupsData } = await findViewGroups({
expectToFail: false,
viewId,
});
expect(viewGroupsData.getViewGroups).toHaveLength(0);
});
it('should switch the widget view back to TABLE_WIDGET', async () => {
const { data } = await upsertViewWidget({
expectToFail: false,
input: {
widgetId,
view: {
type: ViewType.TABLE_WIDGET,
},
},
gqlFields: VIEW_SETTINGS_GQL_FIELDS,
});
expect(data.upsertViewWidget.type).toBe(ViewType.TABLE_WIDGET);
});
it('should update view settings and view fields in a single call', async () => {
const { data } = await upsertViewWidget({
expectToFail: false,
input: {
widgetId,
view: {
type: ViewType.KANBAN_WIDGET,
mainGroupByFieldMetadataId: selectFieldMetadataId,
shouldHideEmptyGroups: true,
},
viewFields: [
{
fieldMetadataId: selectFieldMetadataId,
isVisible: true,
position: 0,
},
],
},
gqlFields: `
${VIEW_SETTINGS_GQL_FIELDS}
viewFields {
id
fieldMetadataId
isVisible
}
`,
});
expect(data.upsertViewWidget.type).toBe(ViewType.KANBAN_WIDGET);
expect(data.upsertViewWidget.shouldHideEmptyGroups).toBe(true);
expect(
(data.upsertViewWidget.viewFields ?? []).some(
(viewField) => viewField.fieldMetadataId === selectFieldMetadataId,
),
).toBe(true);
});
describe('field widget in table display mode', () => {
let fieldWidgetId: string;
let fieldWidgetViewId: string;
beforeAll(async () => {
const { data: fieldViewData } = await createOneView({
expectToFail: false,
input: {
name: 'testFieldWidgetEmbeddedView',
objectMetadataId,
icon: 'IconTable',
type: ViewType.TABLE_WIDGET,
},
});
fieldWidgetViewId = fieldViewData.createView.id;
const { data: fieldWidgetData } = await createOnePageLayoutWidget({
expectToFail: false,
input: {
title: 'Test Field Table Widget For View Settings',
type: WidgetType.FIELD,
pageLayoutTabId,
objectMetadataId,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 1,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
configuration: {
configurationType: WidgetConfigurationType.FIELD,
fieldMetadataId: selectFieldMetadataId,
fieldDisplayMode: FieldDisplayMode.TABLE,
viewId: fieldWidgetViewId,
},
},
});
fieldWidgetId = fieldWidgetData.createPageLayoutWidget.id;
});
afterAll(async () => {
await destroyOnePageLayoutWidget({
expectToFail: false,
input: { id: fieldWidgetId },
});
await destroyOneView({
expectToFail: false,
viewId: fieldWidgetViewId,
});
});
it('should switch a field table widget embedded view to KANBAN_WIDGET', async () => {
const { data } = await upsertViewWidget({
expectToFail: false,
input: {
widgetId: fieldWidgetId,
view: {
type: ViewType.KANBAN_WIDGET,
mainGroupByFieldMetadataId: selectFieldMetadataId,
},
},
gqlFields: VIEW_SETTINGS_GQL_FIELDS,
});
expect(data.upsertViewWidget.type).toBe(ViewType.KANBAN_WIDGET);
expect(data.upsertViewWidget.mainGroupByFieldMetadataId).toBe(
selectFieldMetadataId,
);
});
it('should switch a field table widget embedded view to CALENDAR_WIDGET', async () => {
const { data } = await upsertViewWidget({
expectToFail: false,
input: {
widgetId: fieldWidgetId,
view: {
type: ViewType.CALENDAR_WIDGET,
calendarLayout: ViewCalendarLayout.MONTH,
calendarFieldMetadataId: dateFieldMetadataId,
mainGroupByFieldMetadataId: null,
},
},
gqlFields: VIEW_SETTINGS_GQL_FIELDS,
});
expect(data.upsertViewWidget.type).toBe(ViewType.CALENDAR_WIDGET);
expect(data.upsertViewWidget.calendarFieldMetadataId).toBe(
dateFieldMetadataId,
);
});
it('should still reject a field table widget switching to KANBAN_WIDGET without a main group by field', async () => {
const { errors } = await upsertViewWidget({
expectToFail: true,
input: {
widgetId: fieldWidgetId,
view: {
type: ViewType.KANBAN_WIDGET,
mainGroupByFieldMetadataId: null,
},
},
});
expect(JSON.stringify(errors)).toContain(
'Kanban view must have a main group by field',
);
});
});
});

View File

@@ -0,0 +1,41 @@
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 CreateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/create-view.input';
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { createViewQueryFactory } from './create-view-query-factory.util';
export const createOneView = async ({
input,
gqlFields,
expectToFail,
}: PerformMetadataQueryParams<CreateViewInput>): CommonResponseBody<{
createView: ViewEntity;
}> => {
const graphqlOperation = createViewQueryFactory({
input,
gqlFields,
});
const response = await makeMetadataAPIRequest(graphqlOperation);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'View creation should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'View creation has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,21 @@
import gql from 'graphql-tag';
import { VIEW_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { type CreateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/create-view.input';
export const createViewQueryFactory = ({
gqlFields = VIEW_GQL_FIELDS,
input,
}: PerformMetadataQueryParams<CreateViewInput>) => ({
query: gql`
mutation CreateView($input: CreateViewInput!) {
createView(input: $input) {
${gqlFields}
}
}
`,
variables: {
input,
},
});

View File

@@ -0,0 +1,37 @@
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { deleteViewQueryFactory } from 'test/integration/metadata/suites/view/utils/delete-view-query-factory.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.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 deleteOneView = async ({
viewId,
expectToFail,
}: {
viewId: string;
expectToFail?: boolean;
}): CommonResponseBody<{
deleteView: boolean;
}> => {
const graphqlOperation = deleteViewQueryFactory({
viewId,
});
const response = await makeMetadataAPIRequest(graphqlOperation);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'View deletion should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'View deletion has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,12 @@
import gql from 'graphql-tag';
export const deleteViewQueryFactory = ({ viewId }: { viewId: string }) => ({
query: gql`
mutation DeleteView($id: String!) {
deleteView(id: $id)
}
`,
variables: {
id: viewId,
},
});

View File

@@ -0,0 +1,37 @@
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { destroyViewQueryFactory } from 'test/integration/metadata/suites/view/utils/destroy-view-query-factory.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.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 destroyOneView = async ({
viewId,
expectToFail,
}: {
viewId: string;
expectToFail?: boolean;
}): CommonResponseBody<{
destroyView: boolean;
}> => {
const graphqlOperation = destroyViewQueryFactory({
viewId,
});
const response = await makeMetadataAPIRequest(graphqlOperation);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'View destruction should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'View destruction has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,12 @@
import gql from 'graphql-tag';
export const destroyViewQueryFactory = ({ viewId }: { viewId: string }) => ({
query: gql`
mutation DestroyView($id: String!) {
destroyView(id: $id)
}
`,
variables: {
id: viewId,
},
});

View File

@@ -0,0 +1,42 @@
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { findViewQueryFactory } from 'test/integration/metadata/suites/view/utils/find-view-query-factory.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.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 ViewDTO } from 'src/engine/metadata-modules/view/dtos/view.dto';
export const findOneView = async ({
viewId,
gqlFields,
expectToFail,
}: {
viewId: string;
gqlFields?: string;
expectToFail?: boolean;
}): CommonResponseBody<{
getView: ViewDTO;
}> => {
const graphqlOperation = findViewQueryFactory({
viewId,
gqlFields,
});
const response = await makeMetadataAPIRequest(graphqlOperation);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'View search should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'View search has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,21 @@
import gql from 'graphql-tag';
import { VIEW_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
export const findViewQueryFactory = ({
gqlFields = VIEW_GQL_FIELDS,
viewId,
}: {
gqlFields?: string;
viewId: string;
}) => ({
query: gql`
query GetView($id: String!) {
getView(id: $id) {
${gqlFields}
}
}
`,
variables: {
id: viewId,
},
});

View File

@@ -0,0 +1,19 @@
import gql from 'graphql-tag';
import { VIEW_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
export const findViewsQueryFactory = ({
gqlFields = VIEW_GQL_FIELDS,
objectMetadataId,
}: {
gqlFields?: string;
objectMetadataId?: string;
}) => ({
query: gql`
query GetViews($objectMetadataId: String) {
getViews(objectMetadataId: $objectMetadataId) {
${gqlFields}
}
}
`,
variables: objectMetadataId ? { objectMetadataId } : {},
});

View File

@@ -0,0 +1,42 @@
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { findViewsQueryFactory } from 'test/integration/metadata/suites/view/utils/find-views-query-factory.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.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 ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
export const findViews = async ({
objectMetadataId,
gqlFields,
expectToFail,
}: {
objectMetadataId?: string;
gqlFields?: string;
expectToFail?: boolean;
}): CommonResponseBody<{
getViews: ViewEntity[];
}> => {
const graphqlOperation = findViewsQueryFactory({
objectMetadataId,
gqlFields,
});
const response = await makeMetadataAPIRequest(graphqlOperation);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'View search should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'View search has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,46 @@
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { updateViewQueryFactory } from 'test/integration/metadata/suites/view/utils/update-view-query-factory.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.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 ViewDTO } from 'src/engine/metadata-modules/view/dtos/view.dto';
import { type UpdateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/update-view.input';
export const updateOneView = async ({
viewId,
input,
gqlFields,
expectToFail,
}: {
viewId: string;
input: UpdateViewInput;
gqlFields?: string;
expectToFail?: boolean;
}): CommonResponseBody<{
updateView: ViewDTO;
}> => {
const graphqlOperation = updateViewQueryFactory({
viewId,
input,
gqlFields,
});
const response = await makeMetadataAPIRequest(graphqlOperation);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'View update should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'View update has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};

View File

@@ -0,0 +1,26 @@
import gql from 'graphql-tag';
import { VIEW_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
import { type UpdateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/update-view.input';
export const updateViewQueryFactory = ({
gqlFields = VIEW_GQL_FIELDS,
viewId,
input,
}: {
gqlFields?: string;
viewId: string;
input: UpdateViewInput;
}) => ({
query: gql`
mutation UpdateView($id: String!, $input: UpdateViewInput!) {
updateView(id: $id, input: $input) {
${gqlFields}
}
}
`,
variables: {
id: viewId,
input,
},
});

View File

@@ -0,0 +1,23 @@
import gql from 'graphql-tag';
import { VIEW_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
import { type UpsertViewWidgetInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget.input';
export const upsertViewWidgetQueryFactory = ({
gqlFields = VIEW_GQL_FIELDS,
input,
}: {
gqlFields?: string;
input: UpsertViewWidgetInput;
}) => ({
query: gql`
mutation UpsertViewWidget($input: UpsertViewWidgetInput!) {
upsertViewWidget(input: $input) {
${gqlFields}
}
}
`,
variables: {
input,
},
});

View File

@@ -0,0 +1,43 @@
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { upsertViewWidgetQueryFactory } from 'test/integration/metadata/suites/view/utils/upsert-view-widget-query-factory.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.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 UpsertViewWidgetInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget.input';
import { type ViewDTO } from 'src/engine/metadata-modules/view/dtos/view.dto';
export const upsertViewWidget = async ({
input,
gqlFields,
expectToFail,
}: {
input: UpsertViewWidgetInput;
gqlFields?: string;
expectToFail?: boolean | null;
}): CommonResponseBody<{
upsertViewWidget: ViewDTO;
}> => {
const graphqlOperation = upsertViewWidgetQueryFactory({
input,
gqlFields,
});
const response = await makeMetadataAPIRequest(graphqlOperation);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'Upsert view widget should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'Upsert view widget has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};