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
130 changes: 130 additions & 0 deletions apps/web/src/components/sandbox/content/cms-settings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { describe, expect, it } from "bun:test";
import type { LiveMeta } from "@/components/sections-editor/resolve-schema";
import { resolveAppEditorSchema } from "./app-editor-schema";
import {
CMS_SETTINGS_BLOCK_KEY,
CMS_SETTINGS_RESOLVE_TYPE,
hasCmsSettingsType,
readCmsSettingsBlock,
} from "./cms-settings";

/** As `deco schema` writes it: the type in the `content` group. */
const meta: LiveMeta = {
manifest: {
blocks: {
content: {
seo: { $ref: "#/definitions/c2Vv", namespace: "site" },
"cms-settings": {
$ref: "#/definitions/Y21zLXNldHRpbmdz",
namespace: "deco",
},
},
},
},
schema: {
definitions: {
Y21zLXNldHRpbmdz: {
title: "cms-settings",
type: "object",
required: ["__resolveType"],
properties: {
__resolveType: {
type: "string",
enum: ["cms-settings"],
default: "cms-settings",
},
preview: {
type: "object",
title: "Preview",
properties: {
hosts: {
type: "array",
title: "Hosts",
items: { type: "string" },
},
},
},
telemetry: {
type: "object",
title: "Telemetry",
properties: {
enabled: { type: "boolean", title: "Enabled", default: true },
errorSampleRate: { type: "number", default: 0.05 },
},
},
analytics: {
type: "object",
title: "Analytics",
properties: {
enabled: { type: "boolean", title: "Enabled", default: true },
},
},
},
},
},
},
};

describe("cms-settings", () => {
it("is offered only when the schema describes the type", () => {
expect(hasCmsSettingsType(meta)).toBe(true);
expect(hasCmsSettingsType(undefined)).toBe(false);
// Published 8.1.0-next.3: a content group without it.
expect(
hasCmsSettingsType({
manifest: { blocks: { content: { seo: { $ref: "#/x" } } } },
schema: {},
}),
).toBe(false);
// A v7 meta.
expect(
hasCmsSettingsType({
manifest: { blocks: { sections: { "site/sections/Hero.tsx": {} } } },
schema: {},
}),
).toBe(false);
});

it("stands in for an absent block with its type only (defaults)", () => {
expect(readCmsSettingsBlock({})).toEqual({
kind: "absent",
block: { __resolveType: CMS_SETTINGS_RESOLVE_TYPE },
});
expect(readCmsSettingsBlock({ [CMS_SETTINGS_BLOCK_KEY]: null }).kind).toBe(
"absent",
);
});

it("edits the saved block as is, variants included", () => {
const block = {
__resolveType: "cms-settings",
preview: { hosts: ["staging.example.com"] },
telemetry: {
__resolveType: "multivariate",
variants: [{ rule: { __resolveType: "always" }, value: {} }],
},
};
expect(readCmsSettingsBlock({ CMS: block })).toEqual({
kind: "saved",
block,
});
});

it("does not edit a block named CMS of another type", () => {
expect(readCmsSettingsBlock({ CMS: { __resolveType: "hero" } })).toEqual({
kind: "conflict",
resolveType: "hero",
});
expect(readCmsSettingsBlock({ CMS: "text" })).toEqual({
kind: "conflict",
resolveType: "",
});
});

it("resolves the form from the schema: the three sections", () => {
const schema = resolveAppEditorSchema(CMS_SETTINGS_RESOLVE_TYPE, meta);
expect(Object.keys(schema?.properties ?? {})).toEqual(
expect.arrayContaining(["preview", "telemetry", "analytics"]),
);
});
});
52 changes: 52 additions & 0 deletions apps/web/src/components/sandbox/content/cms-settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { LiveMeta } from "@/components/sections-editor/resolve-schema";

/**
* The CMS settings of a Blocks (v8) site: preview hosts, telemetry and
* analytics, in one optional saved block. Its name and type are the only two
* things the editor knows about it; the form comes from the schema, which
* describes the type. A site whose framework has no such type (published
* `@decocms/blocks` before it existed, or a v7 site) gets no Settings entry.
*/
export const CMS_SETTINGS_BLOCK_KEY = "CMS";
export const CMS_SETTINGS_RESOLVE_TYPE = "cms-settings";

/** Whether the schema describes the `cms-settings` block type. */
export function hasCmsSettingsType(meta: LiveMeta | undefined): boolean {
const groups = meta?.manifest?.blocks ?? {};
return Object.values(groups).some(
(group) => !!group && Object.hasOwn(group, CMS_SETTINGS_RESOLVE_TYPE),
);
}

export type CmsSettingsBlock =
/** No `CMS` block yet: every setting is at its default; the first save creates it. */
| { kind: "absent"; block: Record<string, unknown> }
| { kind: "saved"; block: Record<string, unknown> }
/** A block named `CMS` of another type (`deco check` warns about it). */
| { kind: "conflict"; resolveType: string };

/** The `CMS` block to edit, or a stand-in holding only its type when absent. */
export function readCmsSettingsBlock(
decofile: Record<string, unknown>,
): CmsSettingsBlock {
const value = Object.hasOwn(decofile, CMS_SETTINGS_BLOCK_KEY)
? decofile[CMS_SETTINGS_BLOCK_KEY]
: undefined;
if (value === undefined || value === null) {
return {
kind: "absent",
block: { __resolveType: CMS_SETTINGS_RESOLVE_TYPE },
};
}
const resolveType =
typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>).__resolveType
: undefined;
if (resolveType !== CMS_SETTINGS_RESOLVE_TYPE) {
return {
kind: "conflict",
resolveType: typeof resolveType === "string" ? resolveType : "",
};
}
return { kind: "saved", block: value as Record<string, unknown> };
}
Loading