feat: join field on upload fields (#8379)
This PR makes it possible to use the new `join` field in connection with an `upload` field. Previously `join` was reserved only for relationships.
This commit is contained in:
1
test/joins/.gitignore
vendored
Normal file
1
test/joins/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/uploads/
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { categoriesSlug, postsSlug } from '../shared.js'
|
||||
import { categoriesSlug, postsSlug, uploadsSlug } from '../shared.js'
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: postsSlug,
|
||||
@@ -13,6 +13,11 @@ export const Posts: CollectionConfig = {
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'upload',
|
||||
type: 'upload',
|
||||
relationTo: uploadsSlug,
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'relationship',
|
||||
|
||||
23
test/joins/collections/Uploads.ts
Normal file
23
test/joins/collections/Uploads.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { uploadsSlug } from '../shared.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export const Uploads: CollectionConfig = {
|
||||
slug: uploadsSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'relatedPosts',
|
||||
type: 'join',
|
||||
collection: 'posts',
|
||||
on: 'upload',
|
||||
},
|
||||
],
|
||||
upload: {
|
||||
staticDir: path.resolve(dirname, '../uploads'),
|
||||
},
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import path from 'path'
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { Categories } from './collections/Categories.js'
|
||||
import { Posts } from './collections/Posts.js'
|
||||
import { Uploads } from './collections/Uploads.js'
|
||||
import { seed } from './seed.js'
|
||||
import { localizedCategoriesSlug, localizedPostsSlug } from './shared.js'
|
||||
|
||||
@@ -14,6 +15,7 @@ export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
Posts,
|
||||
Categories,
|
||||
Uploads,
|
||||
{
|
||||
slug: localizedPostsSlug,
|
||||
admin: {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { navigateToDoc } from '../helpers/e2e/navigateToDoc.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
import { categoriesSlug, postsSlug } from './shared.js'
|
||||
import { categoriesSlug, postsSlug, uploadsSlug } from './shared.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@@ -18,6 +18,7 @@ const dirname = path.dirname(filename)
|
||||
test.describe('Admin Panel', () => {
|
||||
let page: Page
|
||||
let categoriesURL: AdminUrlUtil
|
||||
let uploadsURL: AdminUrlUtil
|
||||
let postsURL: AdminUrlUtil
|
||||
|
||||
test.beforeAll(async ({ browser }, testInfo) => {
|
||||
@@ -26,6 +27,7 @@ test.describe('Admin Panel', () => {
|
||||
const { payload, serverURL } = await initPayloadE2ENoConfig({ dirname })
|
||||
postsURL = new AdminUrlUtil(serverURL, postsSlug)
|
||||
categoriesURL = new AdminUrlUtil(serverURL, categoriesSlug)
|
||||
uploadsURL = new AdminUrlUtil(serverURL, uploadsSlug)
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
@@ -183,4 +185,65 @@ test.describe('Admin Panel', () => {
|
||||
await expect(joinField).toBeVisible()
|
||||
await expect(joinField.locator('.relationship-table tbody tr')).toBeHidden()
|
||||
})
|
||||
|
||||
test('should update relationship table when new upload is created', async () => {
|
||||
await navigateToDoc(page, uploadsURL)
|
||||
const joinField = page.locator('.field-type.join').first()
|
||||
await expect(joinField).toBeVisible()
|
||||
|
||||
const addButton = joinField.locator('.relationship-table__actions button.doc-drawer__toggler', {
|
||||
hasText: exactText('Add new'),
|
||||
})
|
||||
|
||||
await expect(addButton).toBeVisible()
|
||||
|
||||
await addButton.click()
|
||||
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
|
||||
await expect(drawer).toBeVisible()
|
||||
const uploadField = drawer.locator('#field-upload')
|
||||
await expect(uploadField).toBeVisible()
|
||||
const uploadValue = uploadField.locator('.upload-relationship-details img')
|
||||
await expect(uploadValue).toBeVisible()
|
||||
const titleField = drawer.locator('#field-title')
|
||||
await expect(titleField).toBeVisible()
|
||||
await titleField.fill('Test post with upload')
|
||||
await drawer.locator('button[id="action-save"]').click()
|
||||
await expect(drawer).toBeHidden()
|
||||
await expect(
|
||||
joinField.locator('tbody tr td:nth-child(2)', {
|
||||
hasText: exactText('Test post with upload'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('should update relationship table when new upload is created', async () => {
|
||||
await navigateToDoc(page, uploadsURL)
|
||||
const joinField = page.locator('.field-type.join').first()
|
||||
await expect(joinField).toBeVisible()
|
||||
|
||||
// TODO: change this to edit the first row in the join table
|
||||
const addButton = joinField.locator('.relationship-table__actions button.doc-drawer__toggler', {
|
||||
hasText: exactText('Add new'),
|
||||
})
|
||||
|
||||
await expect(addButton).toBeVisible()
|
||||
|
||||
await addButton.click()
|
||||
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
|
||||
await expect(drawer).toBeVisible()
|
||||
const uploadField = drawer.locator('#field-upload')
|
||||
await expect(uploadField).toBeVisible()
|
||||
const uploadValue = uploadField.locator('.upload-relationship-details img')
|
||||
await expect(uploadValue).toBeVisible()
|
||||
const titleField = drawer.locator('#field-title')
|
||||
await expect(titleField).toBeVisible()
|
||||
await titleField.fill('Edited title for upload')
|
||||
await drawer.locator('button[id="action-save"]').click()
|
||||
await expect(drawer).toBeHidden()
|
||||
await expect(
|
||||
joinField.locator('tbody tr td:nth-child(2)', {
|
||||
hasText: exactText('Edited title for upload'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
BIN
test/joins/image.png
Normal file
BIN
test/joins/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -1,6 +1,7 @@
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
import { getFileByPath } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||
@@ -9,6 +10,7 @@ import type { Category, Post } from './payload-types.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
import { idToString } from '../helpers/idToString.js'
|
||||
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
import { categoriesSlug, uploadsSlug } from './shared.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@@ -40,19 +42,30 @@ describe('Joins Field', () => {
|
||||
token = data.token
|
||||
|
||||
category = await payload.create({
|
||||
collection: 'categories',
|
||||
collection: categoriesSlug,
|
||||
data: {
|
||||
name: 'paginate example',
|
||||
group: {},
|
||||
},
|
||||
})
|
||||
|
||||
// create an upload
|
||||
const imageFilePath = path.resolve(dirname, './image.png')
|
||||
const imageFile = await getFileByPath(imageFilePath)
|
||||
|
||||
const { id: uploadedImage } = await payload.create({
|
||||
collection: uploadsSlug,
|
||||
data: {},
|
||||
file: imageFile,
|
||||
})
|
||||
|
||||
categoryID = idToString(category.id, payload)
|
||||
|
||||
for (let i = 0; i < 15; i++) {
|
||||
await createPost({
|
||||
title: `test ${i}`,
|
||||
category: category.id,
|
||||
upload: uploadedImage,
|
||||
group: {
|
||||
category: category.id,
|
||||
},
|
||||
@@ -94,6 +107,16 @@ describe('Joins Field', () => {
|
||||
expect(docs[0].category.relatedPosts.docs).toHaveLength(10)
|
||||
})
|
||||
|
||||
it('should populate uploads in joins', async () => {
|
||||
const { docs } = await payload.find({
|
||||
limit: 1,
|
||||
collection: 'posts',
|
||||
})
|
||||
|
||||
expect(docs[0].upload.id).toBeDefined()
|
||||
expect(docs[0].upload.relatedPosts.docs).toHaveLength(10)
|
||||
})
|
||||
|
||||
it('should filter joins using where query', async () => {
|
||||
const categoryWithPosts = await payload.findByID({
|
||||
id: category.id,
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface Config {
|
||||
collections: {
|
||||
posts: Post;
|
||||
categories: Category;
|
||||
uploads: Upload;
|
||||
'localized-posts': LocalizedPost;
|
||||
'localized-categories': LocalizedCategory;
|
||||
users: User;
|
||||
@@ -54,6 +55,7 @@ export interface UserAuthOperations {
|
||||
export interface Post {
|
||||
id: string;
|
||||
title?: string | null;
|
||||
upload?: (string | null) | Upload;
|
||||
category?: (string | null) | Category;
|
||||
group?: {
|
||||
category?: (string | null) | Category;
|
||||
@@ -61,6 +63,28 @@ export interface Post {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "uploads".
|
||||
*/
|
||||
export interface Upload {
|
||||
id: string;
|
||||
relatedPosts?: {
|
||||
docs?: (string | Post)[] | null;
|
||||
hasNextPage?: boolean | null;
|
||||
} | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
thumbnailURL?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "categories".
|
||||
@@ -138,6 +162,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'categories';
|
||||
value: string | Category;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'uploads';
|
||||
value: string | Upload;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'localized-posts';
|
||||
value: string | LocalizedPost;
|
||||
@@ -150,7 +178,6 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null);
|
||||
editedAt?: string | null;
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
import { getFileByPath } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { devUser } from '../credentials.js'
|
||||
import { seedDB } from '../helpers/seed.js'
|
||||
import { categoriesSlug, collectionSlugs, postsSlug } from './shared.js'
|
||||
import { categoriesSlug, collectionSlugs, postsSlug, uploadsSlug } from './shared.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export const seed = async (_payload) => {
|
||||
await _payload.create({
|
||||
@@ -53,6 +60,23 @@ export const seed = async (_payload) => {
|
||||
title: 'Test Post 3',
|
||||
},
|
||||
})
|
||||
|
||||
// create an upload with image.png
|
||||
const imageFilePath = path.resolve(dirname, './image.png')
|
||||
const imageFile = await getFileByPath(imageFilePath)
|
||||
const { id: uploadedImage } = await _payload.create({
|
||||
collection: uploadsSlug,
|
||||
data: {},
|
||||
file: imageFile,
|
||||
})
|
||||
|
||||
// create a post that uses the upload
|
||||
await _payload.create({
|
||||
collection: postsSlug,
|
||||
data: {
|
||||
upload: uploadedImage.id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function clearAndSeedEverything(_payload: Payload) {
|
||||
|
||||
@@ -2,6 +2,8 @@ export const categoriesSlug = 'categories'
|
||||
|
||||
export const postsSlug = 'posts'
|
||||
|
||||
export const uploadsSlug = 'uploads'
|
||||
|
||||
export const localizedPostsSlug = 'localized-posts'
|
||||
|
||||
export const localizedCategoriesSlug = 'localized-categories'
|
||||
|
||||
Reference in New Issue
Block a user