fix(live-preview): compounds merge results (#4306)

This commit is contained in:
Jacob Fletcher
2023-11-29 10:54:14 -05:00
committed by GitHub
parent 3514bfbdae
commit 381c158b03
4 changed files with 233 additions and 108 deletions

View File

@@ -6,6 +6,10 @@ import { mergeData } from '.'
// Send this cached value to `mergeData`, instead of `eventData.fieldSchemaJSON` directly
let payloadLivePreviewFieldSchema = undefined // TODO: type this from `fieldSchemaToJSON` return type
// Each time the data is merged, cache the result as a `previousData` variable
// This will ensure changes compound overtop of each other
let payloadLivePreviewPreviousData = undefined
export const handleMessage = async <T>(args: {
apiRoute?: string
depth?: number
@@ -37,10 +41,12 @@ export const handleMessage = async <T>(args: {
depth,
fieldSchema: payloadLivePreviewFieldSchema,
incomingData: eventData.data,
initialData,
initialData: payloadLivePreviewPreviousData || initialData,
serverURL,
})
payloadLivePreviewPreviousData = mergedData
return mergedData
}
}

View File

@@ -11,50 +11,68 @@ export const traverseRichText = ({
apiRoute: string
depth: number
incomingData: any
populationPromises: Promise<void>[]
populationPromises: Promise<any>[]
result: any
serverURL: string
}): any => {
if (Array.isArray(incomingData)) {
result = incomingData.map((incomingRow) =>
traverseRichText({
if (!result) {
result = []
}
result = incomingData.map((item, index) => {
if (!result[index]) {
result[index] = item
}
return traverseRichText({
apiRoute,
depth,
incomingData: incomingRow,
incomingData: item,
populationPromises,
result,
result: result[index],
serverURL,
}),
)
} else if (typeof incomingData === 'object' && incomingData !== null) {
result = incomingData
})
})
} else if (incomingData && typeof incomingData === 'object') {
if (!result) {
result = {}
}
if ('relationTo' in incomingData && 'value' in incomingData && incomingData.value) {
Object.keys(incomingData).forEach((key) => {
if (!result[key]) {
result[key] = incomingData[key]
}
const isRelationship = key === 'value' && 'relationTo' in incomingData
if (isRelationship) {
const needsPopulation = !result.value || typeof result.value !== 'object'
if (needsPopulation) {
populationPromises.push(
promise({
id: typeof incomingData.value === 'object' ? incomingData.value.id : incomingData.value,
id: incomingData[key],
accessor: 'value',
apiRoute,
collection: String(incomingData.relationTo),
collection: incomingData.relationTo,
depth,
ref: result,
serverURL,
}),
)
}
} else {
result = {}
Object.keys(incomingData).forEach((key) => {
result[key] = traverseRichText({
apiRoute,
depth,
incomingData: incomingData[key],
populationPromises,
result,
result: result[key],
serverURL,
})
})
}
})
} else {
result = incomingData
}

View File

@@ -187,8 +187,7 @@ describe('Collections - Live Preview', () => {
expect(mergedDataWithoutUpload.hero.media).toBeFalsy()
})
it('— relationships - populates all types', async () => {
it('— relationships - populates monomorphic has one relationships', async () => {
const initialData: Partial<Page> = {
title: 'Test Page',
}
@@ -199,8 +198,71 @@ describe('Collections - Live Preview', () => {
incomingData: {
...initialData,
relationshipMonoHasOne: testPost.id,
},
initialData,
serverURL,
returnNumberOfRequests: true,
})
expect(merge1._numberOfRequests).toEqual(1)
expect(merge1.relationshipMonoHasOne).toMatchObject(testPost)
})
it('— relationships - populates monomorphic has many relationships', async () => {
const initialData: Partial<Page> = {
title: 'Test Page',
}
const merge1 = await mergeData({
depth: 1,
fieldSchema: schemaJSON,
incomingData: {
...initialData,
relationshipMonoHasMany: [testPost.id],
},
initialData,
serverURL,
returnNumberOfRequests: true,
})
expect(merge1._numberOfRequests).toEqual(1)
expect(merge1.relationshipMonoHasMany).toMatchObject([testPost])
})
it('— relationships - populates polymorphic has one relationships', async () => {
const initialData: Partial<Page> = {
title: 'Test Page',
}
const merge1 = await mergeData({
depth: 1,
fieldSchema: schemaJSON,
incomingData: {
...initialData,
relationshipPolyHasOne: { value: testPost.id, relationTo: postsSlug },
},
initialData,
serverURL,
returnNumberOfRequests: true,
})
expect(merge1._numberOfRequests).toEqual(1)
expect(merge1.relationshipPolyHasOne).toMatchObject({
value: testPost,
relationTo: postsSlug,
})
})
it('— relationships - populates polymorphic has many relationships', async () => {
const initialData: Partial<Page> = {
title: 'Test Page',
}
const merge1 = await mergeData({
depth: 1,
fieldSchema: schemaJSON,
incomingData: {
...initialData,
relationshipPolyHasMany: [{ value: testPost.id, relationTo: postsSlug }],
},
initialData,
@@ -208,19 +270,12 @@ describe('Collections - Live Preview', () => {
returnNumberOfRequests: true,
})
expect(merge1._numberOfRequests).toEqual(4)
expect(merge1.relationshipMonoHasOne).toMatchObject(testPost)
expect(merge1.relationshipMonoHasMany).toMatchObject([testPost])
expect(merge1.relationshipPolyHasOne).toMatchObject({
value: testPost,
relationTo: postsSlug,
})
expect(merge1._numberOfRequests).toEqual(1)
expect(merge1.relationshipPolyHasMany).toMatchObject([
{ value: testPost, relationTo: postsSlug },
])
})
it('— relationships - can clear relationships', async () => {
const initialData: Partial<Page> = {
title: 'Test Page',
@@ -421,6 +476,114 @@ describe('Collections - Live Preview', () => {
expect(merge2.relationshipInRichText[0].type).toEqual('paragraph')
})
it('— relationships - does not re-populate existing rich text relationships', async () => {
const initialData: Partial<Page> = {
title: 'Test Page',
relationshipInRichText: [
{
type: 'paragraph',
text: 'Paragraph 1',
},
{
type: 'reference',
reference: {
relationTo: 'posts',
value: testPost,
},
},
],
}
// Add a relationship
const merge1 = await mergeData({
depth: 1,
fieldSchema: schemaJSON,
incomingData: {
...initialData,
relationshipInRichText: [
{
type: 'paragraph',
text: 'Paragraph 1',
},
{
type: 'reference',
reference: {
relationTo: 'posts',
value: testPost.id,
},
},
],
},
initialData,
serverURL,
returnNumberOfRequests: true,
})
expect(merge1._numberOfRequests).toEqual(0)
expect(merge1.relationshipInRichText).toHaveLength(2)
expect(merge1.relationshipInRichText[1].reference.value).toMatchObject(testPost)
})
it('— relationships - populates within blocks', async () => {
const block1 = (shallow?: boolean): Extract<Page['layout'][0], { blockType: 'cta' }> => ({
blockType: 'cta',
id: '123',
links: [
{
link: {
label: 'Link 1',
type: 'reference',
reference: {
relationTo: 'posts',
value: shallow ? testPost?.id : testPost,
},
},
},
],
})
const block2: Extract<Page['layout'][0], { blockType: 'content' }> = {
blockType: 'content',
id: '456',
columns: [
{
id: '789',
richText: [
{
type: 'paragraph',
text: 'Column 1',
},
],
},
],
}
const initialData: Partial<Page> = {
title: 'Test Page',
layout: [block1(), block2],
}
// Add a new block before the populated one
// Then check to see that the relationship is still populated
const merge2 = await mergeData({
depth: 1,
fieldSchema: schemaJSON,
incomingData: {
...initialData,
layout: [block2, block1(true)],
},
initialData,
serverURL,
returnNumberOfRequests: true,
})
// Check that the relationship on the first has been removed
// And that the relationship on the second has been populated
expect(merge2.layout[0].links).toBeUndefined()
expect(merge2.layout[1].links[0].link.reference.value).toMatchObject(testPost)
expect(merge2._numberOfRequests).toEqual(1)
})
it('— rich text - merges rich text', async () => {
const initialData: Partial<Page> = {
title: 'Test Page',
@@ -485,66 +648,6 @@ describe('Collections - Live Preview', () => {
expect(merge2.hero.richText[0].children[0].text).toEqual('Paragraph 1 (Updated)')
})
it('— relationships - populates within blocks', async () => {
const block1 = (shallow?: boolean): Extract<Page['layout'][0], { blockType: 'cta' }> => ({
blockType: 'cta',
id: '123',
links: [
{
link: {
label: 'Link 1',
type: 'reference',
reference: {
relationTo: 'posts',
value: shallow ? testPost?.id : testPost,
},
},
},
],
})
const block2: Extract<Page['layout'][0], { blockType: 'content' }> = {
blockType: 'content',
id: '456',
columns: [
{
id: '789',
richText: [
{
type: 'paragraph',
text: 'Column 1',
},
],
},
],
}
const initialData: Partial<Page> = {
title: 'Test Page',
layout: [block1(), block2],
}
// Add a new block before the populated one
// Then check to see that the relationship is still populated
const merge2 = await mergeData({
depth: 1,
fieldSchema: schemaJSON,
incomingData: {
...initialData,
layout: [block2, block1(true)],
},
initialData,
serverURL,
returnNumberOfRequests: true,
})
// Check that the relationship on the first has been removed
// And that the relationship on the second has been populated
expect(merge2.layout[0].links).toBeUndefined()
expect(merge2.layout[1].links[0].link.reference.value).toMatchObject(testPost)
expect(merge2._numberOfRequests).toEqual(1)
})
it('— blocks - adds, reorders, and removes blocks', async () => {
const block1ID = '123'
const block2ID = '456'

View File

@@ -2,6 +2,7 @@ import React, { Fragment } from 'react'
import escapeHTML from 'escape-html'
import Link from 'next/link'
import { Text } from 'slate'
import { CMSLink } from '../Link'
// eslint-disable-next-line no-use-before-define
type Children = Leaf[]
@@ -85,18 +86,15 @@ const serialize = (children?: Children): React.ReactNode[] =>
)
case 'link':
return (
<Link
href={escapeHTML(node.url)}
<CMSLink
key={i}
{...(node?.newTab
? {
target: '_blank',
rel: 'noopener noreferrer',
}
: {})}
type={node.linkType === 'internal' ? 'reference' : 'custom'}
url={node.url}
reference={node.doc as any}
newTab={Boolean(node?.newTab)}
>
{serialize(node?.children)}
</Link>
</CMSLink>
)
default: