refactor(navigation-menu-item): validate universal properties instead of ids (#23566)
Follow-up to the discussion on #23485 and closes https://github.com/twentyhq/twenty/issues/23484. `FlatNavigationMenuItemValidatorService` receives `UniversalFlatEntityValidationArgs<'navigationMenuItem'>`, so the entity it validates is a `UniversalFlatNavigationMenuItem`: `viewId`, `pageLayoutId` and `targetObjectMetadataId` do not exist at that scope. The validator read the right universal keys but passed them through a bag of booleans named after the ids (`hasViewId`, `hasPageLayoutId`, ...) and then reported the id names in its errors. Nothing tied a message to the property it checked, so fixing one message string leaves the other five wrong. ## Changes - Replace the private `validateNavigationMenuItemType` boolean bag with `validateNavigationMenuItemTypeRequiredProperties({ flatNavigationMenuItem })` under `flat-navigation-menu-item/validators/utils/`, in line with `validateAgentRequiredProperties` and `validateNavigationMenuItemPageLayoutReferenceCrossEntity`. It takes the universal entity, so a message can only name a property that exists at that scope. - The util is an explicit `switch` on `NavigationMenuItemType` closed by `assertUnreachable`, so adding a type fails to compile until its contract is declared. - Each case validates its own properties instead of checking presence generically: - `FOLDER`: non blank `name` - `OBJECT`, `VIEW`, `PAGE_LAYOUT`: `targetObjectMetadataUniversalIdentifier` / `viewUniversalIdentifier` / `pageLayoutUniversalIdentifier` must be valid uuids - `RECORD`: `targetRecordId` and `targetObjectMetadataUniversalIdentifier`, both uuids, reported separately - `LINK`: `link` must pass `isValidUrl` - Both call sites spread the result; the update path passes the merged `{ ...from, ...update }` entity, which removes the redundant `name` re-merge. `targetRecordId` stays an id: it points at workspace record data rather than metadata, so it has no universal counterpart. ## Behaviour - Errors name the universal property (`viewUniversalIdentifier`) instead of the id (`viewId`). - Blank strings are now uniformly treated as missing; creation previously accepted `link: " "`. - `RECORD` reports each missing property separately instead of one merged error. - Values that are present but malformed are now rejected: non uuid identifiers and links that are not urls. Standard application identifiers are all v4 uuids and the create/update inputs already carry `@IsUUID`, so this only tightens the app manifest path. ## Verification - Unit tests for the util cover each type valid and invalid, blank names, non url links and non uuid identifiers (23 tests pass alongside the sibling suite) - `nx typecheck twenty-server` clean - oxlint (type-aware) and oxfmt clean on the changed files <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23566?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+184
@@ -0,0 +1,184 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
|
||||
import { validateNavigationMenuItemTypeRequiredProperties } from 'src/engine/metadata-modules/flat-navigation-menu-item/validators/utils/validate-navigation-menu-item-type-required-properties.util';
|
||||
import { NavigationMenuItemExceptionCode } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.exception';
|
||||
import { type UniversalFlatNavigationMenuItem } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-navigation-menu-item.type';
|
||||
|
||||
const VALID_UUID = '20202020-b001-4b01-8b01-c0aba11c0001';
|
||||
const OTHER_VALID_UUID = '20202020-b002-4b02-8b02-c0aba11c0002';
|
||||
|
||||
const buildFlatNavigationMenuItem = (
|
||||
overrides: Partial<UniversalFlatNavigationMenuItem>,
|
||||
): UniversalFlatNavigationMenuItem =>
|
||||
({
|
||||
universalIdentifier: VALID_UUID,
|
||||
name: null,
|
||||
link: null,
|
||||
icon: null,
|
||||
color: null,
|
||||
position: 0,
|
||||
targetRecordId: null,
|
||||
userWorkspaceId: null,
|
||||
folderUniversalIdentifier: null,
|
||||
viewUniversalIdentifier: null,
|
||||
pageLayoutUniversalIdentifier: null,
|
||||
targetObjectMetadataUniversalIdentifier: null,
|
||||
...overrides,
|
||||
}) as UniversalFlatNavigationMenuItem;
|
||||
|
||||
describe('validateNavigationMenuItemTypeRequiredProperties', () => {
|
||||
it('should return an error when type is not defined', () => {
|
||||
const errors = validateNavigationMenuItemTypeRequiredProperties({
|
||||
flatNavigationMenuItem: buildFlatNavigationMenuItem({
|
||||
type: undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0]).toMatchObject({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message: 'Navigation menu item type is required',
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
type: NavigationMenuItemType.FOLDER,
|
||||
expectedMessages: ['A name is required for FOLDER type'],
|
||||
},
|
||||
{
|
||||
type: NavigationMenuItemType.OBJECT,
|
||||
expectedMessages: [
|
||||
'A valid targetObjectMetadataUniversalIdentifier is required for OBJECT type',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
expectedMessages: [
|
||||
'A valid viewUniversalIdentifier is required for VIEW type',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: NavigationMenuItemType.RECORD,
|
||||
expectedMessages: [
|
||||
'A valid targetRecordId is required for RECORD type',
|
||||
'A valid targetObjectMetadataUniversalIdentifier is required for RECORD type',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: NavigationMenuItemType.LINK,
|
||||
expectedMessages: ['A valid link is required for LINK type'],
|
||||
},
|
||||
{
|
||||
type: NavigationMenuItemType.PAGE_LAYOUT,
|
||||
expectedMessages: [
|
||||
'A valid pageLayoutUniversalIdentifier is required for PAGE_LAYOUT type',
|
||||
],
|
||||
},
|
||||
])(
|
||||
'should report every missing property for $type type',
|
||||
({ type, expectedMessages }) => {
|
||||
const errors = validateNavigationMenuItemTypeRequiredProperties({
|
||||
flatNavigationMenuItem: buildFlatNavigationMenuItem({ type }),
|
||||
});
|
||||
|
||||
expect(errors.map(({ message }) => message)).toEqual(expectedMessages);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
type: NavigationMenuItemType.FOLDER,
|
||||
overrides: { name: 'My folder' },
|
||||
},
|
||||
{
|
||||
type: NavigationMenuItemType.OBJECT,
|
||||
overrides: { targetObjectMetadataUniversalIdentifier: VALID_UUID },
|
||||
},
|
||||
{
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
overrides: { viewUniversalIdentifier: VALID_UUID },
|
||||
},
|
||||
{
|
||||
type: NavigationMenuItemType.RECORD,
|
||||
overrides: {
|
||||
targetRecordId: VALID_UUID,
|
||||
targetObjectMetadataUniversalIdentifier: OTHER_VALID_UUID,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: NavigationMenuItemType.LINK,
|
||||
overrides: { link: 'https://twenty.com' },
|
||||
},
|
||||
{
|
||||
type: NavigationMenuItemType.PAGE_LAYOUT,
|
||||
overrides: { pageLayoutUniversalIdentifier: VALID_UUID },
|
||||
},
|
||||
])(
|
||||
'should not report any error when $type type properties are valid',
|
||||
({ type, overrides }) => {
|
||||
const errors = validateNavigationMenuItemTypeRequiredProperties({
|
||||
flatNavigationMenuItem: buildFlatNavigationMenuItem({
|
||||
type,
|
||||
...overrides,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
it('should return an error when type is unknown', () => {
|
||||
const errors = validateNavigationMenuItemTypeRequiredProperties({
|
||||
flatNavigationMenuItem: buildFlatNavigationMenuItem({
|
||||
type: 'UNKNOWN_NAVIGATION_MENU_ITEM_TYPE' as NavigationMenuItemType,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0]).toMatchObject({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message:
|
||||
'Unknown navigation menu item type UNKNOWN_NAVIGATION_MENU_ITEM_TYPE',
|
||||
});
|
||||
});
|
||||
|
||||
it('should treat blank folder names as missing', () => {
|
||||
const errors = validateNavigationMenuItemTypeRequiredProperties({
|
||||
flatNavigationMenuItem: buildFlatNavigationMenuItem({
|
||||
type: NavigationMenuItemType.FOLDER,
|
||||
name: ' ',
|
||||
}),
|
||||
});
|
||||
|
||||
expect(errors.map(({ message }) => message)).toEqual([
|
||||
'A name is required for FOLDER type',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should report an error when the link is not a valid url', () => {
|
||||
const errors = validateNavigationMenuItemTypeRequiredProperties({
|
||||
flatNavigationMenuItem: buildFlatNavigationMenuItem({
|
||||
type: NavigationMenuItemType.LINK,
|
||||
link: 'not a link',
|
||||
}),
|
||||
});
|
||||
|
||||
expect(errors.map(({ message }) => message)).toEqual([
|
||||
'A valid link is required for LINK type',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should report an error when a universal identifier is not a valid uuid', () => {
|
||||
const errors = validateNavigationMenuItemTypeRequiredProperties({
|
||||
flatNavigationMenuItem: buildFlatNavigationMenuItem({
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
viewUniversalIdentifier: 'not-a-uuid',
|
||||
}),
|
||||
});
|
||||
|
||||
expect(errors.map(({ message }) => message)).toEqual([
|
||||
'A valid viewUniversalIdentifier is required for VIEW type',
|
||||
]);
|
||||
});
|
||||
});
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import {
|
||||
type AssertUnreachable,
|
||||
NavigationMenuItemType,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined, isValidUrl, isValidUuid } from 'twenty-shared/utils';
|
||||
|
||||
import { NavigationMenuItemExceptionCode } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.exception';
|
||||
import { type UniversalFlatNavigationMenuItem } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-navigation-menu-item.type';
|
||||
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
|
||||
|
||||
type NavigationMenuItemValidationError =
|
||||
FlatEntityValidationError<NavigationMenuItemExceptionCode>;
|
||||
|
||||
const buildInvalidInputError = (
|
||||
message: string,
|
||||
userFriendlyMessage: MessageDescriptor,
|
||||
): NavigationMenuItemValidationError => ({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message,
|
||||
userFriendlyMessage,
|
||||
});
|
||||
|
||||
const validateUuidProperty = ({
|
||||
value,
|
||||
message,
|
||||
userFriendlyMessage,
|
||||
}: {
|
||||
value: string | null | undefined;
|
||||
message: string;
|
||||
userFriendlyMessage: MessageDescriptor;
|
||||
}): NavigationMenuItemValidationError[] =>
|
||||
isDefined(value) && isValidUuid(value)
|
||||
? []
|
||||
: [buildInvalidInputError(message, userFriendlyMessage)];
|
||||
|
||||
export const validateNavigationMenuItemTypeRequiredProperties = ({
|
||||
flatNavigationMenuItem,
|
||||
}: {
|
||||
flatNavigationMenuItem: UniversalFlatNavigationMenuItem;
|
||||
}): NavigationMenuItemValidationError[] => {
|
||||
const {
|
||||
type,
|
||||
name,
|
||||
link,
|
||||
targetRecordId,
|
||||
targetObjectMetadataUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
pageLayoutUniversalIdentifier,
|
||||
} = flatNavigationMenuItem;
|
||||
|
||||
if (!isDefined(type)) {
|
||||
return [
|
||||
buildInvalidInputError(
|
||||
t`Navigation menu item type is required`,
|
||||
msg`Navigation menu item type is required`,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case NavigationMenuItemType.FOLDER: {
|
||||
return isDefined(name) && name.trim() !== ''
|
||||
? []
|
||||
: [
|
||||
buildInvalidInputError(
|
||||
t`A name is required for FOLDER type`,
|
||||
msg`A name is required for FOLDER type`,
|
||||
),
|
||||
];
|
||||
}
|
||||
case NavigationMenuItemType.OBJECT: {
|
||||
return validateUuidProperty({
|
||||
value: targetObjectMetadataUniversalIdentifier,
|
||||
message: t`A valid targetObjectMetadataUniversalIdentifier is required for OBJECT type`,
|
||||
userFriendlyMessage: msg`A valid targetObjectMetadataUniversalIdentifier is required for OBJECT type`,
|
||||
});
|
||||
}
|
||||
case NavigationMenuItemType.VIEW: {
|
||||
return validateUuidProperty({
|
||||
value: viewUniversalIdentifier,
|
||||
message: t`A valid viewUniversalIdentifier is required for VIEW type`,
|
||||
userFriendlyMessage: msg`A valid viewUniversalIdentifier is required for VIEW type`,
|
||||
});
|
||||
}
|
||||
case NavigationMenuItemType.RECORD: {
|
||||
return [
|
||||
...validateUuidProperty({
|
||||
value: targetRecordId,
|
||||
message: t`A valid targetRecordId is required for RECORD type`,
|
||||
userFriendlyMessage: msg`A valid targetRecordId is required for RECORD type`,
|
||||
}),
|
||||
...validateUuidProperty({
|
||||
value: targetObjectMetadataUniversalIdentifier,
|
||||
message: t`A valid targetObjectMetadataUniversalIdentifier is required for RECORD type`,
|
||||
userFriendlyMessage: msg`A valid targetObjectMetadataUniversalIdentifier is required for RECORD type`,
|
||||
}),
|
||||
];
|
||||
}
|
||||
case NavigationMenuItemType.LINK: {
|
||||
return isDefined(link) && isValidUrl(link)
|
||||
? []
|
||||
: [
|
||||
buildInvalidInputError(
|
||||
t`A valid link is required for LINK type`,
|
||||
msg`A valid link is required for LINK type`,
|
||||
),
|
||||
];
|
||||
}
|
||||
case NavigationMenuItemType.PAGE_LAYOUT: {
|
||||
return validateUuidProperty({
|
||||
value: pageLayoutUniversalIdentifier,
|
||||
message: t`A valid pageLayoutUniversalIdentifier is required for PAGE_LAYOUT type`,
|
||||
userFriendlyMessage: msg`A valid pageLayoutUniversalIdentifier is required for PAGE_LAYOUT type`,
|
||||
});
|
||||
}
|
||||
default: {
|
||||
// oxlint-disable-next-line unused-imports/no-unused-vars
|
||||
type UnhandledNavigationMenuItemType = AssertUnreachable<typeof type>;
|
||||
|
||||
return [
|
||||
buildInvalidInputError(
|
||||
t`Unknown navigation menu item type ${type}`,
|
||||
msg`Unknown navigation menu item type ${type}`,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
+17
-126
@@ -1,12 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { validateNavigationMenuItemTypeRequiredProperties } from 'src/engine/metadata-modules/flat-navigation-menu-item/validators/utils/validate-navigation-menu-item-type-required-properties.util';
|
||||
import { NavigationMenuItemExceptionCode } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.exception';
|
||||
import { type MetadataUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-maps.type';
|
||||
import { validateFlatEntityCircularDependency } from 'src/engine/workspace-manager/workspace-migration/utils/validate-flat-entity-circular-dependency.util';
|
||||
@@ -22,96 +21,6 @@ const NAVIGATION_MENU_ITEM_MAX_DEPTH = 2;
|
||||
|
||||
@Injectable()
|
||||
export class FlatNavigationMenuItemValidatorService {
|
||||
private validateNavigationMenuItemType({
|
||||
type,
|
||||
hasTargetRecordId,
|
||||
hasTargetObjectMetadataId,
|
||||
hasViewId,
|
||||
hasLink,
|
||||
hasPageLayoutId,
|
||||
name,
|
||||
}: {
|
||||
type: NavigationMenuItemType | null | undefined;
|
||||
hasTargetRecordId: boolean;
|
||||
hasTargetObjectMetadataId: boolean;
|
||||
hasViewId: boolean;
|
||||
hasLink: boolean;
|
||||
hasPageLayoutId: boolean;
|
||||
name: string | null | undefined;
|
||||
}): FlatEntityValidationError<NavigationMenuItemExceptionCode>[] {
|
||||
const errors: FlatEntityValidationError<NavigationMenuItemExceptionCode>[] =
|
||||
[];
|
||||
|
||||
if (!isDefined(type)) {
|
||||
errors.push({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message: t`Navigation menu item type is required`,
|
||||
userFriendlyMessage: msg`Navigation menu item type is required`,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case NavigationMenuItemType.FOLDER:
|
||||
if (!isDefined(name) || name.trim() === '') {
|
||||
errors.push({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message: t`Folder name is required`,
|
||||
userFriendlyMessage: msg`Folder name is required`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case NavigationMenuItemType.OBJECT:
|
||||
if (!hasTargetObjectMetadataId) {
|
||||
errors.push({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message: t`targetObjectMetadataId is required for OBJECT type`,
|
||||
userFriendlyMessage: msg`targetObjectMetadataId is required for OBJECT type`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case NavigationMenuItemType.VIEW:
|
||||
if (!hasViewId) {
|
||||
errors.push({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message: t`viewId is required for VIEW type`,
|
||||
userFriendlyMessage: msg`viewId is required for VIEW type`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case NavigationMenuItemType.RECORD:
|
||||
if (!hasTargetRecordId || !hasTargetObjectMetadataId) {
|
||||
errors.push({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message: t`targetRecordId and targetObjectMetadataId are required for RECORD type`,
|
||||
userFriendlyMessage: msg`targetRecordId and targetObjectMetadataId are required for RECORD type`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case NavigationMenuItemType.LINK:
|
||||
if (!hasLink) {
|
||||
errors.push({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message: t`link is required for LINK type`,
|
||||
userFriendlyMessage: msg`link is required for LINK type`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case NavigationMenuItemType.PAGE_LAYOUT:
|
||||
if (!hasPageLayoutId) {
|
||||
errors.push({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message: t`pageLayoutId is required for PAGE_LAYOUT type`,
|
||||
userFriendlyMessage: msg`pageLayoutId is required for PAGE_LAYOUT type`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private getCircularDependencyValidationErrors({
|
||||
navigationMenuItemUniversalIdentifier,
|
||||
folderUniversalIdentifier,
|
||||
@@ -173,6 +82,8 @@ export class FlatNavigationMenuItemValidatorService {
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
universalIdentifier: flatNavigationMenuItem.universalIdentifier,
|
||||
name: flatNavigationMenuItem.name,
|
||||
type: flatNavigationMenuItem.type,
|
||||
},
|
||||
metadataName: 'navigationMenuItem',
|
||||
type: 'create',
|
||||
@@ -189,23 +100,11 @@ export class FlatNavigationMenuItemValidatorService {
|
||||
});
|
||||
}
|
||||
|
||||
const typeValidationErrors = this.validateNavigationMenuItemType({
|
||||
type: flatNavigationMenuItem.type,
|
||||
hasTargetRecordId: isDefined(flatNavigationMenuItem.targetRecordId),
|
||||
hasTargetObjectMetadataId: isDefined(
|
||||
flatNavigationMenuItem.targetObjectMetadataUniversalIdentifier,
|
||||
),
|
||||
hasViewId: isDefined(flatNavigationMenuItem.viewUniversalIdentifier),
|
||||
hasLink:
|
||||
isDefined(flatNavigationMenuItem.link) &&
|
||||
isNonEmptyString(flatNavigationMenuItem.link),
|
||||
hasPageLayoutId: isDefined(
|
||||
flatNavigationMenuItem.pageLayoutUniversalIdentifier,
|
||||
),
|
||||
name: flatNavigationMenuItem.name,
|
||||
});
|
||||
|
||||
validationResult.errors.push(...typeValidationErrors);
|
||||
validationResult.errors.push(
|
||||
...validateNavigationMenuItemTypeRequiredProperties({
|
||||
flatNavigationMenuItem,
|
||||
}),
|
||||
);
|
||||
|
||||
if (isDefined(flatNavigationMenuItem.folderUniversalIdentifier)) {
|
||||
const circularDependencyErrors =
|
||||
@@ -257,6 +156,8 @@ export class FlatNavigationMenuItemValidatorService {
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
universalIdentifier: flatEntityToValidate.universalIdentifier,
|
||||
name: flatEntityToValidate.name,
|
||||
type: flatEntityToValidate.type,
|
||||
},
|
||||
metadataName: 'navigationMenuItem',
|
||||
type: 'delete',
|
||||
@@ -295,6 +196,8 @@ export class FlatNavigationMenuItemValidatorService {
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
universalIdentifier,
|
||||
name: flatEntityUpdate.name ?? fromFlatNavigationMenuItem?.name,
|
||||
type: flatEntityUpdate.type ?? fromFlatNavigationMenuItem?.type,
|
||||
},
|
||||
metadataName: 'navigationMenuItem',
|
||||
type: 'update',
|
||||
@@ -325,23 +228,11 @@ export class FlatNavigationMenuItemValidatorService {
|
||||
...flatEntityUpdate,
|
||||
};
|
||||
|
||||
const nameUpdate = flatEntityUpdate.name;
|
||||
|
||||
const typeValidationErrors = this.validateNavigationMenuItemType({
|
||||
type: toFlatNavigationMenuItem.type,
|
||||
hasTargetRecordId: isDefined(toFlatNavigationMenuItem.targetRecordId),
|
||||
hasTargetObjectMetadataId: isDefined(
|
||||
toFlatNavigationMenuItem.targetObjectMetadataUniversalIdentifier,
|
||||
),
|
||||
hasViewId: isDefined(toFlatNavigationMenuItem.viewUniversalIdentifier),
|
||||
hasLink: isNonEmptyString((toFlatNavigationMenuItem.link ?? '').trim()),
|
||||
hasPageLayoutId: isDefined(
|
||||
toFlatNavigationMenuItem.pageLayoutUniversalIdentifier,
|
||||
),
|
||||
name: isDefined(nameUpdate) ? nameUpdate : toFlatNavigationMenuItem.name,
|
||||
});
|
||||
|
||||
validationResult.errors.push(...typeValidationErrors);
|
||||
validationResult.errors.push(
|
||||
...validateNavigationMenuItemTypeRequiredProperties({
|
||||
flatNavigationMenuItem: toFlatNavigationMenuItem,
|
||||
}),
|
||||
);
|
||||
|
||||
const folderUniversalIdentifierUpdate =
|
||||
flatEntityUpdate.folderUniversalIdentifier;
|
||||
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Sync application should fail on invalid navigation menu items when syncing a FOLDER item with a blank name 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"navigationMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_NAVIGATION_MENU_ITEM_INPUT",
|
||||
"message": "A name is required for FOLDER type",
|
||||
"userFriendlyMessage": "A name is required for FOLDER type",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": " ",
|
||||
"type": "FOLDER",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 navigationMenuItem",
|
||||
"summary": {
|
||||
"navigationMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "A name is required for FOLDER type",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail on invalid navigation menu items when syncing a FOLDER item without name 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"navigationMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_NAVIGATION_MENU_ITEM_INPUT",
|
||||
"message": "A name is required for FOLDER type",
|
||||
"userFriendlyMessage": "A name is required for FOLDER type",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": null,
|
||||
"type": "FOLDER",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 navigationMenuItem",
|
||||
"summary": {
|
||||
"navigationMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "A name is required for FOLDER type",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail on invalid navigation menu items when syncing a LINK item with a link that is not a valid url 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"navigationMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_NAVIGATION_MENU_ITEM_INPUT",
|
||||
"message": "A valid link is required for LINK type",
|
||||
"userFriendlyMessage": "A valid link is required for LINK type",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "Link with invalid url",
|
||||
"type": "LINK",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 navigationMenuItem",
|
||||
"summary": {
|
||||
"navigationMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "A valid link is required for LINK type",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail on invalid navigation menu items when syncing a LINK item without link 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"navigationMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_NAVIGATION_MENU_ITEM_INPUT",
|
||||
"message": "A valid link is required for LINK type",
|
||||
"userFriendlyMessage": "A valid link is required for LINK type",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "Link without url",
|
||||
"type": "LINK",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 navigationMenuItem",
|
||||
"summary": {
|
||||
"navigationMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "A valid link is required for LINK type",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail on invalid navigation menu items when syncing a PAGE_LAYOUT item with a pageLayoutUniversalIdentifier that is not a uuid 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"navigationMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_NAVIGATION_MENU_ITEM_INPUT",
|
||||
"message": "A valid pageLayoutUniversalIdentifier is required for PAGE_LAYOUT type",
|
||||
"userFriendlyMessage": "A valid pageLayoutUniversalIdentifier is required for PAGE_LAYOUT type",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "Page layout with malformed identifier",
|
||||
"type": "PAGE_LAYOUT",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 navigationMenuItem",
|
||||
"summary": {
|
||||
"navigationMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "A valid pageLayoutUniversalIdentifier is required for PAGE_LAYOUT type",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail on invalid navigation menu items when syncing a VIEW item with a viewUniversalIdentifier that is not a uuid 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"navigationMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_NAVIGATION_MENU_ITEM_INPUT",
|
||||
"message": "A valid viewUniversalIdentifier is required for VIEW type",
|
||||
"userFriendlyMessage": "A valid viewUniversalIdentifier is required for VIEW type",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "View with malformed identifier",
|
||||
"type": "VIEW",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 navigationMenuItem",
|
||||
"summary": {
|
||||
"navigationMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "A valid viewUniversalIdentifier is required for VIEW type",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail on invalid navigation menu items when syncing an OBJECT item with a targetObjectUniversalIdentifier that is not a uuid 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"navigationMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_NAVIGATION_MENU_ITEM_INPUT",
|
||||
"message": "A valid targetObjectMetadataUniversalIdentifier is required for OBJECT type",
|
||||
"userFriendlyMessage": "A valid targetObjectMetadataUniversalIdentifier is required for OBJECT type",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "Object with malformed target",
|
||||
"type": "OBJECT",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 navigationMenuItem",
|
||||
"summary": {
|
||||
"navigationMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "A valid targetObjectMetadataUniversalIdentifier is required for OBJECT type",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail on invalid navigation menu items when syncing an OBJECT item without targetObjectUniversalIdentifier 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"navigationMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_NAVIGATION_MENU_ITEM_INPUT",
|
||||
"message": "A valid targetObjectMetadataUniversalIdentifier is required for OBJECT type",
|
||||
"userFriendlyMessage": "A valid targetObjectMetadataUniversalIdentifier is required for OBJECT type",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "Object without target",
|
||||
"type": "OBJECT",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 navigationMenuItem",
|
||||
"summary": {
|
||||
"navigationMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "A valid targetObjectMetadataUniversalIdentifier is required for OBJECT type",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail on invalid navigation menu items when syncing an item with an unknown type 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"navigationMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_NAVIGATION_MENU_ITEM_INPUT",
|
||||
"message": "Unknown navigation menu item type UNKNOWN_NAVIGATION_MENU_ITEM_TYPE",
|
||||
"userFriendlyMessage": "Unknown navigation menu item type UNKNOWN_NAVIGATION_MENU_ITEM_TYPE",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "Item with unknown type",
|
||||
"type": "UNKNOWN_NAVIGATION_MENU_ITEM_TYPE",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 navigationMenuItem",
|
||||
"summary": {
|
||||
"navigationMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "Unknown navigation menu item type UNKNOWN_NAVIGATION_MENU_ITEM_TYPE",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util';
|
||||
import { cleanupApplicationAndAppRegistration } from 'test/integration/metadata/suites/application/utils/cleanup-application-and-app-registration.util';
|
||||
import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util';
|
||||
import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util';
|
||||
import { type NavigationMenuItemManifest } from 'twenty-shared/application';
|
||||
import {
|
||||
eachTestingContextFilter,
|
||||
type EachTestingContext,
|
||||
} from 'twenty-shared/testing';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
|
||||
const TEST_APP_ID = 'c1b2c3d4-0001-4000-a000-000000000001';
|
||||
const TEST_ROLE_ID = 'c1b2c3d4-0002-4000-a000-000000000002';
|
||||
const TEST_NAVIGATION_MENU_ITEM_ID = 'c1b2c3d4-0003-4000-a000-000000000003';
|
||||
|
||||
type TestContext = {
|
||||
navigationMenuItem: NavigationMenuItemManifest;
|
||||
};
|
||||
|
||||
const failingNavigationMenuItemSyncTestCases: EachTestingContext<TestContext>[] =
|
||||
[
|
||||
{
|
||||
title: 'when syncing a FOLDER item without name',
|
||||
context: {
|
||||
navigationMenuItem: {
|
||||
universalIdentifier: TEST_NAVIGATION_MENU_ITEM_ID,
|
||||
type: NavigationMenuItemType.FOLDER,
|
||||
position: 0,
|
||||
icon: 'IconFolder',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when syncing a FOLDER item with a blank name',
|
||||
context: {
|
||||
navigationMenuItem: {
|
||||
universalIdentifier: TEST_NAVIGATION_MENU_ITEM_ID,
|
||||
type: NavigationMenuItemType.FOLDER,
|
||||
position: 0,
|
||||
name: ' ',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when syncing a LINK item without link',
|
||||
context: {
|
||||
navigationMenuItem: {
|
||||
universalIdentifier: TEST_NAVIGATION_MENU_ITEM_ID,
|
||||
type: NavigationMenuItemType.LINK,
|
||||
position: 0,
|
||||
name: 'Link without url',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when syncing a LINK item with a link that is not a valid url',
|
||||
context: {
|
||||
navigationMenuItem: {
|
||||
universalIdentifier: TEST_NAVIGATION_MENU_ITEM_ID,
|
||||
type: NavigationMenuItemType.LINK,
|
||||
position: 0,
|
||||
name: 'Link with invalid url',
|
||||
link: 'not a link',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'when syncing an OBJECT item without targetObjectUniversalIdentifier',
|
||||
context: {
|
||||
navigationMenuItem: {
|
||||
universalIdentifier: TEST_NAVIGATION_MENU_ITEM_ID,
|
||||
type: NavigationMenuItemType.OBJECT,
|
||||
position: 0,
|
||||
name: 'Object without target',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'when syncing an OBJECT item with a targetObjectUniversalIdentifier that is not a uuid',
|
||||
context: {
|
||||
navigationMenuItem: {
|
||||
universalIdentifier: TEST_NAVIGATION_MENU_ITEM_ID,
|
||||
type: NavigationMenuItemType.OBJECT,
|
||||
position: 0,
|
||||
name: 'Object with malformed target',
|
||||
targetObjectUniversalIdentifier: 'not-a-uuid',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'when syncing a VIEW item with a viewUniversalIdentifier that is not a uuid',
|
||||
context: {
|
||||
navigationMenuItem: {
|
||||
universalIdentifier: TEST_NAVIGATION_MENU_ITEM_ID,
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
position: 0,
|
||||
name: 'View with malformed identifier',
|
||||
viewUniversalIdentifier: 'not-a-uuid',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'when syncing a PAGE_LAYOUT item with a pageLayoutUniversalIdentifier that is not a uuid',
|
||||
context: {
|
||||
navigationMenuItem: {
|
||||
universalIdentifier: TEST_NAVIGATION_MENU_ITEM_ID,
|
||||
type: NavigationMenuItemType.PAGE_LAYOUT,
|
||||
position: 0,
|
||||
name: 'Page layout with malformed identifier',
|
||||
pageLayoutUniversalIdentifier: 'not-a-uuid',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when syncing an item with an unknown type',
|
||||
context: {
|
||||
navigationMenuItem: {
|
||||
universalIdentifier: TEST_NAVIGATION_MENU_ITEM_ID,
|
||||
type: 'UNKNOWN_NAVIGATION_MENU_ITEM_TYPE' as NavigationMenuItemType,
|
||||
position: 0,
|
||||
name: 'Item with unknown type',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Sync application should fail on invalid navigation menu items', () => {
|
||||
beforeAll(async () => {
|
||||
await setupApplicationForSync({
|
||||
applicationUniversalIdentifier: TEST_APP_ID,
|
||||
name: 'Test Invalid Navigation Menu Item App',
|
||||
description: 'App for testing navigation menu item manifest validation',
|
||||
sourcePath: 'test-invalid-navigation-menu-item',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupApplicationAndAppRegistration({
|
||||
applicationUniversalIdentifier: TEST_APP_ID,
|
||||
});
|
||||
});
|
||||
|
||||
it.each(eachTestingContextFilter(failingNavigationMenuItemSyncTestCases))(
|
||||
'$title',
|
||||
async ({ context }) => {
|
||||
const { errors } = await syncApplication({
|
||||
manifest: buildBaseManifest({
|
||||
appId: TEST_APP_ID,
|
||||
roleId: TEST_ROLE_ID,
|
||||
overrides: {
|
||||
navigationMenuItems: [context.navigationMenuItem],
|
||||
},
|
||||
}),
|
||||
expectToFail: true,
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
},
|
||||
60000,
|
||||
);
|
||||
});
|
||||
+6
-4
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NavigationMenuItem creation should fail when creating with empty targetObjectMetadataId 1`] = `
|
||||
{
|
||||
@@ -100,11 +100,13 @@ exports[`NavigationMenuItem creation should fail when creating with missing targ
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_NAVIGATION_MENU_ITEM_INPUT",
|
||||
"message": "targetRecordId and targetObjectMetadataId are required for RECORD type",
|
||||
"userFriendlyMessage": "targetRecordId and targetObjectMetadataId are required for RECORD type",
|
||||
"message": "A valid targetObjectMetadataUniversalIdentifier is required for RECORD type",
|
||||
"userFriendlyMessage": "A valid targetObjectMetadataUniversalIdentifier is required for RECORD type",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": null,
|
||||
"type": "RECORD",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
@@ -118,7 +120,7 @@ exports[`NavigationMenuItem creation should fail when creating with missing targ
|
||||
"navigationMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "targetRecordId and targetObjectMetadataId are required for RECORD type",
|
||||
"userFriendlyMessage": "A valid targetObjectMetadataUniversalIdentifier is required for RECORD type",
|
||||
},
|
||||
"message": "Multiple validation errors occurred while creating navigation menu items",
|
||||
"name": "GraphQLError",
|
||||
|
||||
+5
-1
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Navigation Menu Item update should fail with circular dependency when folderId equals id (self-reference) 1`] = `
|
||||
{
|
||||
@@ -15,6 +15,8 @@ exports[`Navigation Menu Item update should fail with circular dependency when f
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "Standalone Folder",
|
||||
"type": "FOLDER",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
@@ -50,6 +52,8 @@ exports[`Navigation Menu Item update should fail with circular dependency when u
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "Parent Folder",
|
||||
"type": "FOLDER",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "navigationMenuItem",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// Type level counterpart of assertUnreachable: fails to compile when a switch
|
||||
// statement does not handle every case, without any runtime behavior
|
||||
export type AssertUnreachable<T extends never> = T;
|
||||
@@ -15,6 +15,7 @@ export { AppBasePath } from './AppBasePath';
|
||||
export { AppPath } from './AppPath';
|
||||
export type { Arrayable } from './Arrayable';
|
||||
export type { ArraySortDirection } from './ArraySortDirection';
|
||||
export type { AssertUnreachable } from './AssertUnreachable.type';
|
||||
export { CalendarChannelContactAutoCreationPolicy } from './CalendarChannelContactAutoCreationPolicy';
|
||||
export { CalendarChannelSyncStage } from './CalendarChannelSyncStage';
|
||||
export { CalendarChannelSyncStatus } from './CalendarChannelSyncStatus';
|
||||
|
||||
Reference in New Issue
Block a user