Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions __tests__/server/duplicatePageToTenant.server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
jest.mock('../../src/constants/defaults', () => ({
// eslint-disable-next-line @typescript-eslint/no-require-imports
DEFAULT_BLOCKS: require('./fixtures/mockBlocks').DEFAULT_BLOCKS,
}))

jest.mock('../../src/blocks/NACMedia/config', () => ({
// eslint-disable-next-line @typescript-eslint/no-require-imports
NACMediaBlock: require('./fixtures/mockBlocks').NACMediaBlock,
}))

jest.mock('../../src/payload.config', () => ({}))

jest.mock('payload', () => ({
getPayload: jest.fn(),
}))

import { duplicatePageToTenant } from '@/collections/Pages/endpoints/duplicatePageToTenant'
import type { Payload, PayloadRequest } from 'payload'
import { getPayload } from 'payload'

const mockFind = jest.fn()
const mockCreate = jest.fn()

const mockTenant = { id: 42, name: 'Test AC', slug: 'tac' }

beforeEach(() => {
jest
.mocked(getPayload)
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
.mockResolvedValue({ find: mockFind, create: mockCreate } as unknown as Payload)
mockFind.mockReset().mockResolvedValue({ docs: [mockTenant] })
mockCreate.mockReset().mockResolvedValue({ id: 99 })
})

function buildRequest(
selectedTenantId: string | undefined,
body: Record<string, unknown>,
): PayloadRequest {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
routeParams: selectedTenantId !== undefined ? { selectedTenantId } : undefined,
json: async () => body,
} as unknown as PayloadRequest
}

describe('duplicatePageToTenant', () => {
it('creates the page as a draft', async () => {
const req = buildRequest('42', {
newPage: { title: 'About', slug: 'about', layout: [] },
})
await duplicatePageToTenant(req)
expect(mockCreate).toHaveBeenCalledWith(expect.objectContaining({ draft: true }))
})

it('appends " - Copy" to the title and "-copy" to the slug', async () => {
const req = buildRequest('42', {
newPage: { title: 'About Us', slug: 'about-us', layout: [] },
})
await duplicatePageToTenant(req)
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({ title: 'About Us - Copy', slug: 'about-us-copy' }),
}),
)
})

it('sets the tenant from the lookup result', async () => {
const req = buildRequest('42', {
newPage: { title: 'About', slug: 'about', layout: [] },
})
await duplicatePageToTenant(req)
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({ tenant: mockTenant }),
}),
)
})

it('passes layout through clearLayoutRelationships', async () => {
const req = buildRequest('42', {
newPage: {
title: 'About',
slug: 'about',
layout: [{ blockType: 'singleBlogPost', post: 123, backgroundColor: 'red' }],
},
})
await duplicatePageToTenant(req)
const layout = mockCreate.mock.calls[0][0].data.layout
expect(layout[0]).not.toHaveProperty('post')
expect(layout[0]).toHaveProperty('backgroundColor', 'red')
})

it('falls back to empty layout when newPage.layout is absent', async () => {
const req = buildRequest('42', { newPage: { title: 'About', slug: 'about' } })
await duplicatePageToTenant(req)
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({ data: expect.objectContaining({ layout: [] }) }),
)
})

it('looks up the tenant by selectedTenantId', async () => {
const req = buildRequest('99', {
newPage: { title: 'About', slug: 'about', layout: [] },
})
await duplicatePageToTenant(req)
expect(mockFind).toHaveBeenCalledWith(
expect.objectContaining({
collection: 'tenants',
where: { id: { equals: '99' } },
}),
)
})
})
55 changes: 55 additions & 0 deletions __tests__/server/fixtures/mockBlocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Minimal block configs for testing clearLayoutRelationships.
// These mirror the shape of Payload Block configs without importing from payload.
export const DEFAULT_BLOCKS = [
{
slug: 'singleBlogPost',
fields: [
{ type: 'relationship', name: 'post', relationTo: 'posts' },
{ type: 'text', name: 'backgroundColor' },
],
},
{
slug: 'mediaBlock',
fields: [{ type: 'upload', name: 'media', relationTo: 'media' }],
},
{
slug: 'sponsorsBlock',
fields: [
{ type: 'relationship', name: 'sponsors', relationTo: 'sponsors', hasMany: true },
{ type: 'text', name: 'sponsorsLayout' },
],
},
{
slug: 'singleEvent',
fields: [
{ type: 'relationship', name: 'event', relationTo: 'events' },
{ type: 'text', name: 'backgroundColor' },
],
},
{
slug: 'blogList',
fields: [
{ type: 'text', name: 'postOptions' },
{
type: 'group',
name: 'staticOptions',
fields: [{ type: 'relationship', name: 'staticPosts', relationTo: 'posts', hasMany: true }],
},
],
},
{
slug: 'imageLinkGrid',
fields: [
{
type: 'array',
name: 'columns',
fields: [
{ type: 'upload', name: 'image', relationTo: 'media' },
{ type: 'text', name: 'caption' },
],
},
],
},
]

export const NACMediaBlock = { slug: 'nacMedia', fields: [] }
Loading
Loading