feat(payload): allows metadata to be appended to the file of the output media (#7293)
## Description Fixes #6951 `Feat`: Adds new prop `withMetadata` to `uploads` config that allows the user to allow media metadata to be appended to the file of the output media. - [x] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] This change requires a documentation update ## Checklist: - [x] I have added tests that prove my fix is effective or that my feature works - [x] Existing test suite passes locally with my changes - [x] I have made corresponding changes to the documentation
This commit is contained in:
@@ -104,6 +104,7 @@ _An asterisk denotes that an option is required._
|
|||||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||||
| **`staticDir`** | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. Defaults to your collection slug |
|
| **`staticDir`** | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. Defaults to your collection slug |
|
||||||
| **`trimOptions`** | An object passed to the the Sharp image library to trim the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize#trim) |
|
| **`trimOptions`** | An object passed to the the Sharp image library to trim the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize#trim) |
|
||||||
|
| **`withMetadata`** | If specified, appends metadata to the output image file. Accepts a boolean or a function that receives `metadata` and `req`, returning a boolean. |
|
||||||
|
|
||||||
|
|
||||||
### Payload-wide Upload Options
|
### Payload-wide Upload Options
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export const createClientCollectionConfig = ({
|
|||||||
delete sanitized.upload.handlers
|
delete sanitized.upload.handlers
|
||||||
delete sanitized.upload.adminThumbnail
|
delete sanitized.upload.adminThumbnail
|
||||||
delete sanitized.upload.externalFileHeaderFilter
|
delete sanitized.upload.externalFileHeaderFilter
|
||||||
|
delete sanitized.upload.withMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('auth' in sanitized && typeof sanitized.auth === 'object') {
|
if ('auth' in sanitized && typeof sanitized.auth === 'object') {
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import type { SharpOptions } from 'sharp'
|
|||||||
|
|
||||||
import type { SanitizedConfig } from '../config/types.js'
|
import type { SanitizedConfig } from '../config/types.js'
|
||||||
import type { PayloadRequest } from '../types/index.js'
|
import type { PayloadRequest } from '../types/index.js'
|
||||||
|
import type { WithMetadata } from './optionallyAppendMetadata.js'
|
||||||
import type { UploadEdits } from './types.js'
|
import type { UploadEdits } from './types.js'
|
||||||
|
|
||||||
|
import { optionallyAppendMetadata } from './optionallyAppendMetadata.js'
|
||||||
|
|
||||||
export const percentToPixel = (value, dimension) => {
|
export const percentToPixel = (value, dimension) => {
|
||||||
return Math.floor((parseFloat(value) / 100) * dimension)
|
return Math.floor((parseFloat(value) / 100) * dimension)
|
||||||
}
|
}
|
||||||
@@ -13,16 +16,20 @@ type CropImageArgs = {
|
|||||||
dimensions: { height: number; width: number }
|
dimensions: { height: number; width: number }
|
||||||
file: PayloadRequest['file']
|
file: PayloadRequest['file']
|
||||||
heightInPixels: number
|
heightInPixels: number
|
||||||
|
req?: PayloadRequest
|
||||||
sharp: SanitizedConfig['sharp']
|
sharp: SanitizedConfig['sharp']
|
||||||
widthInPixels: number
|
widthInPixels: number
|
||||||
|
withMetadata?: WithMetadata
|
||||||
}
|
}
|
||||||
export async function cropImage({
|
export async function cropImage({
|
||||||
cropData,
|
cropData,
|
||||||
dimensions,
|
dimensions,
|
||||||
file,
|
file,
|
||||||
heightInPixels,
|
heightInPixels,
|
||||||
|
req,
|
||||||
sharp,
|
sharp,
|
||||||
widthInPixels,
|
widthInPixels,
|
||||||
|
withMetadata,
|
||||||
}: CropImageArgs) {
|
}: CropImageArgs) {
|
||||||
try {
|
try {
|
||||||
const { x, y } = cropData
|
const { x, y } = cropData
|
||||||
@@ -40,7 +47,13 @@ export async function cropImage({
|
|||||||
width: Number(widthInPixels),
|
width: Number(widthInPixels),
|
||||||
}
|
}
|
||||||
|
|
||||||
const cropped = sharp(file.tempFilePath || file.data, sharpOptions).extract(formattedCropData)
|
let cropped = sharp(file.tempFilePath || file.data, sharpOptions).extract(formattedCropData)
|
||||||
|
|
||||||
|
cropped = await optionallyAppendMetadata({
|
||||||
|
req,
|
||||||
|
sharpFile: cropped,
|
||||||
|
withMetadata,
|
||||||
|
})
|
||||||
|
|
||||||
return await cropped.toBuffer({
|
return await cropped.toBuffer({
|
||||||
resolveWithObject: true,
|
resolveWithObject: true,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { getImageSize } from './getImageSize.js'
|
|||||||
import { getSafeFileName } from './getSafeFilename.js'
|
import { getSafeFileName } from './getSafeFilename.js'
|
||||||
import { resizeAndTransformImageSizes } from './imageResizer.js'
|
import { resizeAndTransformImageSizes } from './imageResizer.js'
|
||||||
import { isImage } from './isImage.js'
|
import { isImage } from './isImage.js'
|
||||||
|
import { optionallyAppendMetadata } from './optionallyAppendMetadata.js'
|
||||||
|
|
||||||
type Args<T> = {
|
type Args<T> = {
|
||||||
collection: Collection
|
collection: Collection
|
||||||
@@ -71,6 +72,7 @@ export const generateFileData = async <T>({
|
|||||||
resizeOptions,
|
resizeOptions,
|
||||||
staticDir,
|
staticDir,
|
||||||
trimOptions,
|
trimOptions,
|
||||||
|
withMetadata,
|
||||||
} = collectionConfig.upload
|
} = collectionConfig.upload
|
||||||
|
|
||||||
const staticPath = staticDir
|
const staticPath = staticDir
|
||||||
@@ -161,6 +163,11 @@ export const generateFileData = async <T>({
|
|||||||
|
|
||||||
if (sharpFile) {
|
if (sharpFile) {
|
||||||
const metadata = await sharpFile.metadata()
|
const metadata = await sharpFile.metadata()
|
||||||
|
sharpFile = await optionallyAppendMetadata({
|
||||||
|
req,
|
||||||
|
sharpFile,
|
||||||
|
withMetadata,
|
||||||
|
})
|
||||||
fileBuffer = await sharpFile.toBuffer({ resolveWithObject: true })
|
fileBuffer = await sharpFile.toBuffer({ resolveWithObject: true })
|
||||||
;({ ext, mime } = await fileTypeFromBuffer(fileBuffer.data)) // This is getting an incorrect gif height back.
|
;({ ext, mime } = await fileTypeFromBuffer(fileBuffer.data)) // This is getting an incorrect gif height back.
|
||||||
fileData.width = fileBuffer.info.width
|
fileData.width = fileBuffer.info.width
|
||||||
@@ -208,8 +215,10 @@ export const generateFileData = async <T>({
|
|||||||
dimensions,
|
dimensions,
|
||||||
file,
|
file,
|
||||||
heightInPixels: uploadEdits.heightInPixels,
|
heightInPixels: uploadEdits.heightInPixels,
|
||||||
|
req,
|
||||||
sharp,
|
sharp,
|
||||||
widthInPixels: uploadEdits.widthInPixels,
|
widthInPixels: uploadEdits.widthInPixels,
|
||||||
|
withMetadata,
|
||||||
})
|
})
|
||||||
|
|
||||||
filesToSave.push({
|
filesToSave.push({
|
||||||
@@ -274,6 +283,7 @@ export const generateFileData = async <T>({
|
|||||||
sharp,
|
sharp,
|
||||||
staticPath,
|
staticPath,
|
||||||
uploadEdits,
|
uploadEdits,
|
||||||
|
withMetadata,
|
||||||
})
|
})
|
||||||
|
|
||||||
fileData.sizes = sizeData
|
fileData.sizes = sizeData
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import sanitize from 'sanitize-filename'
|
|||||||
import type { SanitizedCollectionConfig } from '../collections/config/types.js'
|
import type { SanitizedCollectionConfig } from '../collections/config/types.js'
|
||||||
import type { SharpDependency } from '../config/types.js'
|
import type { SharpDependency } from '../config/types.js'
|
||||||
import type { PayloadRequest } from '../types/index.js'
|
import type { PayloadRequest } from '../types/index.js'
|
||||||
|
import type { WithMetadata } from './optionallyAppendMetadata.js'
|
||||||
import type {
|
import type {
|
||||||
FileSize,
|
FileSize,
|
||||||
FileSizes,
|
FileSizes,
|
||||||
@@ -18,6 +19,7 @@ import type {
|
|||||||
|
|
||||||
import { isNumber } from '../utilities/isNumber.js'
|
import { isNumber } from '../utilities/isNumber.js'
|
||||||
import fileExists from './fileExists.js'
|
import fileExists from './fileExists.js'
|
||||||
|
import { optionallyAppendMetadata } from './optionallyAppendMetadata.js'
|
||||||
|
|
||||||
type ResizeArgs = {
|
type ResizeArgs = {
|
||||||
config: SanitizedCollectionConfig
|
config: SanitizedCollectionConfig
|
||||||
@@ -29,6 +31,7 @@ type ResizeArgs = {
|
|||||||
sharp?: SharpDependency
|
sharp?: SharpDependency
|
||||||
staticPath: string
|
staticPath: string
|
||||||
uploadEdits?: UploadEdits
|
uploadEdits?: UploadEdits
|
||||||
|
withMetadata?: WithMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Result from resizing and transforming the requested image sizes */
|
/** Result from resizing and transforming the requested image sizes */
|
||||||
@@ -245,6 +248,7 @@ export async function resizeAndTransformImageSizes({
|
|||||||
sharp,
|
sharp,
|
||||||
staticPath,
|
staticPath,
|
||||||
uploadEdits,
|
uploadEdits,
|
||||||
|
withMetadata,
|
||||||
}: ResizeArgs): Promise<ImageSizesResult> {
|
}: ResizeArgs): Promise<ImageSizesResult> {
|
||||||
const { focalPoint: focalPointEnabled = true, imageSizes } = config.upload
|
const { focalPoint: focalPointEnabled = true, imageSizes } = config.upload
|
||||||
|
|
||||||
@@ -320,8 +324,15 @@ export async function resizeAndTransformImageSizes({
|
|||||||
width: prioritizeHeight ? undefined : resizeWidth,
|
width: prioritizeHeight ? undefined : resizeWidth,
|
||||||
})
|
})
|
||||||
|
|
||||||
// must read from buffer, resize.metadata will return the original image metadata
|
const metadataAppendedFile = await optionallyAppendMetadata({
|
||||||
const { info } = await resized.toBuffer({ resolveWithObject: true })
|
req,
|
||||||
|
sharpFile: resized,
|
||||||
|
withMetadata,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Must read from buffer, resized.metadata will return the original image metadata
|
||||||
|
const { info } = await metadataAppendedFile.toBuffer({ resolveWithObject: true })
|
||||||
|
|
||||||
resizeImageMeta.height = extractHeightFromImage({
|
resizeImageMeta.height = extractHeightFromImage({
|
||||||
...originalImageMeta,
|
...originalImageMeta,
|
||||||
height: info.height,
|
height: info.height,
|
||||||
@@ -379,7 +390,13 @@ export async function resizeAndTransformImageSizes({
|
|||||||
resized = resized.trim(imageResizeConfig.trimOptions)
|
resized = resized.trim(imageResizeConfig.trimOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: bufferData, info: bufferInfo } = await resized.toBuffer({
|
const metadataAppendedFile = await optionallyAppendMetadata({
|
||||||
|
req,
|
||||||
|
sharpFile: resized,
|
||||||
|
withMetadata,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: bufferData, info: bufferInfo } = await metadataAppendedFile.toBuffer({
|
||||||
resolveWithObject: true,
|
resolveWithObject: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
29
packages/payload/src/uploads/optionallyAppendMetadata.ts
Normal file
29
packages/payload/src/uploads/optionallyAppendMetadata.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { Sharp, Metadata as SharpMetadata } from 'sharp'
|
||||||
|
|
||||||
|
import type { PayloadRequest } from '../types/index.js'
|
||||||
|
|
||||||
|
export type WithMetadata =
|
||||||
|
| ((options: { metadata: SharpMetadata; req: PayloadRequest }) => Promise<boolean>)
|
||||||
|
| boolean
|
||||||
|
|
||||||
|
export async function optionallyAppendMetadata({
|
||||||
|
req,
|
||||||
|
sharpFile,
|
||||||
|
withMetadata,
|
||||||
|
}: {
|
||||||
|
req: PayloadRequest
|
||||||
|
sharpFile: Sharp
|
||||||
|
withMetadata: WithMetadata
|
||||||
|
}): Promise<Sharp> {
|
||||||
|
const metadata = await sharpFile.metadata()
|
||||||
|
|
||||||
|
if (withMetadata === true) {
|
||||||
|
return sharpFile.withMetadata()
|
||||||
|
} else if (typeof withMetadata === 'function') {
|
||||||
|
const useMetadata = await withMetadata({ metadata, req })
|
||||||
|
|
||||||
|
if (useMetadata) return sharpFile.withMetadata()
|
||||||
|
}
|
||||||
|
|
||||||
|
return sharpFile
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { ResizeOptions, Sharp } from 'sharp'
|
import type { ResizeOptions, Sharp, Metadata as SharpMetadata } from 'sharp'
|
||||||
|
|
||||||
import type { TypeWithID } from '../collections/config/types.js'
|
import type { TypeWithID } from '../collections/config/types.js'
|
||||||
import type { PayloadRequest } from '../types/index.js'
|
import type { PayloadRequest } from '../types/index.js'
|
||||||
|
import type { WithMetadata } from './optionallyAppendMetadata.js'
|
||||||
|
|
||||||
export type FileSize = {
|
export type FileSize = {
|
||||||
filename: null | string
|
filename: null | string
|
||||||
@@ -153,6 +154,17 @@ export type UploadConfig = {
|
|||||||
*/
|
*/
|
||||||
staticDir?: string
|
staticDir?: string
|
||||||
trimOptions?: ImageUploadTrimOptions
|
trimOptions?: ImageUploadTrimOptions
|
||||||
|
/**
|
||||||
|
* Optionally append metadata to the image during processing.
|
||||||
|
*
|
||||||
|
* Can be a boolean or a function.
|
||||||
|
*
|
||||||
|
* If true, metadata will be appended to the image.
|
||||||
|
* If false, no metadata will be appended.
|
||||||
|
* If a function, it will receive an object containing the metadata and should return a boolean indicating whether to append the metadata.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
withMetadata?: WithMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SanitizedUploadConfig = {
|
export type SanitizedUploadConfig = {
|
||||||
|
|||||||
@@ -133,6 +133,60 @@ export default buildConfigWithDefaults({
|
|||||||
staticDir: path.resolve(dirname, './object-fit'),
|
staticDir: path.resolve(dirname, './object-fit'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: 'with-meta-data',
|
||||||
|
fields: [],
|
||||||
|
upload: {
|
||||||
|
imageSizes: [
|
||||||
|
{
|
||||||
|
name: 'sizeOne',
|
||||||
|
height: 300,
|
||||||
|
width: 400,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
||||||
|
staticDir: path.resolve(dirname, './with-meta-data'),
|
||||||
|
withMetadata: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'without-meta-data',
|
||||||
|
fields: [],
|
||||||
|
upload: {
|
||||||
|
imageSizes: [
|
||||||
|
{
|
||||||
|
name: 'sizeTwo',
|
||||||
|
height: 400,
|
||||||
|
width: 300,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
||||||
|
staticDir: path.resolve(dirname, './without-meta-data'),
|
||||||
|
withMetadata: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'with-only-jpeg-meta-data',
|
||||||
|
fields: [],
|
||||||
|
upload: {
|
||||||
|
imageSizes: [
|
||||||
|
{
|
||||||
|
name: 'sizeThree',
|
||||||
|
height: 400,
|
||||||
|
width: 300,
|
||||||
|
withoutEnlargement: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
staticDir: path.resolve(dirname, './with-only-jpeg-meta-data'),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
|
withMetadata: async ({ metadata }) => {
|
||||||
|
if (metadata.format === 'jpeg') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slug: 'crop-only',
|
slug: 'crop-only',
|
||||||
fields: [],
|
fields: [],
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { Page } from '@playwright/test'
|
import type { Page } from '@playwright/test'
|
||||||
import type { Payload } from 'payload'
|
|
||||||
|
|
||||||
import { expect, test } from '@playwright/test'
|
import { expect, test } from '@playwright/test'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
@@ -27,6 +26,9 @@ import {
|
|||||||
focalOnlySlug,
|
focalOnlySlug,
|
||||||
mediaSlug,
|
mediaSlug,
|
||||||
relationSlug,
|
relationSlug,
|
||||||
|
withMetadataSlug,
|
||||||
|
withOnlyJPEGMetadataSlug,
|
||||||
|
withoutMetadataSlug,
|
||||||
} from './shared.js'
|
} from './shared.js'
|
||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
@@ -43,6 +45,9 @@ let relationURL: AdminUrlUtil
|
|||||||
let adminThumbnailSizeURL: AdminUrlUtil
|
let adminThumbnailSizeURL: AdminUrlUtil
|
||||||
let adminThumbnailFunctionURL: AdminUrlUtil
|
let adminThumbnailFunctionURL: AdminUrlUtil
|
||||||
let focalOnlyURL: AdminUrlUtil
|
let focalOnlyURL: AdminUrlUtil
|
||||||
|
let withMetadataURL: AdminUrlUtil
|
||||||
|
let withoutMetadataURL: AdminUrlUtil
|
||||||
|
let withOnlyJPEGMetadataURL: AdminUrlUtil
|
||||||
|
|
||||||
describe('uploads', () => {
|
describe('uploads', () => {
|
||||||
let page: Page
|
let page: Page
|
||||||
@@ -62,6 +67,9 @@ describe('uploads', () => {
|
|||||||
adminThumbnailSizeURL = new AdminUrlUtil(serverURL, adminThumbnailSizeSlug)
|
adminThumbnailSizeURL = new AdminUrlUtil(serverURL, adminThumbnailSizeSlug)
|
||||||
adminThumbnailFunctionURL = new AdminUrlUtil(serverURL, adminThumbnailFunctionSlug)
|
adminThumbnailFunctionURL = new AdminUrlUtil(serverURL, adminThumbnailFunctionSlug)
|
||||||
focalOnlyURL = new AdminUrlUtil(serverURL, focalOnlySlug)
|
focalOnlyURL = new AdminUrlUtil(serverURL, focalOnlySlug)
|
||||||
|
withMetadataURL = new AdminUrlUtil(serverURL, withMetadataSlug)
|
||||||
|
withoutMetadataURL = new AdminUrlUtil(serverURL, withoutMetadataSlug)
|
||||||
|
withOnlyJPEGMetadataURL = new AdminUrlUtil(serverURL, withOnlyJPEGMetadataSlug)
|
||||||
|
|
||||||
const context = await browser.newContext()
|
const context = await browser.newContext()
|
||||||
page = await context.newPage()
|
page = await context.newPage()
|
||||||
@@ -346,6 +354,116 @@ describe('uploads', () => {
|
|||||||
expect(uploadedImage.mimeType).toEqual('image/png')
|
expect(uploadedImage.mimeType).toEqual('image/png')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should upload image with metadata', async () => {
|
||||||
|
await page.goto(withMetadataURL.create)
|
||||||
|
await page.waitForURL(withMetadataURL.create)
|
||||||
|
|
||||||
|
const fileChooserPromise = page.waitForEvent('filechooser')
|
||||||
|
await page.getByText('Select a file').click()
|
||||||
|
const fileChooser = await fileChooserPromise
|
||||||
|
await wait(1000)
|
||||||
|
await fileChooser.setFiles(path.join(dirname, 'test-image.jpg'))
|
||||||
|
|
||||||
|
await page.waitForSelector('button#action-save')
|
||||||
|
await page.locator('button#action-save').click()
|
||||||
|
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||||
|
await wait(1000)
|
||||||
|
|
||||||
|
const mediaID = page.url().split('/').pop()
|
||||||
|
|
||||||
|
const { doc: mediaDoc } = await client.findByID({
|
||||||
|
id: mediaID,
|
||||||
|
slug: withMetadataSlug,
|
||||||
|
auth: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const acceptableFileSizes = [9431, 9435]
|
||||||
|
|
||||||
|
expect(acceptableFileSizes).toContain(mediaDoc.sizes.sizeOne.filesize)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should upload image without metadata', async () => {
|
||||||
|
await page.goto(withoutMetadataURL.create)
|
||||||
|
await page.waitForURL(withoutMetadataURL.create)
|
||||||
|
|
||||||
|
const fileChooserPromise = page.waitForEvent('filechooser')
|
||||||
|
await page.getByText('Select a file').click()
|
||||||
|
const fileChooser = await fileChooserPromise
|
||||||
|
await wait(1000)
|
||||||
|
await fileChooser.setFiles(path.join(dirname, 'test-image.jpg'))
|
||||||
|
|
||||||
|
await page.waitForSelector('button#action-save')
|
||||||
|
await page.locator('button#action-save').click()
|
||||||
|
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||||
|
await wait(1000)
|
||||||
|
|
||||||
|
const mediaID = page.url().split('/').pop()
|
||||||
|
|
||||||
|
const { doc: mediaDoc } = await client.findByID({
|
||||||
|
id: mediaID,
|
||||||
|
slug: withoutMetadataSlug,
|
||||||
|
auth: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const acceptableFileSizes = [2424, 2445]
|
||||||
|
|
||||||
|
expect(acceptableFileSizes).toContain(mediaDoc.sizes.sizeTwo.filesize)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should only upload image with metadata if jpeg mimetype', async () => {
|
||||||
|
await page.goto(withOnlyJPEGMetadataURL.create)
|
||||||
|
await page.waitForURL(withOnlyJPEGMetadataURL.create)
|
||||||
|
|
||||||
|
const fileChooserPromiseForJPEG = page.waitForEvent('filechooser')
|
||||||
|
await page.getByText('Select a file').click()
|
||||||
|
const fileChooserForJPEG = await fileChooserPromiseForJPEG
|
||||||
|
await wait(1000)
|
||||||
|
await fileChooserForJPEG.setFiles(path.join(dirname, 'test-image.jpg'))
|
||||||
|
|
||||||
|
await page.waitForSelector('button#action-save')
|
||||||
|
await page.locator('button#action-save').click()
|
||||||
|
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||||
|
await wait(1000)
|
||||||
|
|
||||||
|
const jpegMediaID = page.url().split('/').pop()
|
||||||
|
|
||||||
|
const { doc: jpegMediaDoc } = await client.findByID({
|
||||||
|
id: jpegMediaID,
|
||||||
|
slug: withOnlyJPEGMetadataSlug,
|
||||||
|
auth: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const acceptableFileSizesForJPEG = [9554, 9575]
|
||||||
|
|
||||||
|
// without metadata appended, the jpeg image filesize would be 2424
|
||||||
|
expect(acceptableFileSizesForJPEG).toContain(jpegMediaDoc.sizes.sizeThree.filesize)
|
||||||
|
|
||||||
|
await page.goto(withOnlyJPEGMetadataURL.create)
|
||||||
|
await page.waitForURL(withOnlyJPEGMetadataURL.create)
|
||||||
|
|
||||||
|
const fileChooserPromiseForWEBP = page.waitForEvent('filechooser')
|
||||||
|
await page.getByText('Select a file').click()
|
||||||
|
const fileChooserForWEBP = await fileChooserPromiseForWEBP
|
||||||
|
await wait(1000)
|
||||||
|
await fileChooserForWEBP.setFiles(path.join(dirname, 'animated.webp'))
|
||||||
|
|
||||||
|
await page.waitForSelector('button#action-save')
|
||||||
|
await page.locator('button#action-save').click()
|
||||||
|
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||||
|
await wait(1000)
|
||||||
|
|
||||||
|
const webpMediaID = page.url().split('/').pop()
|
||||||
|
|
||||||
|
const { doc: webpMediaDoc } = await client.findByID({
|
||||||
|
id: webpMediaID,
|
||||||
|
slug: withOnlyJPEGMetadataSlug,
|
||||||
|
auth: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// With metadata, the animated image filesize would be 218762
|
||||||
|
expect(webpMediaDoc.sizes.sizeThree.filesize).toEqual(211638)
|
||||||
|
})
|
||||||
|
|
||||||
describe('image manipulation', () => {
|
describe('image manipulation', () => {
|
||||||
test('should crop image correctly', async () => {
|
test('should crop image correctly', async () => {
|
||||||
const positions = {
|
const positions = {
|
||||||
|
|||||||
@@ -12,3 +12,6 @@ export const unstoredMediaSlug = 'unstored-media'
|
|||||||
export const versionSlug = 'versions'
|
export const versionSlug = 'versions'
|
||||||
export const animatedTypeMedia = 'animated-type-media'
|
export const animatedTypeMedia = 'animated-type-media'
|
||||||
export const customUploadFieldSlug = 'custom-upload-field'
|
export const customUploadFieldSlug = 'custom-upload-field'
|
||||||
|
export const withMetadataSlug = 'with-meta-data'
|
||||||
|
export const withoutMetadataSlug = 'without-meta-data'
|
||||||
|
export const withOnlyJPEGMetadataSlug = 'with-only-jpeg-meta-data'
|
||||||
|
|||||||
Reference in New Issue
Block a user