@diviops/mcp-server 1.5.13 → 1.5.16
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 +14 -1
- package/data/verified-attrs-backlog.json +6 -6
- package/data/verified-attrs.json +32 -18
- package/dist/compatibility.d.ts +25 -0
- package/dist/index.js +349 -4
- package/dist/preset-cli/button-emitter.d.ts +1 -1
- package/dist/preset-cli/button-emitter.js +1 -1
- package/dist/preset-cli/cli.d.ts +7 -3
- package/dist/preset-cli/cli.js +131 -5
- package/dist/preset-cli/heading-font-emitter.d.ts +1 -1
- package/dist/preset-cli/heading-font-emitter.js +3 -3
- package/dist/preset-cli/spacing-emitter.d.ts +132 -0
- package/dist/preset-cli/spacing-emitter.js +276 -0
- package/dist/preset-cli/write-path.d.ts +16 -0
- package/dist/preset-cli/write-path.js +22 -0
- package/dist/wp-cli-fs-validator.d.ts +1 -1
- package/dist/wp-cli-fs-validator.js +1 -1
- package/dist/wp-cli.js +1 -2
- package/dist/wp-client.js +17 -0
- package/package.json +3 -2
- package/dist/preset-cli/__tests__/button-emitter.test.d.ts +0 -8
- package/dist/preset-cli/__tests__/button-emitter.test.js +0 -188
- package/dist/preset-cli/__tests__/cli.test.d.ts +0 -9
- package/dist/preset-cli/__tests__/cli.test.js +0 -534
- package/dist/preset-cli/__tests__/heading-font-emitter.test.d.ts +0 -12
- package/dist/preset-cli/__tests__/heading-font-emitter.test.js +0 -249
- package/dist/preset-cli/__tests__/preset-create-unchanged.test.d.ts +0 -13
- package/dist/preset-cli/__tests__/preset-create-unchanged.test.js +0 -64
- package/dist/preset-cli/__tests__/registry.test.d.ts +0 -5
- package/dist/preset-cli/__tests__/registry.test.js +0 -175
- package/dist/preset-cli/__tests__/text-body-font-emitter.test.d.ts +0 -14
- package/dist/preset-cli/__tests__/text-body-font-emitter.test.js +0 -191
- package/dist/preset-cli/__tests__/write-path.test.d.ts +0 -8
- package/dist/preset-cli/__tests__/write-path.test.js +0 -229
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `divi/font-body` text-body-font emitter shape + gating coverage:
|
|
3
|
-
* - fixture-based shape assertion against round-2 (Pattern A) canonical
|
|
4
|
-
* capture for the captured fields;
|
|
5
|
-
* - emit-on-specification: omitted size / lineHeight / weight produce no
|
|
6
|
-
* key in the output;
|
|
7
|
-
* - no renderAttrs on the in-memory entry (mirror happens at route layer);
|
|
8
|
-
* - Pattern B refusal: selecting `--pattern local` resolves to a
|
|
9
|
-
* registry-absence refusal (no `divi/font-body` + `local_hosted_pattern_b`
|
|
10
|
-
* entry exists in verified-attrs.json);
|
|
11
|
-
* - variant-aware registry gating: Pattern A evidence does NOT vouch for
|
|
12
|
-
* Pattern B and vice versa.
|
|
13
|
-
*/
|
|
14
|
-
import { test } from "node:test";
|
|
15
|
-
import assert from "node:assert/strict";
|
|
16
|
-
import { readFileSync } from "node:fs";
|
|
17
|
-
import { fileURLToPath } from "node:url";
|
|
18
|
-
import { dirname, join } from "node:path";
|
|
19
|
-
import { emitTextBodyFontGroupPreset, composeTextBodyFontAttrs, buildTextBodyFontPresetCreateBody, TEXT_BODY_FONT_MODULE, TEXT_BODY_FONT_GROUP_NAME, TEXT_BODY_FONT_GROUP_ID, TEXT_BODY_FONT_PATTERN_VARIANTS, } from "../text-body-font-emitter.js";
|
|
20
|
-
import { loadRegistry } from "../registry.js";
|
|
21
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
const REPO_ROOT = join(__dirname, "..", "..", "..", "..");
|
|
23
|
-
const FIXTURE_DIR = join(REPO_ROOT, "docs/verification/evidence/canonical-shape-dumps-2026-05-18");
|
|
24
|
-
const FIXTURE_ROUND_2 = join(FIXTURE_DIR, "round-2-body-text-pattern-a.json");
|
|
25
|
-
const registry = loadRegistry();
|
|
26
|
-
test("emitter byte-matches round-2 (Pattern A — Google Fonts) fixture attrs", () => {
|
|
27
|
-
const fixture = JSON.parse(readFileSync(FIXTURE_ROUND_2, "utf8"));
|
|
28
|
-
const canonicalAttrs = fixture.preset_entry.attrs;
|
|
29
|
-
const entry = emitTextBodyFontGroupPreset({
|
|
30
|
-
name: "canonical-body-text-vb-2026-05-18",
|
|
31
|
-
pattern: "google",
|
|
32
|
-
family: "Inter",
|
|
33
|
-
weight: "400",
|
|
34
|
-
color: '$variable({"type":"color","value":{"name":"gcid-body-color","settings":{}}})$',
|
|
35
|
-
size: "16px",
|
|
36
|
-
}, registry);
|
|
37
|
-
assert.deepEqual(entry.attrs, canonicalAttrs, "emitted attrs must byte-match the Pattern A canonical capture");
|
|
38
|
-
assert.equal(entry.type, "group");
|
|
39
|
-
assert.equal(entry.group_name, TEXT_BODY_FONT_GROUP_NAME);
|
|
40
|
-
assert.equal(entry.group_id, TEXT_BODY_FONT_GROUP_ID);
|
|
41
|
-
assert.equal(entry.module_name, TEXT_BODY_FONT_MODULE);
|
|
42
|
-
assert.equal(entry.pattern_variant, TEXT_BODY_FONT_PATTERN_VARIANTS.google);
|
|
43
|
-
});
|
|
44
|
-
test("emit-on-specification: only specified keys produce keys (no defaults)", () => {
|
|
45
|
-
const attrs = composeTextBodyFontAttrs({
|
|
46
|
-
name: "minimal",
|
|
47
|
-
pattern: "google",
|
|
48
|
-
family: "Inter",
|
|
49
|
-
});
|
|
50
|
-
const value = attrs.content.decoration.bodyFont.body.font.desktop
|
|
51
|
-
.value;
|
|
52
|
-
assert.deepEqual(Object.keys(value), ["family"], "ONLY the touched key emitted — no defaulted weight/color/size/lineHeight");
|
|
53
|
-
});
|
|
54
|
-
test("omitted weight / size / lineHeight produce no keys", () => {
|
|
55
|
-
const attrs = composeTextBodyFontAttrs({
|
|
56
|
-
name: "fc",
|
|
57
|
-
pattern: "google",
|
|
58
|
-
family: "Inter",
|
|
59
|
-
color: "#666666",
|
|
60
|
-
});
|
|
61
|
-
const value = attrs.content.decoration.bodyFont.body.font.desktop
|
|
62
|
-
.value;
|
|
63
|
-
assert.equal("weight" in value, false, "weight omitted → no key");
|
|
64
|
-
assert.equal("size" in value, false, "size omitted → no key");
|
|
65
|
-
assert.equal("lineHeight" in value, false, "lineHeight omitted → no key");
|
|
66
|
-
assert.deepEqual(Object.keys(value).sort(), ["color", "family"]);
|
|
67
|
-
});
|
|
68
|
-
test("entry carries no renderAttrs key (mirror happens at route layer)", () => {
|
|
69
|
-
const entry = emitTextBodyFontGroupPreset({
|
|
70
|
-
name: "no-renderattrs",
|
|
71
|
-
pattern: "google",
|
|
72
|
-
family: "Inter",
|
|
73
|
-
weight: "400",
|
|
74
|
-
}, registry);
|
|
75
|
-
assert.equal("renderAttrs" in entry, false, "renderAttrs must NOT appear on the in-memory entry — the plugin's " +
|
|
76
|
-
"/preset/create route mirrors attrs into styleAttrs/renderAttrs at " +
|
|
77
|
-
"the write layer");
|
|
78
|
-
assert.equal("styleAttrs" in entry, false, "same for styleAttrs");
|
|
79
|
-
});
|
|
80
|
-
test("variable color tokens get the {name,settings} object shape + trailing )$", () => {
|
|
81
|
-
const attrs = composeTextBodyFontAttrs({
|
|
82
|
-
name: "v",
|
|
83
|
-
pattern: "google",
|
|
84
|
-
color: "gcid-body-color",
|
|
85
|
-
});
|
|
86
|
-
const color = attrs.content.decoration.bodyFont.body.font.desktop
|
|
87
|
-
.value.color;
|
|
88
|
-
assert.ok(color.endsWith(")$"));
|
|
89
|
-
assert.match(color, /^\$variable\(/);
|
|
90
|
-
const payload = JSON.parse(color.slice("$variable(".length, -2));
|
|
91
|
-
assert.deepEqual(payload, {
|
|
92
|
-
type: "color",
|
|
93
|
-
value: { name: "gcid-body-color", settings: {} },
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
test("literal hex color is emitted verbatim, not wrapped", () => {
|
|
97
|
-
const attrs = composeTextBodyFontAttrs({
|
|
98
|
-
name: "h",
|
|
99
|
-
pattern: "google",
|
|
100
|
-
color: "#666666",
|
|
101
|
-
});
|
|
102
|
-
assert.equal(attrs.content.decoration.bodyFont.body.font.desktop.value.color, "#666666");
|
|
103
|
-
});
|
|
104
|
-
test("lineHeight is emitted only when specified", () => {
|
|
105
|
-
const withLH = composeTextBodyFontAttrs({
|
|
106
|
-
name: "lh+",
|
|
107
|
-
pattern: "google",
|
|
108
|
-
family: "Inter",
|
|
109
|
-
lineHeight: "1.5",
|
|
110
|
-
});
|
|
111
|
-
assert.equal(withLH.content.decoration.bodyFont.body.font.desktop.value
|
|
112
|
-
.lineHeight, "1.5");
|
|
113
|
-
const without = composeTextBodyFontAttrs({
|
|
114
|
-
name: "lh-",
|
|
115
|
-
pattern: "google",
|
|
116
|
-
family: "Inter",
|
|
117
|
-
});
|
|
118
|
-
assert.equal("lineHeight" in
|
|
119
|
-
without.content.decoration.bodyFont.body.font.desktop.value, false);
|
|
120
|
-
});
|
|
121
|
-
test("emitter rejects an invalid `pattern` value", () => {
|
|
122
|
-
assert.throws(() => emitTextBodyFontGroupPreset(
|
|
123
|
-
// @ts-expect-error — exercising runtime validation
|
|
124
|
-
{ name: "x", pattern: "auto", family: "Inter" }, registry), /pattern.*google.*local/i);
|
|
125
|
-
});
|
|
126
|
-
test("emitter rejects an empty preset (no styling specified)", () => {
|
|
127
|
-
assert.throws(() => emitTextBodyFontGroupPreset({ name: "empty", pattern: "google" }, registry), /empty preset/);
|
|
128
|
-
});
|
|
129
|
-
test("emitter rejects a missing name", () => {
|
|
130
|
-
assert.throws(() => emitTextBodyFontGroupPreset({ name: "", pattern: "google", family: "Inter" }, registry), /requires a non-empty `name`/);
|
|
131
|
-
});
|
|
132
|
-
test("Pattern B (local) is refused — registry-absence for divi/font-body", () => {
|
|
133
|
-
// Track 6 contract: NO `divi/font-body` + `local_hosted_pattern_b` entry
|
|
134
|
-
// exists in verified-attrs.json. `resolveEvidence`'s standard "absent
|
|
135
|
-
// from verified-attrs.json" throw fires natively — no special-case branch
|
|
136
|
-
// in the emitter. The error message MUST name the family AND the variant
|
|
137
|
-
// so operators can file the gap correctly.
|
|
138
|
-
assert.throws(() => emitTextBodyFontGroupPreset({
|
|
139
|
-
name: "body-local",
|
|
140
|
-
pattern: "local",
|
|
141
|
-
family: "Inter",
|
|
142
|
-
color: "#666666",
|
|
143
|
-
}, registry), (err) => {
|
|
144
|
-
assert.match(err.message, /absent from verified-attrs\.json/, "missing variant must fail with the canonical registry-absence message");
|
|
145
|
-
assert.match(err.message, /divi\/font-body/, "error names the pattern family");
|
|
146
|
-
assert.match(err.message, /local_hosted_pattern_b/, "error names the missing variant");
|
|
147
|
-
return true;
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
test("Pattern B refusal fires even with compound input (e.g. --font-weight)", () => {
|
|
151
|
-
// Compound-input parity: passing additional fields alongside
|
|
152
|
-
// --pattern local does NOT change the refusal — the gate fires on the
|
|
153
|
-
// missing registry entry regardless of which other fields are set.
|
|
154
|
-
assert.throws(() => emitTextBodyFontGroupPreset({
|
|
155
|
-
name: "body-local-weight",
|
|
156
|
-
pattern: "local",
|
|
157
|
-
family: "Inter",
|
|
158
|
-
weight: "700",
|
|
159
|
-
color: "#666666",
|
|
160
|
-
size: "16px",
|
|
161
|
-
}, registry), /absent from verified-attrs\.json/);
|
|
162
|
-
});
|
|
163
|
-
test("real registry: Pattern A on divi/text clears the write threshold", () => {
|
|
164
|
-
// Sanity guard: if a registry edit ever drops the (divi/font-body,
|
|
165
|
-
// google_fonts_pattern_a, divi/text) cell below VB_PRESET_STORAGE_VERIFIED
|
|
166
|
-
// (4), this test is the canary.
|
|
167
|
-
const entry = emitTextBodyFontGroupPreset({
|
|
168
|
-
name: "canary",
|
|
169
|
-
pattern: "google",
|
|
170
|
-
family: "Inter",
|
|
171
|
-
weight: "400",
|
|
172
|
-
}, registry);
|
|
173
|
-
assert.equal(entry.pattern_variant, TEXT_BODY_FONT_PATTERN_VARIANTS.google);
|
|
174
|
-
});
|
|
175
|
-
test("buildTextBodyFontPresetCreateBody mirrors the diviops_preset_create body shape", () => {
|
|
176
|
-
const entry = emitTextBodyFontGroupPreset({ name: "Body", pattern: "google", family: "Inter", weight: "400" }, registry);
|
|
177
|
-
const body = buildTextBodyFontPresetCreateBody(entry, { dry_run: true });
|
|
178
|
-
assert.deepEqual(body, {
|
|
179
|
-
module_name: TEXT_BODY_FONT_MODULE,
|
|
180
|
-
name: "Body",
|
|
181
|
-
attrs: entry.attrs,
|
|
182
|
-
type: "group",
|
|
183
|
-
group_name: TEXT_BODY_FONT_GROUP_NAME,
|
|
184
|
-
group_id: TEXT_BODY_FONT_GROUP_ID,
|
|
185
|
-
dry_run: true,
|
|
186
|
-
});
|
|
187
|
-
// pattern_variant is in-memory metadata; it must NOT leak into the wire body.
|
|
188
|
-
assert.equal("pattern_variant" in body, false, "pattern_variant is client-side gating metadata; it must not be sent over the wire");
|
|
189
|
-
const noDry = buildTextBodyFontPresetCreateBody(entry);
|
|
190
|
-
assert.equal("dry_run" in noDry, false);
|
|
191
|
-
});
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Apply-mode coverage — mocked only (no live substrate write, per #725 AC #8).
|
|
3
|
-
*
|
|
4
|
-
* Asserts: the capability gate (storage_multipath_probe_v1 present → proceed,
|
|
5
|
-
* absent → fail fast), the POST route + request body, and credential
|
|
6
|
-
* handling. The HTTP client is a stub; nothing touches the network.
|
|
7
|
-
*/
|
|
8
|
-
export {};
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Apply-mode coverage — mocked only (no live substrate write, per #725 AC #8).
|
|
3
|
-
*
|
|
4
|
-
* Asserts: the capability gate (storage_multipath_probe_v1 present → proceed,
|
|
5
|
-
* absent → fail fast), the POST route + request body, and credential
|
|
6
|
-
* handling. The HTTP client is a stub; nothing touches the network.
|
|
7
|
-
*/
|
|
8
|
-
import { test } from "node:test";
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
|
-
import { applyButtonPreset, applyHeadingFontPreset, applyTextBodyFontPreset, assertStorageCapability, buildClientFromEnv, CapabilityMissingError, CredentialsMissingError, STORAGE_CAPABILITY, PRESET_CREATE_ROUTE, } from "../write-path.js";
|
|
11
|
-
import { emitButtonGroupPreset } from "../button-emitter.js";
|
|
12
|
-
import { emitHeadingFontGroupPreset } from "../heading-font-emitter.js";
|
|
13
|
-
import { emitTextBodyFontGroupPreset } from "../text-body-font-emitter.js";
|
|
14
|
-
import { loadRegistry } from "../registry.js";
|
|
15
|
-
const registry = loadRegistry();
|
|
16
|
-
function handshake(capabilities) {
|
|
17
|
-
return {
|
|
18
|
-
compatible: true,
|
|
19
|
-
plugin_version: "1.4.9",
|
|
20
|
-
min_server: "1.5.0",
|
|
21
|
-
divi: { active: true, version: "5.5.2" },
|
|
22
|
-
capabilities,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
const TEST_SERVER_VERSION = "1.5.9";
|
|
26
|
-
/** A mock client recording the calls it receives. */
|
|
27
|
-
function mockClient(opts) {
|
|
28
|
-
const calls = [];
|
|
29
|
-
const handshakeVersions = [];
|
|
30
|
-
return {
|
|
31
|
-
calls,
|
|
32
|
-
handshakeVersions,
|
|
33
|
-
async handshake(serverVersion) {
|
|
34
|
-
handshakeVersions.push(serverVersion);
|
|
35
|
-
const hs = handshake(opts.capabilities);
|
|
36
|
-
if (opts.pluginVersion)
|
|
37
|
-
hs.plugin_version = opts.pluginVersion;
|
|
38
|
-
return hs;
|
|
39
|
-
},
|
|
40
|
-
async requestEnveloped(endpoint, options) {
|
|
41
|
-
calls.push({ endpoint, options });
|
|
42
|
-
return { ok: true, data: { preset_id: "mocked123" } };
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
test("assertStorageCapability proceeds when the capability is present", async () => {
|
|
47
|
-
const client = mockClient({ capabilities: { [STORAGE_CAPABILITY]: true } });
|
|
48
|
-
const hs = await assertStorageCapability(client, TEST_SERVER_VERSION);
|
|
49
|
-
assert.equal(hs.capabilities[STORAGE_CAPABILITY], true);
|
|
50
|
-
assert.deepEqual(client.handshakeVersions, [TEST_SERVER_VERSION], "the server version reaches handshake()");
|
|
51
|
-
assert.ok(client.handshakeVersions.every((v) => typeof v === "string" && v.length > 0), "handshake() never receives an undefined/empty server version");
|
|
52
|
-
});
|
|
53
|
-
test("assertStorageCapability fails fast when the capability is absent", async () => {
|
|
54
|
-
const client = mockClient({ capabilities: {}, pluginVersion: "1.4.8" });
|
|
55
|
-
await assert.rejects(() => assertStorageCapability(client, TEST_SERVER_VERSION), (err) => {
|
|
56
|
-
assert.ok(err instanceof CapabilityMissingError);
|
|
57
|
-
assert.equal(err.capability, STORAGE_CAPABILITY);
|
|
58
|
-
assert.match(err.message, /1\.4\.8/);
|
|
59
|
-
return true;
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
test("applyButtonPreset gates capability BEFORE issuing the write", async () => {
|
|
63
|
-
const client = mockClient({ capabilities: {} });
|
|
64
|
-
const entry = emitButtonGroupPreset({ name: "Primary", bg_color: "#111" }, registry);
|
|
65
|
-
await assert.rejects(() => applyButtonPreset(client, entry, { serverVersion: TEST_SERVER_VERSION }), CapabilityMissingError);
|
|
66
|
-
assert.equal(client.calls.length, 0, "no write issued when capability is absent");
|
|
67
|
-
});
|
|
68
|
-
test("applyButtonPreset posts to /preset/create with the canonical body", async () => {
|
|
69
|
-
const client = mockClient({ capabilities: { [STORAGE_CAPABILITY]: true } });
|
|
70
|
-
const entry = emitButtonGroupPreset({
|
|
71
|
-
name: "Primary",
|
|
72
|
-
bg_color: "gcid-primary-color",
|
|
73
|
-
bg_color_hover: "gcid-secondary-color",
|
|
74
|
-
radius: { topLeft: "8px", topRight: "8px", bottomLeft: "8px", bottomRight: "8px" },
|
|
75
|
-
font: { family: "Inter", weight: "600", color: "gcid-body-color" },
|
|
76
|
-
}, registry);
|
|
77
|
-
const result = await applyButtonPreset(client, entry, {
|
|
78
|
-
serverVersion: TEST_SERVER_VERSION,
|
|
79
|
-
});
|
|
80
|
-
assert.equal(client.handshakeVersions[0], TEST_SERVER_VERSION, "applyButtonPreset threads a non-empty server version into handshake()");
|
|
81
|
-
assert.ok((client.handshakeVersions[0] ?? "").length > 0, "handshake() server version is never empty in apply mode");
|
|
82
|
-
assert.equal(client.calls.length, 1, "exactly one write");
|
|
83
|
-
const call = client.calls[0];
|
|
84
|
-
assert.equal(call.endpoint, PRESET_CREATE_ROUTE, "posts to /preset/create");
|
|
85
|
-
const options = call.options;
|
|
86
|
-
assert.equal(options.method, "POST");
|
|
87
|
-
assert.equal(options.body.type, "group");
|
|
88
|
-
assert.equal(options.body.module_name, "divi/button");
|
|
89
|
-
assert.equal(options.body.group_name, "divi/button");
|
|
90
|
-
assert.equal(options.body.group_id, "button");
|
|
91
|
-
assert.equal(options.body.name, "Primary");
|
|
92
|
-
assert.deepEqual(options.body.attrs, entry.attrs);
|
|
93
|
-
assert.equal("dry_run" in options.body, false, "apply mode does not set dry_run");
|
|
94
|
-
assert.deepEqual(result, { ok: true, data: { preset_id: "mocked123" } });
|
|
95
|
-
});
|
|
96
|
-
test("applyButtonPreset threads dry_run into the body when requested", async () => {
|
|
97
|
-
const client = mockClient({ capabilities: { [STORAGE_CAPABILITY]: true } });
|
|
98
|
-
const entry = emitButtonGroupPreset({ name: "P", bg_color: "#111" }, registry);
|
|
99
|
-
await applyButtonPreset(client, entry, {
|
|
100
|
-
serverVersion: TEST_SERVER_VERSION,
|
|
101
|
-
dry_run: true,
|
|
102
|
-
});
|
|
103
|
-
const options = client.calls[0].options;
|
|
104
|
-
assert.equal(options.body.dry_run, true);
|
|
105
|
-
});
|
|
106
|
-
// ------------------------------------------------------------------
|
|
107
|
-
// heading-font apply-mode — mocked only.
|
|
108
|
-
// Mirrors the button apply-mode coverage: capability gate first, then a
|
|
109
|
-
// single POST to /preset/create with the canonical body. pattern_variant
|
|
110
|
-
// is in-memory only and must NOT appear in the wire body.
|
|
111
|
-
// ------------------------------------------------------------------
|
|
112
|
-
test("applyHeadingFontPreset gates capability BEFORE issuing the write", async () => {
|
|
113
|
-
const client = mockClient({ capabilities: {} });
|
|
114
|
-
const entry = emitHeadingFontGroupPreset({ name: "H1", pattern: "google", family: "Inter", weight: "700" }, registry);
|
|
115
|
-
await assert.rejects(() => applyHeadingFontPreset(client, entry, {
|
|
116
|
-
serverVersion: TEST_SERVER_VERSION,
|
|
117
|
-
}), CapabilityMissingError);
|
|
118
|
-
assert.equal(client.calls.length, 0, "no write issued when capability is absent");
|
|
119
|
-
});
|
|
120
|
-
test("applyHeadingFontPreset posts to /preset/create with the canonical body", async () => {
|
|
121
|
-
const client = mockClient({ capabilities: { [STORAGE_CAPABILITY]: true } });
|
|
122
|
-
const entry = emitHeadingFontGroupPreset({
|
|
123
|
-
name: "H1",
|
|
124
|
-
pattern: "google",
|
|
125
|
-
family: "Inter",
|
|
126
|
-
weight: "700",
|
|
127
|
-
color: "gcid-heading-color",
|
|
128
|
-
size: "48px",
|
|
129
|
-
}, registry);
|
|
130
|
-
const result = await applyHeadingFontPreset(client, entry, {
|
|
131
|
-
serverVersion: TEST_SERVER_VERSION,
|
|
132
|
-
});
|
|
133
|
-
assert.equal(client.handshakeVersions[0], TEST_SERVER_VERSION);
|
|
134
|
-
assert.equal(client.calls.length, 1);
|
|
135
|
-
const call = client.calls[0];
|
|
136
|
-
assert.equal(call.endpoint, PRESET_CREATE_ROUTE);
|
|
137
|
-
const options = call.options;
|
|
138
|
-
assert.equal(options.method, "POST");
|
|
139
|
-
assert.equal(options.body.type, "group");
|
|
140
|
-
assert.equal(options.body.module_name, "divi/heading");
|
|
141
|
-
assert.equal(options.body.group_name, "divi/font");
|
|
142
|
-
assert.equal(options.body.group_id, "designTitleText");
|
|
143
|
-
assert.equal(options.body.name, "H1");
|
|
144
|
-
assert.deepEqual(options.body.attrs, entry.attrs);
|
|
145
|
-
assert.equal("pattern_variant" in options.body, false, "pattern_variant is client-side gating metadata; it must not be on the wire");
|
|
146
|
-
assert.equal("dry_run" in options.body, false);
|
|
147
|
-
assert.deepEqual(result, { ok: true, data: { preset_id: "mocked123" } });
|
|
148
|
-
});
|
|
149
|
-
test("applyHeadingFontPreset threads dry_run into the body when requested", async () => {
|
|
150
|
-
const client = mockClient({ capabilities: { [STORAGE_CAPABILITY]: true } });
|
|
151
|
-
const entry = emitHeadingFontGroupPreset({ name: "H1", pattern: "local", family: "Sora 700" }, registry);
|
|
152
|
-
await applyHeadingFontPreset(client, entry, {
|
|
153
|
-
serverVersion: TEST_SERVER_VERSION,
|
|
154
|
-
dry_run: true,
|
|
155
|
-
});
|
|
156
|
-
const options = client.calls[0].options;
|
|
157
|
-
assert.equal(options.body.dry_run, true);
|
|
158
|
-
});
|
|
159
|
-
// ------------------------------------------------------------------
|
|
160
|
-
// text-body-font apply-mode — mocked only (Track 6).
|
|
161
|
-
// Mirrors the heading-font apply-mode coverage. pattern_variant is
|
|
162
|
-
// in-memory metadata and must NOT appear in the wire body. Pattern B is
|
|
163
|
-
// refused at the emitter level (registry-absence) — apply-mode is never
|
|
164
|
-
// reached for `--pattern local`, so it has no mocked test here.
|
|
165
|
-
// ------------------------------------------------------------------
|
|
166
|
-
test("applyTextBodyFontPreset gates capability BEFORE issuing the write", async () => {
|
|
167
|
-
const client = mockClient({ capabilities: {} });
|
|
168
|
-
const entry = emitTextBodyFontGroupPreset({ name: "Body", pattern: "google", family: "Inter", weight: "400" }, registry);
|
|
169
|
-
await assert.rejects(() => applyTextBodyFontPreset(client, entry, {
|
|
170
|
-
serverVersion: TEST_SERVER_VERSION,
|
|
171
|
-
}), CapabilityMissingError);
|
|
172
|
-
assert.equal(client.calls.length, 0, "no write issued when capability is absent");
|
|
173
|
-
});
|
|
174
|
-
test("applyTextBodyFontPreset posts to /preset/create with the canonical body", async () => {
|
|
175
|
-
const client = mockClient({ capabilities: { [STORAGE_CAPABILITY]: true } });
|
|
176
|
-
const entry = emitTextBodyFontGroupPreset({
|
|
177
|
-
name: "Body",
|
|
178
|
-
pattern: "google",
|
|
179
|
-
family: "Inter",
|
|
180
|
-
weight: "400",
|
|
181
|
-
color: "gcid-body-color",
|
|
182
|
-
size: "16px",
|
|
183
|
-
}, registry);
|
|
184
|
-
const result = await applyTextBodyFontPreset(client, entry, {
|
|
185
|
-
serverVersion: TEST_SERVER_VERSION,
|
|
186
|
-
});
|
|
187
|
-
assert.equal(client.handshakeVersions[0], TEST_SERVER_VERSION);
|
|
188
|
-
assert.equal(client.calls.length, 1);
|
|
189
|
-
const call = client.calls[0];
|
|
190
|
-
assert.equal(call.endpoint, PRESET_CREATE_ROUTE);
|
|
191
|
-
const options = call.options;
|
|
192
|
-
assert.equal(options.method, "POST");
|
|
193
|
-
assert.equal(options.body.type, "group");
|
|
194
|
-
assert.equal(options.body.module_name, "divi/text");
|
|
195
|
-
assert.equal(options.body.group_name, "divi/font-body");
|
|
196
|
-
assert.equal(options.body.group_id, "designText");
|
|
197
|
-
assert.equal(options.body.name, "Body");
|
|
198
|
-
assert.deepEqual(options.body.attrs, entry.attrs);
|
|
199
|
-
assert.equal("pattern_variant" in options.body, false, "pattern_variant is client-side gating metadata; it must not be on the wire");
|
|
200
|
-
assert.equal("dry_run" in options.body, false);
|
|
201
|
-
assert.deepEqual(result, { ok: true, data: { preset_id: "mocked123" } });
|
|
202
|
-
});
|
|
203
|
-
test("applyTextBodyFontPreset threads dry_run into the body when requested", async () => {
|
|
204
|
-
const client = mockClient({ capabilities: { [STORAGE_CAPABILITY]: true } });
|
|
205
|
-
const entry = emitTextBodyFontGroupPreset({ name: "Body", pattern: "google", family: "Inter" }, registry);
|
|
206
|
-
await applyTextBodyFontPreset(client, entry, {
|
|
207
|
-
serverVersion: TEST_SERVER_VERSION,
|
|
208
|
-
dry_run: true,
|
|
209
|
-
});
|
|
210
|
-
const options = client.calls[0].options;
|
|
211
|
-
assert.equal(options.body.dry_run, true);
|
|
212
|
-
});
|
|
213
|
-
test("buildClientFromEnv throws CredentialsMissingError when env vars are absent", () => {
|
|
214
|
-
assert.throws(() => buildClientFromEnv({}), (err) => {
|
|
215
|
-
assert.ok(err instanceof CredentialsMissingError);
|
|
216
|
-
assert.match(err.message, /WP_URL/);
|
|
217
|
-
assert.match(err.message, /WP_USER/);
|
|
218
|
-
assert.match(err.message, /WP_APP_PASSWORD/);
|
|
219
|
-
return true;
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
test("buildClientFromEnv succeeds with the standard env vars", () => {
|
|
223
|
-
const client = buildClientFromEnv({
|
|
224
|
-
WP_URL: "http://divi5-ai.local",
|
|
225
|
-
WP_USER: "admin",
|
|
226
|
-
WP_APP_PASSWORD: "xxxx xxxx xxxx",
|
|
227
|
-
});
|
|
228
|
-
assert.ok(client, "WPClient constructed from WP_URL/WP_USER/WP_APP_PASSWORD");
|
|
229
|
-
});
|