payload-mcp-toolkit 0.7.0 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -8
- package/dist/api-keys.js +57 -21
- package/dist/api-keys.js.map +1 -1
- package/dist/auth-strategy.d.ts +18 -7
- package/dist/auth-strategy.js +54 -12
- package/dist/auth-strategy.js.map +1 -1
- package/dist/tools/_helpers.d.ts +34 -0
- package/dist/tools/_helpers.js +98 -0
- package/dist/tools/_helpers.js.map +1 -1
- package/dist/tools/publish-draft.js +33 -1
- package/dist/tools/publish-draft.js.map +1 -1
- package/dist/tools/publish-global-draft.js +30 -1
- package/dist/tools/publish-global-draft.js.map +1 -1
- package/package.json +29 -15
- package/dist/__tests__/api-keys.test.js +0 -292
- package/dist/__tests__/api-keys.test.js.map +0 -1
- package/dist/__tests__/auth-strategy.test.js +0 -681
- package/dist/__tests__/auth-strategy.test.js.map +0 -1
- package/dist/__tests__/conflict-detection.test.js +0 -69
- package/dist/__tests__/conflict-detection.test.js.map +0 -1
- package/dist/__tests__/delete-document.test.js +0 -70
- package/dist/__tests__/delete-document.test.js.map +0 -1
- package/dist/__tests__/endpoint.test.js +0 -143
- package/dist/__tests__/endpoint.test.js.map +0 -1
- package/dist/__tests__/find-document.test.js +0 -178
- package/dist/__tests__/find-document.test.js.map +0 -1
- package/dist/__tests__/find-global.test.js +0 -173
- package/dist/__tests__/find-global.test.js.map +0 -1
- package/dist/__tests__/global-versions.test.js +0 -183
- package/dist/__tests__/global-versions.test.js.map +0 -1
- package/dist/__tests__/hash.test.js +0 -58
- package/dist/__tests__/hash.test.js.map +0 -1
- package/dist/__tests__/index-integration.test.js +0 -191
- package/dist/__tests__/index-integration.test.js.map +0 -1
- package/dist/__tests__/introspection.test.js +0 -659
- package/dist/__tests__/introspection.test.js.map +0 -1
- package/dist/__tests__/patch-global-layout.test.js +0 -474
- package/dist/__tests__/patch-global-layout.test.js.map +0 -1
- package/dist/__tests__/patch-layout.test.js +0 -171
- package/dist/__tests__/patch-layout.test.js.map +0 -1
- package/dist/__tests__/registry.test.js +0 -795
- package/dist/__tests__/registry.test.js.map +0 -1
- package/dist/__tests__/resources.test.js +0 -139
- package/dist/__tests__/resources.test.js.map +0 -1
- package/dist/__tests__/update-global.test.js +0 -157
- package/dist/__tests__/update-global.test.js.map +0 -1
- package/dist/__tests__/url-validator.test.js +0 -326
- package/dist/__tests__/url-validator.test.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/__tests__/introspection.test.ts"],"sourcesContent":["import { describe, it, expect } from 'vitest'\r\nimport type { Block, CollectionConfig, GlobalConfig } from 'payload'\r\nimport {\r\n introspectCollection,\r\n introspectCollections,\r\n introspectBlocks,\r\n buildBlockNestingMap,\r\n buildRelationshipGraph,\r\n hasGlobalDrafts,\r\n introspectGlobal,\r\n introspectGlobals,\r\n} from '../introspection'\r\n\r\n// ─── Sample schema (kept inline so the test is self-contained) ─────\r\n\r\nconst Media: CollectionConfig = {\r\n slug: 'media',\r\n upload: true,\r\n fields: [{ name: 'alt', type: 'text', required: true }],\r\n}\r\n\r\nconst Categories: CollectionConfig = {\r\n slug: 'categories',\r\n fields: [\r\n { name: 'name', type: 'text', required: true },\r\n { name: 'slug', type: 'text', required: true },\r\n ],\r\n}\r\n\r\nconst Authors: CollectionConfig = {\r\n slug: 'authors',\r\n fields: [\r\n { name: 'name', type: 'text', required: true },\r\n { name: 'slug', type: 'text', required: true },\r\n { name: 'avatar', type: 'upload', relationTo: 'media' },\r\n ],\r\n}\r\n\r\n// Leaf-style blocks\r\nconst Heading: Block = {\r\n slug: 'heading',\r\n fields: [\r\n { name: 'text', type: 'text', required: true },\r\n {\r\n name: 'level',\r\n type: 'select',\r\n options: ['h1', 'h2', 'h3'],\r\n defaultValue: 'h2',\r\n },\r\n {\r\n name: 'align',\r\n type: 'select',\r\n options: ['left', 'center', 'right'],\r\n defaultValue: 'left',\r\n },\r\n ],\r\n}\r\n\r\nconst RichText: Block = {\r\n slug: 'richText',\r\n fields: [{ name: 'content', type: 'richText' }],\r\n}\r\n\r\nconst ImageBlock: Block = {\r\n slug: 'image',\r\n fields: [\r\n { name: 'image', type: 'upload', relationTo: 'media', required: true },\r\n { name: 'caption', type: 'text' },\r\n ],\r\n}\r\n\r\n// Container-style blocks (have nested blocks fields)\r\nconst FullWidth: Block = {\r\n slug: 'fullWidth',\r\n fields: [\r\n {\r\n name: 'content',\r\n type: 'blocks',\r\n blocks: [Heading, RichText, ImageBlock],\r\n },\r\n ],\r\n}\r\n\r\nconst HeadingOnly: Block = {\r\n slug: 'headingOnly',\r\n fields: [\r\n {\r\n name: 'content',\r\n type: 'blocks',\r\n maxRows: 1,\r\n blocks: [Heading],\r\n },\r\n ],\r\n}\r\n\r\nconst CtaBanner: Block = {\r\n slug: 'ctaBanner',\r\n fields: [\r\n { name: 'headline', type: 'text', required: true },\r\n { name: 'buttonLabel', type: 'text' },\r\n { name: 'buttonHref', type: 'text' },\r\n ],\r\n}\r\n\r\n// Deeply-nestable container — exercises the recursive path\r\nconst Accordion: Block = {\r\n slug: 'accordion',\r\n fields: [\r\n {\r\n name: 'panels',\r\n type: 'array',\r\n fields: [\r\n { name: 'title', type: 'text' },\r\n {\r\n name: 'body',\r\n type: 'blocks',\r\n blocks: [Heading, RichText, FullWidth],\r\n },\r\n ],\r\n },\r\n ],\r\n}\r\n\r\nconst allBlocks: Block[] = [Heading, RichText, ImageBlock, FullWidth, HeadingOnly, CtaBanner, Accordion]\r\n\r\nconst Posts: CollectionConfig = {\r\n slug: 'posts',\r\n versions: { drafts: true },\r\n fields: [\r\n { name: 'title', type: 'text', required: true },\r\n { name: 'slug', type: 'text', required: true },\r\n { name: 'featured', type: 'checkbox' },\r\n { name: 'category', type: 'relationship', relationTo: 'categories' },\r\n {\r\n name: 'authors',\r\n type: 'relationship',\r\n relationTo: 'authors',\r\n hasMany: true,\r\n },\r\n { name: 'coverImage', type: 'upload', relationTo: 'media' },\r\n {\r\n name: 'tags',\r\n type: 'array',\r\n fields: [{ name: 'tag', type: 'text' }],\r\n },\r\n ],\r\n}\r\n\r\nconst Pages: CollectionConfig = {\r\n slug: 'pages',\r\n versions: { drafts: true },\r\n fields: [\r\n {\r\n type: 'tabs',\r\n tabs: [\r\n {\r\n name: 'hero',\r\n label: 'Hero',\r\n fields: [\r\n { name: 'heroTitle', type: 'text' },\r\n {\r\n name: 'heroSize',\r\n type: 'select',\r\n options: ['small', 'medium', 'large'],\r\n defaultValue: 'medium',\r\n },\r\n ],\r\n },\r\n {\r\n label: 'Content',\r\n fields: [\r\n { name: 'slug', type: 'text', required: true },\r\n { name: 'layout', type: 'blocks', blocks: [FullWidth, HeadingOnly, CtaBanner, Accordion] },\r\n ],\r\n },\r\n ],\r\n },\r\n ],\r\n}\r\n\r\n// ─── introspectCollection ──────────────────────────────────────────\r\n\r\ndescribe('introspectCollection', () => {\r\n it('extracts Posts collection fields, relationships, and draft status', () => {\r\n const schema = introspectCollection(Posts)\r\n\r\n expect(schema.slug).toBe('posts')\r\n expect(schema.hasDrafts).toBe(true)\r\n\r\n const fieldNames = schema.fields.map((f) => f.name)\r\n expect(fieldNames).toContain('title')\r\n expect(fieldNames).toContain('slug')\r\n expect(fieldNames).toContain('featured')\r\n expect(fieldNames).toContain('tags')\r\n\r\n const relFieldNames = schema.relationships.map((r) => r.fieldName)\r\n expect(relFieldNames).toContain('category')\r\n expect(relFieldNames).toContain('authors')\r\n\r\n const cover = schema.relationships.find((r) => r.fieldName === 'coverImage')\r\n expect(cover).toBeDefined()\r\n expect(cover!.relationTo).toBe('media')\r\n\r\n expect(schema.searchableFields).toContain('title')\r\n expect(schema.searchableFields).toContain('slug')\r\n })\r\n\r\n it('extracts Pages collection with tab-nested fields', () => {\r\n const schema = introspectCollection(Pages)\r\n\r\n expect(schema.slug).toBe('pages')\r\n expect(schema.hasDrafts).toBe(true)\r\n\r\n const fieldNames = schema.fields.map((f) => f.name)\r\n expect(fieldNames).toContain('heroTitle')\r\n expect(fieldNames).toContain('slug')\r\n expect(fieldNames).toContain('layout')\r\n })\r\n\r\n it('detects collections without draft support', () => {\r\n const schema = introspectCollection(Categories)\r\n expect(schema.hasDrafts).toBe(false)\r\n })\r\n\r\n it('extracts select field options from Pages heroSize', () => {\r\n const schema = introspectCollection(Pages)\r\n const heroSize = schema.fields.find((f) => f.name === 'heroSize')\r\n expect(heroSize).toBeDefined()\r\n expect(heroSize!.type).toBe('select')\r\n expect(heroSize!.options).toBeDefined()\r\n expect(heroSize!.options!.length).toBe(3)\r\n })\r\n})\r\n\r\n// ─── introspectBlocks (flat catalog) ───────────────────────────────\r\n\r\ndescribe('introspectBlocks', () => {\r\n it('returns a flat catalog of every block with no section/leaf split', () => {\r\n const catalog = introspectBlocks(allBlocks)\r\n const slugs = catalog.blocks.map((b) => b.slug)\r\n expect(slugs).toEqual([\r\n 'heading',\r\n 'richText',\r\n 'image',\r\n 'fullWidth',\r\n 'headingOnly',\r\n 'ctaBanner',\r\n 'accordion',\r\n ])\r\n })\r\n\r\n it('extracts each block\\'s fields including select options', () => {\r\n const catalog = introspectBlocks(allBlocks)\r\n const heading = catalog.blocks.find((b) => b.slug === 'heading')\r\n expect(heading).toBeDefined()\r\n const headingFieldNames = heading!.fields.map((f) => f.name)\r\n expect(headingFieldNames).toEqual(['text', 'level', 'align'])\r\n const level = heading!.fields.find((f) => f.name === 'level')\r\n expect(level!.options).toBeDefined()\r\n })\r\n})\r\n\r\n// ─── buildBlockNestingMap ──────────────────────────────────────────\r\n\r\ndescribe('buildBlockNestingMap', () => {\r\n it('records the layout field on Pages with the slugs it accepts', () => {\r\n const map = buildBlockNestingMap([Pages, Posts], allBlocks)\r\n const pageLayout = map.find(\r\n (e) => e.ownerType === 'collection' && e.owner === 'pages' && e.fieldPath === 'layout',\r\n )\r\n expect(pageLayout).toBeDefined()\r\n expect(pageLayout!.acceptedBlockSlugs).toEqual(['fullWidth', 'headingOnly', 'ctaBanner', 'accordion'])\r\n })\r\n\r\n it('records nested blocks fields inside container blocks', () => {\r\n const map = buildBlockNestingMap([Pages], allBlocks)\r\n\r\n const fullWidthContent = map.find(\r\n (e) => e.ownerType === 'block' && e.owner === 'fullWidth' && e.fieldPath === 'content',\r\n )\r\n expect(fullWidthContent).toBeDefined()\r\n expect(fullWidthContent!.acceptedBlockSlugs).toEqual(['heading', 'richText', 'image'])\r\n\r\n const headingOnly = map.find(\r\n (e) => e.ownerType === 'block' && e.owner === 'headingOnly' && e.fieldPath === 'content',\r\n )\r\n expect(headingOnly!.acceptedBlockSlugs).toEqual(['heading'])\r\n expect(headingOnly!.maxRows).toBe(1)\r\n })\r\n\r\n it('handles arbitrarily-deep nesting via array fields inside blocks', () => {\r\n const map = buildBlockNestingMap([Pages], allBlocks)\r\n\r\n const accordionPanelBody = map.find(\r\n (e) =>\r\n e.ownerType === 'block' && e.owner === 'accordion' && e.fieldPath === 'panels[].body',\r\n )\r\n expect(accordionPanelBody).toBeDefined()\r\n expect(accordionPanelBody!.acceptedBlockSlugs).toEqual(['heading', 'richText', 'fullWidth'])\r\n })\r\n\r\n it('omits unknown slugs not present in the block list', () => {\r\n const Stray: CollectionConfig = {\r\n slug: 'stray',\r\n fields: [\r\n {\r\n name: 'layout',\r\n type: 'blocks',\r\n blocks: [Heading, { slug: 'mystery', fields: [] } as Block],\r\n },\r\n ],\r\n }\r\n const map = buildBlockNestingMap([Stray], [Heading]) // mystery not in catalog\r\n const stray = map.find((e) => e.owner === 'stray' && e.fieldPath === 'layout')\r\n expect(stray!.acceptedBlockSlugs).toEqual(['heading'])\r\n })\r\n\r\n it('omits fixed blocks (no nested blocks fields) from the map', () => {\r\n const map = buildBlockNestingMap([Pages], allBlocks)\r\n const ctaEntries = map.filter((e) => e.owner === 'ctaBanner')\r\n expect(ctaEntries).toHaveLength(0)\r\n })\r\n})\r\n\r\n// ─── buildRelationshipGraph ────────────────────────────────────────\r\n\r\n// ─── Global introspection ──────────────────────────────────────────\r\n\r\nconst SiteSettings: GlobalConfig = {\r\n slug: 'site-settings',\r\n fields: [\r\n { name: 'siteName', type: 'text', required: true },\r\n { name: 'tagline', type: 'text' },\r\n {\r\n name: 'social',\r\n type: 'group',\r\n fields: [\r\n { name: 'twitter', type: 'text' },\r\n { name: 'instagram', type: 'text' },\r\n ],\r\n },\r\n ],\r\n}\r\n\r\nconst FooterGlobal: GlobalConfig = {\r\n slug: 'footer',\r\n versions: { drafts: true },\r\n fields: [\r\n {\r\n name: 'layout',\r\n type: 'blocks',\r\n blocks: [Heading, CtaBanner],\r\n },\r\n ],\r\n}\r\n\r\nconst HeaderGlobal: GlobalConfig = {\r\n slug: 'header',\r\n fields: [\r\n {\r\n name: 'menu',\r\n type: 'group',\r\n fields: [\r\n { name: 'label', type: 'text' },\r\n {\r\n name: 'links',\r\n type: 'blocks',\r\n blocks: [Heading],\r\n },\r\n ],\r\n },\r\n ],\r\n}\r\n\r\ndescribe('hasGlobalDrafts', () => {\r\n it('returns true for { versions: { drafts: true } }', () => {\r\n expect(hasGlobalDrafts({ slug: 'g', versions: { drafts: true }, fields: [] })).toBe(true)\r\n })\r\n\r\n it('returns false for { versions: { drafts: false } }', () => {\r\n expect(hasGlobalDrafts({ slug: 'g', versions: { drafts: false }, fields: [] })).toBe(false)\r\n })\r\n\r\n it('returns false when versions is undefined', () => {\r\n expect(hasGlobalDrafts({ slug: 'g', fields: [] })).toBe(false)\r\n })\r\n\r\n it('returns false for versions without drafts key', () => {\r\n expect(\r\n hasGlobalDrafts({ slug: 'g', versions: { maxPerDoc: 10 }, fields: [] } as GlobalConfig),\r\n ).toBe(false)\r\n })\r\n})\r\n\r\ndescribe('introspectGlobal', () => {\r\n it('extracts SiteSettings fields and draft/live-preview flags', () => {\r\n const schema = introspectGlobal(SiteSettings)\r\n expect(schema.slug).toBe('site-settings')\r\n expect(schema.hasDrafts).toBe(false)\r\n expect(schema.hasLivePreview).toBe(false)\r\n const names = schema.fields.map((f) => f.name)\r\n expect(names).toContain('siteName')\r\n expect(names).toContain('tagline')\r\n const social = schema.fields.find((f) => f.name === 'social')\r\n expect(social?.type).toBe('group')\r\n expect(social?.fields?.map((f) => f.name)).toEqual(['twitter', 'instagram'])\r\n })\r\n\r\n it('reports hasDrafts: true when versions.drafts is set', () => {\r\n expect(introspectGlobal(FooterGlobal).hasDrafts).toBe(true)\r\n })\r\n})\r\n\r\ndescribe('introspectGlobals', () => {\r\n it('returns an empty Map for []', () => {\r\n expect(introspectGlobals([]).size).toBe(0)\r\n })\r\n\r\n it('keys the map by slug', () => {\r\n const map = introspectGlobals([SiteSettings, FooterGlobal])\r\n expect(map.has('site-settings')).toBe(true)\r\n expect(map.has('footer')).toBe(true)\r\n })\r\n})\r\n\r\ndescribe('buildBlockNestingMap with globals', () => {\r\n it('emits an edge with ownerType \"global\" for a top-level blocks field', () => {\r\n const map = buildBlockNestingMap([], [FooterGlobal], allBlocks)\r\n const edge = map.find(\r\n (e) => e.ownerType === 'global' && e.owner === 'footer' && e.fieldPath === 'layout',\r\n )\r\n expect(edge).toBeDefined()\r\n expect(edge!.acceptedBlockSlugs).toEqual(['heading', 'ctaBanner'])\r\n })\r\n\r\n it('walks group-nested blocks fields under a global with the dotted path', () => {\r\n const map = buildBlockNestingMap([], [HeaderGlobal], allBlocks)\r\n const edge = map.find(\r\n (e) => e.ownerType === 'global' && e.owner === 'header' && e.fieldPath === 'menu.links',\r\n )\r\n expect(edge).toBeDefined()\r\n expect(edge!.acceptedBlockSlugs).toEqual(['heading'])\r\n })\r\n\r\n it('two-arg call (no globals) produces the same edges as before — regression guard', () => {\r\n const before = buildBlockNestingMap([Pages], allBlocks)\r\n const after = buildBlockNestingMap([Pages], [], allBlocks)\r\n expect(after).toEqual(before)\r\n })\r\n\r\n it('invariant: throws when (owner, fieldPath) appears with different ownerTypes', () => {\r\n const ClashCollection: CollectionConfig = {\r\n slug: 'site-settings',\r\n fields: [{ name: 'layout', type: 'blocks', blocks: [Heading] }],\r\n }\r\n const ClashGlobal: GlobalConfig = {\r\n slug: 'site-settings',\r\n fields: [{ name: 'layout', type: 'blocks', blocks: [Heading] }],\r\n }\r\n expect(() => buildBlockNestingMap([ClashCollection], [ClashGlobal], [Heading])).toThrow(\r\n /invariant violated/i,\r\n )\r\n })\r\n})\r\n\r\ndescribe('buildRelationshipGraph', () => {\r\n it('builds correct graph from sample collections', () => {\r\n const schemas = introspectCollections([Posts, Pages, Categories, Authors, Media])\r\n const edges = buildRelationshipGraph(schemas)\r\n\r\n const postEdges = edges.filter((e) => e.fromCollection === 'posts')\r\n const postTargets = postEdges.map((e) => e.toCollection)\r\n expect(postTargets).toContain('categories')\r\n expect(postTargets).toContain('authors')\r\n expect(postTargets).toContain('media')\r\n\r\n const authorEdges = edges.filter((e) => e.fromCollection === 'authors')\r\n expect(authorEdges.map((e) => e.toCollection)).toContain('media')\r\n })\r\n})\r\n"],"names":["describe","it","expect","introspectCollection","introspectCollections","introspectBlocks","buildBlockNestingMap","buildRelationshipGraph","hasGlobalDrafts","introspectGlobal","introspectGlobals","Media","slug","upload","fields","name","type","required","Categories","Authors","relationTo","Heading","options","defaultValue","RichText","ImageBlock","FullWidth","blocks","HeadingOnly","maxRows","CtaBanner","Accordion","allBlocks","Posts","versions","drafts","hasMany","Pages","tabs","label","schema","toBe","hasDrafts","fieldNames","map","f","toContain","relFieldNames","relationships","r","fieldName","cover","find","toBeDefined","searchableFields","heroSize","length","catalog","slugs","b","toEqual","heading","headingFieldNames","level","pageLayout","e","ownerType","owner","fieldPath","acceptedBlockSlugs","fullWidthContent","headingOnly","accordionPanelBody","Stray","stray","ctaEntries","filter","toHaveLength","SiteSettings","FooterGlobal","HeaderGlobal","maxPerDoc","hasLivePreview","names","social","size","has","edge","before","after","ClashCollection","ClashGlobal","toThrow","schemas","edges","postEdges","fromCollection","postTargets","toCollection","authorEdges"],"mappings":"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"}
|
|
@@ -1,474 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { introspectBlocks, buildBlockNestingMap } from '../introspection';
|
|
3
|
-
import { createPatchGlobalLayoutTool } from '../tools/patch-global-layout';
|
|
4
|
-
const Heading = {
|
|
5
|
-
slug: 'heading',
|
|
6
|
-
fields: [
|
|
7
|
-
{
|
|
8
|
-
name: 'text',
|
|
9
|
-
type: 'text'
|
|
10
|
-
}
|
|
11
|
-
]
|
|
12
|
-
};
|
|
13
|
-
const CtaBanner = {
|
|
14
|
-
slug: 'ctaBanner',
|
|
15
|
-
fields: [
|
|
16
|
-
{
|
|
17
|
-
name: 'label',
|
|
18
|
-
type: 'text'
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
};
|
|
22
|
-
const Container = {
|
|
23
|
-
slug: 'container',
|
|
24
|
-
fields: [
|
|
25
|
-
{
|
|
26
|
-
name: 'sections',
|
|
27
|
-
type: 'blocks',
|
|
28
|
-
blocks: [
|
|
29
|
-
Heading,
|
|
30
|
-
CtaBanner
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
-
};
|
|
35
|
-
const FooterGlobal = {
|
|
36
|
-
slug: 'footer',
|
|
37
|
-
versions: {
|
|
38
|
-
drafts: true
|
|
39
|
-
},
|
|
40
|
-
fields: [
|
|
41
|
-
{
|
|
42
|
-
name: 'sections',
|
|
43
|
-
type: 'blocks',
|
|
44
|
-
blocks: [
|
|
45
|
-
Heading,
|
|
46
|
-
CtaBanner,
|
|
47
|
-
Container
|
|
48
|
-
]
|
|
49
|
-
}
|
|
50
|
-
]
|
|
51
|
-
};
|
|
52
|
-
const PlainSettings = {
|
|
53
|
-
slug: 'plain',
|
|
54
|
-
fields: [
|
|
55
|
-
{
|
|
56
|
-
name: 'siteName',
|
|
57
|
-
type: 'text'
|
|
58
|
-
}
|
|
59
|
-
]
|
|
60
|
-
};
|
|
61
|
-
function buildReq() {
|
|
62
|
-
return {
|
|
63
|
-
payload: {
|
|
64
|
-
findGlobal: vi.fn(),
|
|
65
|
-
updateGlobal: vi.fn(),
|
|
66
|
-
logger: {
|
|
67
|
-
info: vi.fn(),
|
|
68
|
-
warn: vi.fn(),
|
|
69
|
-
error: vi.fn()
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
context: {},
|
|
73
|
-
user: null
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
const allBlocks = [
|
|
77
|
-
Heading,
|
|
78
|
-
CtaBanner,
|
|
79
|
-
Container
|
|
80
|
-
];
|
|
81
|
-
const catalog = introspectBlocks(allBlocks);
|
|
82
|
-
const nesting = buildBlockNestingMap([], [
|
|
83
|
-
FooterGlobal
|
|
84
|
-
], allBlocks);
|
|
85
|
-
const drafts = new Set([
|
|
86
|
-
'footer'
|
|
87
|
-
]);
|
|
88
|
-
describe('patchGlobalLayout', ()=>{
|
|
89
|
-
it('returns null when no global has a blocks field (factory short-circuit)', ()=>{
|
|
90
|
-
const emptyNesting = buildBlockNestingMap([], [
|
|
91
|
-
PlainSettings
|
|
92
|
-
], allBlocks);
|
|
93
|
-
const tool = createPatchGlobalLayoutTool(catalog, emptyNesting, new Set());
|
|
94
|
-
expect(tool).toBeNull();
|
|
95
|
-
});
|
|
96
|
-
it('registers when at least one global has a blocks field', ()=>{
|
|
97
|
-
const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts);
|
|
98
|
-
expect(tool).not.toBeNull();
|
|
99
|
-
expect(tool.name).toBe('patchGlobalLayout');
|
|
100
|
-
});
|
|
101
|
-
it('append: writes existing + new blocks back via updateGlobal', async ()=>{
|
|
102
|
-
const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts);
|
|
103
|
-
const req = buildReq();
|
|
104
|
-
req.payload.findGlobal.mockResolvedValue({
|
|
105
|
-
sections: [
|
|
106
|
-
{
|
|
107
|
-
blockType: 'heading',
|
|
108
|
-
text: 'a'
|
|
109
|
-
}
|
|
110
|
-
]
|
|
111
|
-
});
|
|
112
|
-
req.payload.updateGlobal.mockResolvedValue({});
|
|
113
|
-
await tool.handler({
|
|
114
|
-
slug: 'footer',
|
|
115
|
-
layoutField: 'sections',
|
|
116
|
-
blocks: [
|
|
117
|
-
{
|
|
118
|
-
blockType: 'ctaBanner',
|
|
119
|
-
label: 'Buy'
|
|
120
|
-
}
|
|
121
|
-
],
|
|
122
|
-
operation: 'append'
|
|
123
|
-
}, req, {});
|
|
124
|
-
expect(req.payload.updateGlobal).toHaveBeenCalledWith(expect.objectContaining({
|
|
125
|
-
slug: 'footer',
|
|
126
|
-
draft: true,
|
|
127
|
-
data: {
|
|
128
|
-
sections: [
|
|
129
|
-
{
|
|
130
|
-
blockType: 'heading',
|
|
131
|
-
text: 'a'
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
blockType: 'ctaBanner',
|
|
135
|
-
label: 'Buy'
|
|
136
|
-
}
|
|
137
|
-
]
|
|
138
|
-
}
|
|
139
|
-
}));
|
|
140
|
-
});
|
|
141
|
-
it('replaceAt index 0 swaps the first block', async ()=>{
|
|
142
|
-
const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts);
|
|
143
|
-
const req = buildReq();
|
|
144
|
-
req.payload.findGlobal.mockResolvedValue({
|
|
145
|
-
sections: [
|
|
146
|
-
{
|
|
147
|
-
blockType: 'heading',
|
|
148
|
-
text: 'a'
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
blockType: 'ctaBanner',
|
|
152
|
-
label: 'old'
|
|
153
|
-
}
|
|
154
|
-
]
|
|
155
|
-
});
|
|
156
|
-
req.payload.updateGlobal.mockResolvedValue({});
|
|
157
|
-
await tool.handler({
|
|
158
|
-
slug: 'footer',
|
|
159
|
-
layoutField: 'sections',
|
|
160
|
-
blocks: [
|
|
161
|
-
{
|
|
162
|
-
blockType: 'heading',
|
|
163
|
-
text: 'new-first'
|
|
164
|
-
}
|
|
165
|
-
],
|
|
166
|
-
operation: 'replaceAt',
|
|
167
|
-
insertIndex: 0
|
|
168
|
-
}, req, {});
|
|
169
|
-
expect(req.payload.updateGlobal).toHaveBeenCalledWith(expect.objectContaining({
|
|
170
|
-
data: {
|
|
171
|
-
sections: [
|
|
172
|
-
{
|
|
173
|
-
blockType: 'heading',
|
|
174
|
-
text: 'new-first'
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
blockType: 'ctaBanner',
|
|
178
|
-
label: 'old'
|
|
179
|
-
}
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
}));
|
|
183
|
-
});
|
|
184
|
-
it('rejects blocks whose slug is not in the field allow-list', async ()=>{
|
|
185
|
-
const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts);
|
|
186
|
-
const req = buildReq();
|
|
187
|
-
const result = await tool.handler({
|
|
188
|
-
slug: 'footer',
|
|
189
|
-
layoutField: 'sections',
|
|
190
|
-
// ImageBlock is not in the FooterGlobal allow list.
|
|
191
|
-
blocks: [
|
|
192
|
-
{
|
|
193
|
-
blockType: 'image',
|
|
194
|
-
src: 'x'
|
|
195
|
-
}
|
|
196
|
-
],
|
|
197
|
-
operation: 'full'
|
|
198
|
-
}, req, {});
|
|
199
|
-
const text = result.content[0].text;
|
|
200
|
-
expect(text).toMatch(/unknown blockType/i);
|
|
201
|
-
expect(text).toContain('image');
|
|
202
|
-
expect(req.payload.updateGlobal).not.toHaveBeenCalled();
|
|
203
|
-
});
|
|
204
|
-
it('validates nested blocks fields with breadcrumb path on failure', async ()=>{
|
|
205
|
-
const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts);
|
|
206
|
-
const req = buildReq();
|
|
207
|
-
const result = await tool.handler({
|
|
208
|
-
slug: 'footer',
|
|
209
|
-
layoutField: 'sections',
|
|
210
|
-
// Container.sections allows only heading + ctaBanner, but here we shove
|
|
211
|
-
// a non-existent slug to ensure the nested breadcrumb is reported.
|
|
212
|
-
blocks: [
|
|
213
|
-
{
|
|
214
|
-
blockType: 'container',
|
|
215
|
-
sections: [
|
|
216
|
-
{
|
|
217
|
-
blockType: 'mystery',
|
|
218
|
-
foo: 1
|
|
219
|
-
}
|
|
220
|
-
]
|
|
221
|
-
}
|
|
222
|
-
],
|
|
223
|
-
operation: 'full'
|
|
224
|
-
}, req, {});
|
|
225
|
-
const text = result.content[0].text;
|
|
226
|
-
expect(text).toMatch(/sections\[0\]\.sections\[0\]/);
|
|
227
|
-
expect(text).toMatch(/mystery/);
|
|
228
|
-
expect(req.payload.updateGlobal).not.toHaveBeenCalled();
|
|
229
|
-
});
|
|
230
|
-
it('dotted layoutField preserves sibling fields in the parent group', async ()=>{
|
|
231
|
-
// Reg test for the sibling-wipe bug: a dotted layoutField like
|
|
232
|
-
// "sections.layout" used to write {sections: {layout: [...]}} which
|
|
233
|
-
// Payload merges at the top level only — silently wiping
|
|
234
|
-
// sections.copyright. writePath now reads the existing parent group
|
|
235
|
-
// off the fetched global and merges siblings in.
|
|
236
|
-
const NestedFooter = {
|
|
237
|
-
slug: 'nestedFooter',
|
|
238
|
-
versions: {
|
|
239
|
-
drafts: true
|
|
240
|
-
},
|
|
241
|
-
fields: [
|
|
242
|
-
{
|
|
243
|
-
name: 'sections',
|
|
244
|
-
type: 'group',
|
|
245
|
-
fields: [
|
|
246
|
-
{
|
|
247
|
-
name: 'copyright',
|
|
248
|
-
type: 'text'
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
name: 'layout',
|
|
252
|
-
type: 'blocks',
|
|
253
|
-
blocks: [
|
|
254
|
-
Heading,
|
|
255
|
-
CtaBanner
|
|
256
|
-
]
|
|
257
|
-
}
|
|
258
|
-
]
|
|
259
|
-
}
|
|
260
|
-
]
|
|
261
|
-
};
|
|
262
|
-
const nestedNesting = buildBlockNestingMap([], [
|
|
263
|
-
NestedFooter
|
|
264
|
-
], allBlocks);
|
|
265
|
-
const tool = createPatchGlobalLayoutTool(catalog, nestedNesting, new Set([
|
|
266
|
-
'nestedFooter'
|
|
267
|
-
]));
|
|
268
|
-
const req = buildReq();
|
|
269
|
-
req.payload.findGlobal.mockResolvedValue({
|
|
270
|
-
sections: {
|
|
271
|
-
copyright: 'do-not-wipe',
|
|
272
|
-
layout: [
|
|
273
|
-
{
|
|
274
|
-
blockType: 'heading',
|
|
275
|
-
text: 'a'
|
|
276
|
-
}
|
|
277
|
-
]
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
req.payload.updateGlobal.mockResolvedValue({});
|
|
281
|
-
await tool.handler({
|
|
282
|
-
slug: 'nestedFooter',
|
|
283
|
-
layoutField: 'sections.layout',
|
|
284
|
-
blocks: [
|
|
285
|
-
{
|
|
286
|
-
blockType: 'ctaBanner',
|
|
287
|
-
label: 'Buy'
|
|
288
|
-
}
|
|
289
|
-
],
|
|
290
|
-
operation: 'append'
|
|
291
|
-
}, req, {});
|
|
292
|
-
expect(req.payload.updateGlobal).toHaveBeenCalledWith(expect.objectContaining({
|
|
293
|
-
data: {
|
|
294
|
-
sections: {
|
|
295
|
-
copyright: 'do-not-wipe',
|
|
296
|
-
layout: [
|
|
297
|
-
{
|
|
298
|
-
blockType: 'heading',
|
|
299
|
-
text: 'a'
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
blockType: 'ctaBanner',
|
|
303
|
-
label: 'Buy'
|
|
304
|
-
}
|
|
305
|
-
]
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}));
|
|
309
|
-
});
|
|
310
|
-
it('multi-level dotted layoutField preserves siblings at every depth', async ()=>{
|
|
311
|
-
const DeepFooter = {
|
|
312
|
-
slug: 'deepFooter',
|
|
313
|
-
versions: {
|
|
314
|
-
drafts: true
|
|
315
|
-
},
|
|
316
|
-
fields: [
|
|
317
|
-
{
|
|
318
|
-
name: 'a',
|
|
319
|
-
type: 'group',
|
|
320
|
-
fields: [
|
|
321
|
-
{
|
|
322
|
-
name: 'keepA',
|
|
323
|
-
type: 'text'
|
|
324
|
-
},
|
|
325
|
-
{
|
|
326
|
-
name: 'b',
|
|
327
|
-
type: 'group',
|
|
328
|
-
fields: [
|
|
329
|
-
{
|
|
330
|
-
name: 'keepB',
|
|
331
|
-
type: 'text'
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
name: 'layout',
|
|
335
|
-
type: 'blocks',
|
|
336
|
-
blocks: [
|
|
337
|
-
Heading
|
|
338
|
-
]
|
|
339
|
-
}
|
|
340
|
-
]
|
|
341
|
-
}
|
|
342
|
-
]
|
|
343
|
-
}
|
|
344
|
-
]
|
|
345
|
-
};
|
|
346
|
-
const deepNesting = buildBlockNestingMap([], [
|
|
347
|
-
DeepFooter
|
|
348
|
-
], allBlocks);
|
|
349
|
-
const tool = createPatchGlobalLayoutTool(catalog, deepNesting, new Set([
|
|
350
|
-
'deepFooter'
|
|
351
|
-
]));
|
|
352
|
-
const req = buildReq();
|
|
353
|
-
req.payload.findGlobal.mockResolvedValue({
|
|
354
|
-
a: {
|
|
355
|
-
keepA: 'A-survives',
|
|
356
|
-
b: {
|
|
357
|
-
keepB: 'B-survives',
|
|
358
|
-
layout: []
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
req.payload.updateGlobal.mockResolvedValue({});
|
|
363
|
-
await tool.handler({
|
|
364
|
-
slug: 'deepFooter',
|
|
365
|
-
layoutField: 'a.b.layout',
|
|
366
|
-
blocks: [
|
|
367
|
-
{
|
|
368
|
-
blockType: 'heading',
|
|
369
|
-
text: 'new'
|
|
370
|
-
}
|
|
371
|
-
],
|
|
372
|
-
operation: 'full'
|
|
373
|
-
}, req, {});
|
|
374
|
-
expect(req.payload.updateGlobal).toHaveBeenCalledWith(expect.objectContaining({
|
|
375
|
-
data: {
|
|
376
|
-
a: {
|
|
377
|
-
keepA: 'A-survives',
|
|
378
|
-
b: {
|
|
379
|
-
keepB: 'B-survives',
|
|
380
|
-
layout: [
|
|
381
|
-
{
|
|
382
|
-
blockType: 'heading',
|
|
383
|
-
text: 'new'
|
|
384
|
-
}
|
|
385
|
-
]
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}));
|
|
390
|
-
});
|
|
391
|
-
it('expectedUpdatedAt mismatch rejects the patch without writing', async ()=>{
|
|
392
|
-
const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts);
|
|
393
|
-
const req = buildReq();
|
|
394
|
-
req.payload.findGlobal.mockResolvedValue({
|
|
395
|
-
sections: [],
|
|
396
|
-
updatedAt: '2026-01-02T00:00:00.000Z'
|
|
397
|
-
});
|
|
398
|
-
const result = await tool.handler({
|
|
399
|
-
slug: 'footer',
|
|
400
|
-
layoutField: 'sections',
|
|
401
|
-
blocks: [
|
|
402
|
-
{
|
|
403
|
-
blockType: 'heading',
|
|
404
|
-
text: 'a'
|
|
405
|
-
}
|
|
406
|
-
],
|
|
407
|
-
operation: 'append',
|
|
408
|
-
expectedUpdatedAt: '2026-01-01T00:00:00.000Z'
|
|
409
|
-
}, req, {});
|
|
410
|
-
expect(result.content[0].text).toMatch(/conflict/i);
|
|
411
|
-
expect(req.payload.updateGlobal).not.toHaveBeenCalled();
|
|
412
|
-
});
|
|
413
|
-
it('expectedUpdatedAt match proceeds with the patch', async ()=>{
|
|
414
|
-
const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts);
|
|
415
|
-
const req = buildReq();
|
|
416
|
-
req.payload.findGlobal.mockResolvedValue({
|
|
417
|
-
sections: [],
|
|
418
|
-
updatedAt: '2026-01-02T00:00:00.000Z'
|
|
419
|
-
});
|
|
420
|
-
req.payload.updateGlobal.mockResolvedValue({});
|
|
421
|
-
await tool.handler({
|
|
422
|
-
slug: 'footer',
|
|
423
|
-
layoutField: 'sections',
|
|
424
|
-
blocks: [
|
|
425
|
-
{
|
|
426
|
-
blockType: 'heading',
|
|
427
|
-
text: 'a'
|
|
428
|
-
}
|
|
429
|
-
],
|
|
430
|
-
operation: 'append',
|
|
431
|
-
expectedUpdatedAt: '2026-01-02T00:00:00.000Z'
|
|
432
|
-
}, req, {});
|
|
433
|
-
expect(req.payload.updateGlobal).toHaveBeenCalled();
|
|
434
|
-
});
|
|
435
|
-
it('locale arg is forwarded to findGlobal and updateGlobal', async ()=>{
|
|
436
|
-
const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts);
|
|
437
|
-
const req = buildReq();
|
|
438
|
-
req.payload.findGlobal.mockResolvedValue({
|
|
439
|
-
sections: []
|
|
440
|
-
});
|
|
441
|
-
req.payload.updateGlobal.mockResolvedValue({});
|
|
442
|
-
await tool.handler({
|
|
443
|
-
slug: 'footer',
|
|
444
|
-
layoutField: 'sections',
|
|
445
|
-
blocks: [
|
|
446
|
-
{
|
|
447
|
-
blockType: 'heading',
|
|
448
|
-
text: 'a'
|
|
449
|
-
}
|
|
450
|
-
],
|
|
451
|
-
operation: 'append',
|
|
452
|
-
locale: 'fr'
|
|
453
|
-
}, req, {});
|
|
454
|
-
expect(req.payload.findGlobal).toHaveBeenCalledWith(expect.objectContaining({
|
|
455
|
-
locale: 'fr'
|
|
456
|
-
}));
|
|
457
|
-
expect(req.payload.updateGlobal).toHaveBeenCalledWith(expect.objectContaining({
|
|
458
|
-
locale: 'fr'
|
|
459
|
-
}));
|
|
460
|
-
});
|
|
461
|
-
it('returns an error when layoutField is not a blocks field on the global', async ()=>{
|
|
462
|
-
const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts);
|
|
463
|
-
const req = buildReq();
|
|
464
|
-
const result = await tool.handler({
|
|
465
|
-
slug: 'footer',
|
|
466
|
-
layoutField: 'definitely-not-a-field',
|
|
467
|
-
blocks: [],
|
|
468
|
-
operation: 'full'
|
|
469
|
-
}, req, {});
|
|
470
|
-
expect(result.content[0].text).toMatch(/not a blocks-typed field/i);
|
|
471
|
-
});
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
//# sourceMappingURL=patch-global-layout.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/__tests__/patch-global-layout.test.ts"],"sourcesContent":["import { describe, it, expect, vi } from 'vitest'\r\nimport type { Block, GlobalConfig } from 'payload'\r\nimport { introspectBlocks, buildBlockNestingMap } from '../introspection'\r\nimport { createPatchGlobalLayoutTool } from '../tools/patch-global-layout'\r\n\r\nconst Heading: Block = { slug: 'heading', fields: [{ name: 'text', type: 'text' }] }\r\nconst CtaBanner: Block = { slug: 'ctaBanner', fields: [{ name: 'label', type: 'text' }] }\r\nconst Container: Block = {\r\n slug: 'container',\r\n fields: [\r\n {\r\n name: 'sections',\r\n type: 'blocks',\r\n blocks: [Heading, CtaBanner],\r\n },\r\n ],\r\n}\r\n\r\nconst FooterGlobal: GlobalConfig = {\r\n slug: 'footer',\r\n versions: { drafts: true },\r\n fields: [\r\n {\r\n name: 'sections',\r\n type: 'blocks',\r\n blocks: [Heading, CtaBanner, Container],\r\n },\r\n ],\r\n}\r\n\r\nconst PlainSettings: GlobalConfig = {\r\n slug: 'plain',\r\n fields: [{ name: 'siteName', type: 'text' }],\r\n}\r\n\r\nfunction buildReq() {\r\n return {\r\n payload: {\r\n findGlobal: vi.fn(),\r\n updateGlobal: vi.fn(),\r\n logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },\r\n },\r\n context: {},\r\n user: null,\r\n }\r\n}\r\n\r\nconst allBlocks = [Heading, CtaBanner, Container]\r\nconst catalog = introspectBlocks(allBlocks)\r\nconst nesting = buildBlockNestingMap([], [FooterGlobal], allBlocks)\r\nconst drafts = new Set(['footer'])\r\n\r\ndescribe('patchGlobalLayout', () => {\r\n it('returns null when no global has a blocks field (factory short-circuit)', () => {\r\n const emptyNesting = buildBlockNestingMap([], [PlainSettings], allBlocks)\r\n const tool = createPatchGlobalLayoutTool(catalog, emptyNesting, new Set())\r\n expect(tool).toBeNull()\r\n })\r\n\r\n it('registers when at least one global has a blocks field', () => {\r\n const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts)\r\n expect(tool).not.toBeNull()\r\n expect(tool!.name).toBe('patchGlobalLayout')\r\n })\r\n\r\n it('append: writes existing + new blocks back via updateGlobal', async () => {\r\n const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts)!\r\n const req = buildReq()\r\n req.payload.findGlobal.mockResolvedValue({\r\n sections: [{ blockType: 'heading', text: 'a' }],\r\n })\r\n req.payload.updateGlobal.mockResolvedValue({})\r\n\r\n await tool.handler(\r\n {\r\n slug: 'footer',\r\n layoutField: 'sections',\r\n blocks: [{ blockType: 'ctaBanner', label: 'Buy' }],\r\n operation: 'append',\r\n },\r\n req as never,\r\n {},\r\n )\r\n\r\n expect(req.payload.updateGlobal).toHaveBeenCalledWith(\r\n expect.objectContaining({\r\n slug: 'footer',\r\n draft: true,\r\n data: {\r\n sections: [\r\n { blockType: 'heading', text: 'a' },\r\n { blockType: 'ctaBanner', label: 'Buy' },\r\n ],\r\n },\r\n }),\r\n )\r\n })\r\n\r\n it('replaceAt index 0 swaps the first block', async () => {\r\n const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts)!\r\n const req = buildReq()\r\n req.payload.findGlobal.mockResolvedValue({\r\n sections: [\r\n { blockType: 'heading', text: 'a' },\r\n { blockType: 'ctaBanner', label: 'old' },\r\n ],\r\n })\r\n req.payload.updateGlobal.mockResolvedValue({})\r\n\r\n await tool.handler(\r\n {\r\n slug: 'footer',\r\n layoutField: 'sections',\r\n blocks: [{ blockType: 'heading', text: 'new-first' }],\r\n operation: 'replaceAt',\r\n insertIndex: 0,\r\n },\r\n req as never,\r\n {},\r\n )\r\n\r\n expect(req.payload.updateGlobal).toHaveBeenCalledWith(\r\n expect.objectContaining({\r\n data: {\r\n sections: [\r\n { blockType: 'heading', text: 'new-first' },\r\n { blockType: 'ctaBanner', label: 'old' },\r\n ],\r\n },\r\n }),\r\n )\r\n })\r\n\r\n it('rejects blocks whose slug is not in the field allow-list', async () => {\r\n const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts)!\r\n const req = buildReq()\r\n const result = await tool.handler(\r\n {\r\n slug: 'footer',\r\n layoutField: 'sections',\r\n // ImageBlock is not in the FooterGlobal allow list.\r\n blocks: [{ blockType: 'image', src: 'x' }],\r\n operation: 'full',\r\n },\r\n req as never,\r\n {},\r\n )\r\n const text = result.content[0]!.text\r\n expect(text).toMatch(/unknown blockType/i)\r\n expect(text).toContain('image')\r\n expect(req.payload.updateGlobal).not.toHaveBeenCalled()\r\n })\r\n\r\n it('validates nested blocks fields with breadcrumb path on failure', async () => {\r\n const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts)!\r\n const req = buildReq()\r\n const result = await tool.handler(\r\n {\r\n slug: 'footer',\r\n layoutField: 'sections',\r\n // Container.sections allows only heading + ctaBanner, but here we shove\r\n // a non-existent slug to ensure the nested breadcrumb is reported.\r\n blocks: [\r\n {\r\n blockType: 'container',\r\n sections: [{ blockType: 'mystery', foo: 1 }],\r\n },\r\n ],\r\n operation: 'full',\r\n },\r\n req as never,\r\n {},\r\n )\r\n const text = result.content[0]!.text\r\n expect(text).toMatch(/sections\\[0\\]\\.sections\\[0\\]/)\r\n expect(text).toMatch(/mystery/)\r\n expect(req.payload.updateGlobal).not.toHaveBeenCalled()\r\n })\r\n\r\n it('dotted layoutField preserves sibling fields in the parent group', async () => {\r\n // Reg test for the sibling-wipe bug: a dotted layoutField like\r\n // \"sections.layout\" used to write {sections: {layout: [...]}} which\r\n // Payload merges at the top level only — silently wiping\r\n // sections.copyright. writePath now reads the existing parent group\r\n // off the fetched global and merges siblings in.\r\n const NestedFooter: GlobalConfig = {\r\n slug: 'nestedFooter',\r\n versions: { drafts: true },\r\n fields: [\r\n {\r\n name: 'sections',\r\n type: 'group',\r\n fields: [\r\n { name: 'copyright', type: 'text' },\r\n { name: 'layout', type: 'blocks', blocks: [Heading, CtaBanner] },\r\n ],\r\n },\r\n ],\r\n }\r\n const nestedNesting = buildBlockNestingMap([], [NestedFooter], allBlocks)\r\n const tool = createPatchGlobalLayoutTool(catalog, nestedNesting, new Set(['nestedFooter']))!\r\n const req = buildReq()\r\n req.payload.findGlobal.mockResolvedValue({\r\n sections: {\r\n copyright: 'do-not-wipe',\r\n layout: [{ blockType: 'heading', text: 'a' }],\r\n },\r\n })\r\n req.payload.updateGlobal.mockResolvedValue({})\r\n\r\n await tool.handler(\r\n {\r\n slug: 'nestedFooter',\r\n layoutField: 'sections.layout',\r\n blocks: [{ blockType: 'ctaBanner', label: 'Buy' }],\r\n operation: 'append',\r\n },\r\n req as never,\r\n {},\r\n )\r\n\r\n expect(req.payload.updateGlobal).toHaveBeenCalledWith(\r\n expect.objectContaining({\r\n data: {\r\n sections: {\r\n copyright: 'do-not-wipe',\r\n layout: [\r\n { blockType: 'heading', text: 'a' },\r\n { blockType: 'ctaBanner', label: 'Buy' },\r\n ],\r\n },\r\n },\r\n }),\r\n )\r\n })\r\n\r\n it('multi-level dotted layoutField preserves siblings at every depth', async () => {\r\n const DeepFooter: GlobalConfig = {\r\n slug: 'deepFooter',\r\n versions: { drafts: true },\r\n fields: [\r\n {\r\n name: 'a',\r\n type: 'group',\r\n fields: [\r\n { name: 'keepA', type: 'text' },\r\n {\r\n name: 'b',\r\n type: 'group',\r\n fields: [\r\n { name: 'keepB', type: 'text' },\r\n { name: 'layout', type: 'blocks', blocks: [Heading] },\r\n ],\r\n },\r\n ],\r\n },\r\n ],\r\n }\r\n const deepNesting = buildBlockNestingMap([], [DeepFooter], allBlocks)\r\n const tool = createPatchGlobalLayoutTool(catalog, deepNesting, new Set(['deepFooter']))!\r\n const req = buildReq()\r\n req.payload.findGlobal.mockResolvedValue({\r\n a: {\r\n keepA: 'A-survives',\r\n b: {\r\n keepB: 'B-survives',\r\n layout: [],\r\n },\r\n },\r\n })\r\n req.payload.updateGlobal.mockResolvedValue({})\r\n\r\n await tool.handler(\r\n {\r\n slug: 'deepFooter',\r\n layoutField: 'a.b.layout',\r\n blocks: [{ blockType: 'heading', text: 'new' }],\r\n operation: 'full',\r\n },\r\n req as never,\r\n {},\r\n )\r\n\r\n expect(req.payload.updateGlobal).toHaveBeenCalledWith(\r\n expect.objectContaining({\r\n data: {\r\n a: {\r\n keepA: 'A-survives',\r\n b: {\r\n keepB: 'B-survives',\r\n layout: [{ blockType: 'heading', text: 'new' }],\r\n },\r\n },\r\n },\r\n }),\r\n )\r\n })\r\n\r\n it('expectedUpdatedAt mismatch rejects the patch without writing', async () => {\r\n const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts)!\r\n const req = buildReq()\r\n req.payload.findGlobal.mockResolvedValue({\r\n sections: [],\r\n updatedAt: '2026-01-02T00:00:00.000Z',\r\n })\r\n\r\n const result = await tool.handler(\r\n {\r\n slug: 'footer',\r\n layoutField: 'sections',\r\n blocks: [{ blockType: 'heading', text: 'a' }],\r\n operation: 'append',\r\n expectedUpdatedAt: '2026-01-01T00:00:00.000Z',\r\n },\r\n req as never,\r\n {},\r\n )\r\n\r\n expect(result.content[0]!.text).toMatch(/conflict/i)\r\n expect(req.payload.updateGlobal).not.toHaveBeenCalled()\r\n })\r\n\r\n it('expectedUpdatedAt match proceeds with the patch', async () => {\r\n const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts)!\r\n const req = buildReq()\r\n req.payload.findGlobal.mockResolvedValue({\r\n sections: [],\r\n updatedAt: '2026-01-02T00:00:00.000Z',\r\n })\r\n req.payload.updateGlobal.mockResolvedValue({})\r\n\r\n await tool.handler(\r\n {\r\n slug: 'footer',\r\n layoutField: 'sections',\r\n blocks: [{ blockType: 'heading', text: 'a' }],\r\n operation: 'append',\r\n expectedUpdatedAt: '2026-01-02T00:00:00.000Z',\r\n },\r\n req as never,\r\n {},\r\n )\r\n\r\n expect(req.payload.updateGlobal).toHaveBeenCalled()\r\n })\r\n\r\n it('locale arg is forwarded to findGlobal and updateGlobal', async () => {\r\n const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts)!\r\n const req = buildReq()\r\n req.payload.findGlobal.mockResolvedValue({ sections: [] })\r\n req.payload.updateGlobal.mockResolvedValue({})\r\n\r\n await tool.handler(\r\n {\r\n slug: 'footer',\r\n layoutField: 'sections',\r\n blocks: [{ blockType: 'heading', text: 'a' }],\r\n operation: 'append',\r\n locale: 'fr',\r\n },\r\n req as never,\r\n {},\r\n )\r\n\r\n expect(req.payload.findGlobal).toHaveBeenCalledWith(expect.objectContaining({ locale: 'fr' }))\r\n expect(req.payload.updateGlobal).toHaveBeenCalledWith(expect.objectContaining({ locale: 'fr' }))\r\n })\r\n\r\n it('returns an error when layoutField is not a blocks field on the global', async () => {\r\n const tool = createPatchGlobalLayoutTool(catalog, nesting, drafts)!\r\n const req = buildReq()\r\n const result = await tool.handler(\r\n {\r\n slug: 'footer',\r\n layoutField: 'definitely-not-a-field',\r\n blocks: [],\r\n operation: 'full',\r\n },\r\n req as never,\r\n {},\r\n )\r\n expect(result.content[0]!.text).toMatch(/not a blocks-typed field/i)\r\n })\r\n})\r\n"],"names":["describe","it","expect","vi","introspectBlocks","buildBlockNestingMap","createPatchGlobalLayoutTool","Heading","slug","fields","name","type","CtaBanner","Container","blocks","FooterGlobal","versions","drafts","PlainSettings","buildReq","payload","findGlobal","fn","updateGlobal","logger","info","warn","error","context","user","allBlocks","catalog","nesting","Set","emptyNesting","tool","toBeNull","not","toBe","req","mockResolvedValue","sections","blockType","text","handler","layoutField","label","operation","toHaveBeenCalledWith","objectContaining","draft","data","insertIndex","result","src","content","toMatch","toContain","toHaveBeenCalled","foo","NestedFooter","nestedNesting","copyright","layout","DeepFooter","deepNesting","a","keepA","b","keepB","updatedAt","expectedUpdatedAt","locale"],"mappings":"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"}
|