From 708f67e439d807fa963e8253f6265577ba6c3116 Mon Sep 17 00:00:00 2001 From: Xavier Rutayisire Date: Mon, 16 Oct 2023 18:19:44 +0200 Subject: [PATCH] fix(page type): Convert to page type should create slice zone for main tab if necessary --- .../src/domain/CustomTypeModel.ts | 118 +++++++++ .../domain/__tests__/CustomTypeModel.test.ts | 247 ++++++++++++++++++ .../actions/convertCustomToPageType.ts | 9 +- 3 files changed, 369 insertions(+), 5 deletions(-) create mode 100644 packages/slice-machine/src/domain/CustomTypeModel.ts create mode 100644 packages/slice-machine/src/domain/__tests__/CustomTypeModel.test.ts diff --git a/packages/slice-machine/src/domain/CustomTypeModel.ts b/packages/slice-machine/src/domain/CustomTypeModel.ts new file mode 100644 index 0000000000..a26aa30615 --- /dev/null +++ b/packages/slice-machine/src/domain/CustomTypeModel.ts @@ -0,0 +1,118 @@ +import { + CustomType, + DynamicSection, + DynamicSlicesConfig, +} from "@prismicio/types-internal/lib/customtypes"; + +export function getSectionEntries( + customType: CustomType +): [string, DynamicSection][] { + return Object.entries(customType.json); +} + +export function getMainSectionEntry( + customType: CustomType +): [string, DynamicSection] | undefined { + // Currently we cannot rely on the name of the main section + // since it's possible to rename it + const sections = getSectionEntries(customType); + return sections[0]; +} + +export function getSection( + customType: CustomType, + sectionId: string +): DynamicSection | undefined { + return customType.json[sectionId]; +} + +export function getSectionSliceZoneConfig( + customType: CustomType, + sectionId: string +): DynamicSlicesConfig | undefined { + const section = getSection(customType, sectionId); + + if (section === undefined) { + return undefined; + } + + // In Slice Machine we currently only support one slice zone per section + // so we retrieve the first one + const maybeSliceZone = Object.values(section).find( + (value) => value.type === "Slices" + ); + + return maybeSliceZone?.config ?? undefined; +} + +export function findAvailableSectionSlicesKey( + customType: CustomType, + sectionId: string +): string { + const sectionsEntries = getSectionEntries(customType); + const sectionIndex = sectionsEntries.findIndex(([key]) => key === sectionId); + + const existingKeys = sectionsEntries.flatMap(([_, section]) => + Object.keys(section).filter((key) => section[key].type === "Slices") + ); + + let i = sectionIndex; + let proposedKey; + do { + proposedKey = `slices${i !== 0 ? i.toString() : ""}`; + i++; + } while (existingKeys.includes(proposedKey)); + + return proposedKey; +} + +export function createSectionSliceZone( + customType: CustomType, + sectionId: string +): CustomType { + const maybeSectionSliceZoneConfig = getSectionSliceZoneConfig( + customType, + sectionId + ); + + // If the section already has a slice zone, return the custom type as is + if (maybeSectionSliceZoneConfig) { + return customType; + } + + // Get the next available key for the slice zone + const availableSectionSlicesKey = findAvailableSectionSlicesKey( + customType, + sectionId + ); + + return { + ...customType, + json: { + ...customType.json, + [sectionId]: { + ...customType.json[sectionId], + [availableSectionSlicesKey]: { + type: "Slices", + fieldset: "Slice Zone", + }, + }, + }, + }; +} + +export function convertToPageType(customType: CustomType): CustomType { + let newCustomType: CustomType = { + ...customType, + format: "page", + }; + + // Create the slice zone for the main section if it doesn't exist + const mainSectionEntry = getMainSectionEntry(customType); + if (mainSectionEntry) { + const [mainSectionKey] = mainSectionEntry; + newCustomType = createSectionSliceZone(newCustomType, mainSectionKey); + } + + return newCustomType; +} diff --git a/packages/slice-machine/src/domain/__tests__/CustomTypeModel.test.ts b/packages/slice-machine/src/domain/__tests__/CustomTypeModel.test.ts new file mode 100644 index 0000000000..6f7a1c7970 --- /dev/null +++ b/packages/slice-machine/src/domain/__tests__/CustomTypeModel.test.ts @@ -0,0 +1,247 @@ +import { describe, expect } from "vitest"; + +import { + CustomType, + DynamicSection, +} from "@prismicio/types-internal/lib/customtypes"; + +import * as CustomTypeModel from "../CustomTypeModel"; + +describe("CustomTypeModel test suite", () => { + const mainSection: DynamicSection = { + uid: { + config: { + label: "MainSectionField", + }, + type: "UID", + }, + slices: { + type: "Slices", + fieldset: "Slice Zone", + config: { + choices: { + hero_banner: { + type: "SharedSlice", + }, + promo_section_image_tiles: { + type: "SharedSlice", + }, + }, + }, + }, + }; + const anotherSection: DynamicSection = { + uid: { + config: { + label: "AnotherSectionField", + }, + type: "UID", + }, + }; + const mockCustomType: CustomType = { + format: "custom", + id: "id", + json: { + mainSection, + anotherSection, + }, + label: "lama", + repeatable: true, + status: true, + }; + + it("getSectionEntries should return the sections entries", () => { + expect(CustomTypeModel.getSectionEntries(mockCustomType)).toEqual([ + ["mainSection", mainSection], + ["anotherSection", anotherSection], + ]); + }); + + it("getSectionEntries should return an empty array if there are no sections", () => { + expect( + CustomTypeModel.getSectionEntries({ + ...mockCustomType, + json: {}, + }) + ).toEqual([]); + }); + + it("getMainSectionEntry should return the first section even if not named Main", () => { + expect(CustomTypeModel.getMainSectionEntry(mockCustomType)).toEqual([ + "mainSection", + mainSection, + ]); + }); + + it("getMainSectionEntry should return undefined if there is are sections", () => { + expect( + CustomTypeModel.getMainSectionEntry({ + ...mockCustomType, + json: {}, + }) + ).toEqual(undefined); + }); + + it("getSection should return the section matching the key", () => { + expect( + CustomTypeModel.getSection(mockCustomType, "anotherSection") + ).toEqual(anotherSection); + }); + + it("getSection should return undefined if there are no sections", () => { + expect( + CustomTypeModel.getSection( + { + ...mockCustomType, + json: {}, + }, + "mainSection" + ) + ).toEqual(undefined); + }); + + it("getSectionSliceZoneConfig should return the config of the given section", () => { + expect( + CustomTypeModel.getSectionSliceZoneConfig(mockCustomType, "mainSection") + ).toEqual({ + choices: { + hero_banner: { + type: "SharedSlice", + }, + promo_section_image_tiles: { + type: "SharedSlice", + }, + }, + }); + }); + + it("getSectionSliceZoneConfig should return undefined if there are no sections", () => { + expect( + CustomTypeModel.getSectionSliceZoneConfig( + { + ...mockCustomType, + json: {}, + }, + "mainSection" + ) + ).toEqual(undefined); + }); + + it("findAvailableSectionSlicesKey should return 'slices'", () => { + expect( + CustomTypeModel.findAvailableSectionSlicesKey( + { + ...mockCustomType, + json: { + anotherSection: {}, + }, + }, + "anotherSection" + ) + ).toEqual("slices"); + }); + + it("findAvailableSectionSlicesKey should return 'slices1'", () => { + expect( + CustomTypeModel.findAvailableSectionSlicesKey( + mockCustomType, + "anotherSection" + ) + ).toEqual("slices1"); + }); + + it("findAvailableSectionSlicesKey should return 'slices2'", () => { + expect( + CustomTypeModel.findAvailableSectionSlicesKey( + { + ...mockCustomType, + json: { + mainSection, + SecondSection: { + slices1: { + type: "Slices", + }, + }, + anotherSection, + }, + }, + "anotherSection" + ) + ).toEqual("slices2"); + }); + + it("findAvailableSectionSlicesKey should return 'slices3'", () => { + expect( + CustomTypeModel.findAvailableSectionSlicesKey( + { + ...mockCustomType, + json: { + mainSection, + SecondSection: { + slices2: { + type: "Slices", + }, + }, + anotherSection, + }, + }, + "anotherSection" + ) + ).toEqual("slices3"); + }); + + it("createSectionSliceZone should return the given custom type with a slice zone for given section", () => { + expect( + CustomTypeModel.createSectionSliceZone(mockCustomType, "anotherSection") + ).toEqual({ + ...mockCustomType, + json: { + ...mockCustomType.json, + anotherSection: { + ...mockCustomType.json.anotherSection, + slices1: { + type: "Slices", + fieldset: "Slice Zone", + }, + }, + }, + }); + }); + + it("createSectionSliceZone should return the same custom type if slice zone already exist for given section", () => { + expect( + CustomTypeModel.createSectionSliceZone(mockCustomType, "mainSection") + ).toEqual(mockCustomType); + }); + + it("convertToPageType should convert the given custom type", () => { + expect(CustomTypeModel.convertToPageType(mockCustomType)).toEqual({ + ...mockCustomType, + format: "page", + }); + }); + + it("convertToPageType should convert the given custom type with a slice zone for Main section when it doesn't exist", () => { + expect( + CustomTypeModel.convertToPageType({ + ...mockCustomType, + json: { + mainSection: {}, + anotherSection, + }, + }) + ).toEqual({ + ...mockCustomType, + json: { + mainSection: { + slices: { + type: "Slices", + fieldset: "Slice Zone", + }, + }, + anotherSection, + }, + format: "page", + }); + }); +}); diff --git a/packages/slice-machine/src/features/customTypes/actions/convertCustomToPageType.ts b/packages/slice-machine/src/features/customTypes/actions/convertCustomToPageType.ts index aa3fc67fa5..a58b5ca939 100644 --- a/packages/slice-machine/src/features/customTypes/actions/convertCustomToPageType.ts +++ b/packages/slice-machine/src/features/customTypes/actions/convertCustomToPageType.ts @@ -1,8 +1,10 @@ import { toast } from "react-toastify"; - import { CustomType } from "@prismicio/types-internal/lib/customtypes"; + import { CustomTypeFormat } from "@slicemachine/manager"; +import { convertToPageType } from "@src/domain/CustomTypeModel"; import { managerClient } from "@src/managerClient"; + import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages"; export async function convertCustomToPageType( @@ -13,10 +15,7 @@ export async function convertCustomToPageType( CUSTOM_TYPES_MESSAGES[customType.format as CustomTypeFormat]; try { - const newCustomType: CustomType = { - ...customType, - format: "page", - }; + const newCustomType = convertToPageType(customType); await managerClient.customTypes.updateCustomType({ model: newCustomType, });