import { type NestableContent, GroupContent, GroupItemContentType, isGroupContent, } from "@prismicio/types-internal/lib/content" import { SharedSliceContent, SharedSliceContentType, } from "@prismicio/types-internal/lib/content" import { type NestableWidget, type SharedSlice, type Variation, GroupFieldType, } from "@prismicio/types-internal/lib/customtypes" import { DiffOperation, GroupWidgetDiff, NestableWidgetDiff, SliceDiff, VariationDiff, } from "@prismicio/types-internal/lib/customtypes/diff" import { v4 as uuid } from "uuid" import type { SliceMock } from "../Mock" import type { MockConfig } from "../MockConfig" import type { GroupMockConfig, NestableWidgetMockConfig } from "." import { GroupMock, NestableWidgetMock, randomGroupBlock } from "." export interface VariationMockConfig { nbItemsBlocks?: number primaryFields?: Partial> itemFields?: Partial> } export interface SharedSliceMockConfig extends MockConfig< SharedSlice["type"], Omit > { variation?: string variations?: { [variationId: string]: VariationMockConfig } } type SlicePrimaryWidgetMockConfig = GroupMockConfig | NestableWidgetMockConfig function randomItemsBlock( fieldsDefs: Array<[string, NestableWidget]>, mockConfigs?: Partial>, ): Array<[string, NestableContent]> { return fieldsDefs.map(([key, field]: [string, NestableWidget]) => { const mockConfig = mockConfigs && mockConfigs[key] return [key, NestableWidgetMock.generate(field, mockConfig)] }) } function random( variationDef: Variation, config?: VariationMockConfig, ): SharedSliceContent { const nbItemsBlocks = config?.nbItemsBlocks || 1 return { __TYPE__: SharedSliceContentType, variation: variationDef.id, primary: (() => { if (!variationDef.primary) return {} return randomGroupBlock( Object.entries(variationDef.primary), config?.primaryFields, ).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) })(), items: (() => { if (!variationDef.items) return [] return Array(nbItemsBlocks) .fill(null) .map(() => { return randomItemsBlock( Object.entries(variationDef.items || {}), config?.itemFields, ) }) .map((b) => ({ __TYPE__: GroupItemContentType, key: uuid(), value: b, })) })(), } } export const SharedSliceMock: SliceMock< SharedSlice, SharedSliceContent, SharedSliceMockConfig > = { generate( def: SharedSlice, config?: SharedSliceMockConfig, ): SharedSliceContent { if (config?.value) { return { __TYPE__: SharedSliceContentType, ...config.value } } const variationDef = def.variations.find((v) => v.id === config?.variation) ?? def.variations[0] if (!variationDef) { throw new Error( `Something happened during Shared slice generation. No variations were configured for slice ${def.id}.`, ) } const variationMockConfig = config?.variations && config.variations[variationDef.id] return generateVariation(variationDef, variationMockConfig) }, patch( diff: SliceDiff, content?: SharedSliceContent, config?: SharedSliceMockConfig, ): | { ok: true; result: SharedSliceContent | undefined } | { ok: false; error: Error } { switch (diff.op) { case DiffOperation.Removed: if (!config?.variation || config.variation === content?.variation) return { ok: true, result: undefined, } return { ok: false, error: new Error( `Content doesn't match the config for variation ${config.variation}`, ), } case DiffOperation.Added: if (content) return { ok: false, error: new Error( `Content already exists for slice ${diff.value.id}.`, ), } if ( !config?.variation || diff.value.variations.find((v) => v.id === config.variation) ) { return { ok: true, result: this.generate(diff.value, config), } } return { ok: false, error: new Error( `Configured variation ${config.variation} not found in slice ${diff.value.id} definition.`, ), } case DiffOperation.Updated: { // we take the content as reference or fallback to the config if no content yet. // This case can happen if a variation is newly added const variationResult: | { ok: true; result: string } | { ok: false; error: Error } = (() => { if (content?.variation) { if (config?.variation && content.variation !== config?.variation) return { ok: false, error: new Error( `Variation ${content.variation} from the content doesn't match the config ${config?.variation}.`, ), } return { ok: true, result: content.variation, } } else { return config?.variation ? { ok: true, result: config.variation, } : { ok: false, error: new Error( "No variation provided. You must either provide some content or a config.", ), } } })() if (!variationResult.ok) return variationResult const variationId = variationResult.result const variationDiff = diff.value.variations?.[variationId] if (!variationDiff) // the content provided can't be patched since there is no corresponding diff return { ok: false, error: new Error( `The model of the content with variation ${variationId} has not changed.`, ), } return patchVariation( variationId, variationDiff, content, variationDiff.op !== DiffOperation.Removed ? config?.variations?.[variationId] : undefined, ) } } }, } function generateVariation( def: Variation, config?: VariationMockConfig, ): SharedSliceContent { return random(def, config) } function patchVariation( variationId: string, diff: VariationDiff, content?: SharedSliceContent, config?: VariationMockConfig, ): | { ok: true; result: SharedSliceContent | undefined } | { ok: false; error: Error } { switch (diff.op) { case DiffOperation.Removed: return { ok: true, result: undefined, } case DiffOperation.Added: if (!content) return { ok: true, result: generateVariation(diff.value, config), // we're not supposed to have content already } return { ok: false, error: new Error( `Content already exists for variation ${content.variation}.`, ), } case DiffOperation.Updated: { const primary = diff.value.primary && content?.primary ? patchWidgets( diff.value.primary, Object.entries(content?.primary), config?.primaryFields, ) : content?.primary || {} const items: SharedSliceContent["items"] = (() => { if (diff.value.items && content?.items) { const diffItems = diff.value.items return content.items.map((block) => { return { __TYPE__: GroupItemContentType, ...(block.key ? { key: block.key } : {}), value: Object.entries( patchWidgets(diffItems, block.value, config?.itemFields), ), } }) } else return content?.items || [] })() return { ok: true, result: { __TYPE__: "SharedSliceContent", variation: variationId, primary, items, }, } } } } function patchWidgets( diff: Record< string, TContent extends GroupContent ? GroupWidgetDiff : NestableWidgetDiff >, content: Array<[string, TContent]>, config?: Partial< Record< string, TContent extends GroupContent ? GroupMockConfig : NestableWidgetMockConfig > >, ): Record { const patched = Object.entries(diff).reduce< Record >((acc, [widgetId, widgetChange]) => { const widgetContent = content.find(([id]) => id === widgetId)?.[1] const widgetConfig = config?.[widgetId] // this will be undefined if a widget is in the diff with a remove OP let patched = undefined if ( // If content is not group content... !isGroupContent(widgetContent) && // ...and widget config is not of type Group... widgetConfig?.type !== GroupFieldType && // ...and the widget change value is not of type Group widgetChange.op !== DiffOperation.Removed && widgetChange.value.type !== GroupFieldType ) { patched = NestableWidgetMock.patch( widgetChange, widgetContent, widgetConfig, ) } else if ( // If content is group content... (!widgetContent || isGroupContent(widgetContent)) && // ...and widget config is of type Group. (!widgetConfig || widgetConfig.type === GroupFieldType) ) { patched = GroupMock.patch(widgetChange, widgetContent, widgetConfig) } return { ...acc, ...(patched ? { [widgetId]: patched } : {}), } }, {}) const updatedContent = content.reduce((acc, [widgetId, widgetContent]) => { // patched can return undefined if there is no diff or if it was removed. // we need to verify the widget has an actual diff to know if we return undefined or the previous content const updatedWidgetContent = diff[widgetId] ? patched[widgetId] : widgetContent if (!updatedWidgetContent) return acc return { ...acc, [widgetId]: updatedWidgetContent, } }, {}) return { ...updatedContent, ...patched, } }