diff --git a/packages/ui/src/fields/Relationship/index.tsx b/packages/ui/src/fields/Relationship/index.tsx index 648c12cf3..a51459644 100644 --- a/packages/ui/src/fields/Relationship/index.tsx +++ b/packages/ui/src/fields/Relationship/index.tsx @@ -495,6 +495,11 @@ const RelationshipField: React.FC = (props) => { }} disabled={readOnly || formProcessing || drawerIsOpen} filterOption={enableWordBoundarySearch ? filterOption : undefined} + getOptionValue={(option) => { + return hasMany && Array.isArray(relationTo) + ? `${option.relationTo}_${option.value}` + : option.value + }} isLoading={isLoading} isMulti={hasMany} isSortable={isSortable} diff --git a/test/fields-relationship/collectionSlugs.ts b/test/fields-relationship/collectionSlugs.ts index 94ed552a0..3ecb0cea9 100644 --- a/test/fields-relationship/collectionSlugs.ts +++ b/test/fields-relationship/collectionSlugs.ts @@ -9,3 +9,6 @@ export const relationWithTitleSlug = 'relation-with-title' export const relationUpdatedExternallySlug = 'relation-updated-externally' export const collection1Slug = 'collection-1' export const collection2Slug = 'collection-2' +export const videoCollectionSlug = 'videos' +export const podcastCollectionSlug = 'podcasts' +export const mixedMediaCollectionSlug = 'mixed-media' diff --git a/test/fields-relationship/config.ts b/test/fields-relationship/config.ts index 77a2746d8..3bc970160 100644 --- a/test/fields-relationship/config.ts +++ b/test/fields-relationship/config.ts @@ -9,6 +9,8 @@ import { PrePopulateFieldUI } from './PrePopulateFieldUI/index.js' import { collection1Slug, collection2Slug, + mixedMediaCollectionSlug, + podcastCollectionSlug, relationFalseFilterOptionSlug, relationOneSlug, relationRestrictedSlug, @@ -17,6 +19,7 @@ import { relationUpdatedExternallySlug, relationWithTitleSlug, slug, + videoCollectionSlug, } from './collectionSlugs.js' export interface FieldsRelationship { @@ -325,6 +328,51 @@ export default buildConfigWithDefaults({ ], slug: collection2Slug, }, + { + slug: videoCollectionSlug, + admin: { + useAsTitle: 'title', + }, + fields: [ + { + name: 'id', + type: 'number', + required: true, + }, + { + name: 'title', + type: 'text', + }, + ], + }, + { + slug: podcastCollectionSlug, + admin: { + useAsTitle: 'title', + }, + fields: [ + { + name: 'id', + type: 'number', + required: true, + }, + { + name: 'title', + type: 'text', + }, + ], + }, + { + slug: mixedMediaCollectionSlug, + fields: [ + { + type: 'relationship', + name: 'relatedMedia', + relationTo: [videoCollectionSlug, podcastCollectionSlug], + hasMany: true, + }, + ], + }, ], onInit: async (payload) => { await payload.create({ @@ -461,5 +509,22 @@ export default buildConfigWithDefaults({ }, }) } + + for (let i = 0; i < 2; i++) { + await payload.create({ + collection: videoCollectionSlug, + data: { + id: i, + title: `Video ${i}`, + }, + }) + await payload.create({ + collection: podcastCollectionSlug, + data: { + id: i, + title: `Podcast ${i}`, + }, + }) + } }, }) diff --git a/test/fields-relationship/e2e.spec.ts b/test/fields-relationship/e2e.spec.ts index f11d9177f..7df9e37ef 100644 --- a/test/fields-relationship/e2e.spec.ts +++ b/test/fields-relationship/e2e.spec.ts @@ -1,5 +1,4 @@ import type { Page } from '@playwright/test' -import type { Payload } from 'payload' import { expect, test } from '@playwright/test' import path from 'path' @@ -22,12 +21,12 @@ import { openDocControls, openDocDrawer, saveDocAndAssert, - throttleTest, } from '../helpers.js' import { AdminUrlUtil } from '../helpers/adminUrlUtil.js' import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js' import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js' import { + mixedMediaCollectionSlug, relationFalseFilterOptionSlug, relationOneSlug, relationRestrictedSlug, @@ -397,6 +396,26 @@ describe('fields - relationship', () => { await expect(options).toContainText('truth') }) + test('should allow docs with same ID but different collections to be selectable', async () => { + const mixedMedia = new AdminUrlUtil(serverURL, mixedMediaCollectionSlug) + await page.goto(mixedMedia.create) + // wait for relationship options to load + const podcastsFilterOptionsReq = page.waitForResponse(/api\/podcasts/) + const videosFilterOptionsReq = page.waitForResponse(/api\/videos/) + // select relationshipMany field that relies on siblingData field above + await page.locator('#field-relatedMedia .rs__control').click() + await podcastsFilterOptionsReq + await videosFilterOptionsReq + + const options = page.locator('.rs__option') + await expect(options).toHaveCount(4) // 4 docs + await options.locator(`text=Video 0`).click() + + await page.locator('#field-relatedMedia .rs__control').click() + const remainingOptions = page.locator('.rs__option') + await expect(remainingOptions).toHaveCount(3) // 3 docs + }) + // TODO: Flaky test in CI - fix. test.skip('should open document drawer from read-only relationships', async () => { const editURL = url.edit(docWithExistingRelations.id) diff --git a/test/fields-relationship/payload-types.ts b/test/fields-relationship/payload-types.ts index f751fff6e..53a28b411 100644 --- a/test/fields-relationship/payload-types.ts +++ b/test/fields-relationship/payload-types.ts @@ -18,6 +18,9 @@ export interface Config { 'relation-updated-externally': RelationUpdatedExternally; 'collection-1': Collection1; 'collection-2': Collection2; + videos: Video; + podcasts: Podcast; + mixedMedia: MixedMedia; users: User; 'payload-preferences': PayloadPreference; 'payload-migrations': PayloadMigration; @@ -192,6 +195,47 @@ export interface Collection2 { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "videos". + */ +export interface Video { + id: number; + title?: string | null; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "podcasts". + */ +export interface Podcast { + id: number; + title?: string | null; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "mixedMedia". + */ +export interface MixedMedia { + id: string; + relatedMedia?: + | ( + | { + relationTo: 'videos'; + value: number | Video; + } + | { + relationTo: 'podcasts'; + value: number | Podcast; + } + )[] + | null; + updatedAt: string; + createdAt: string; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "users".