Files
openmaic/OpenMAIC/tests/lib/agent/tools/edit-elements-gate.test.ts
2026-08-16 14:58:47 +08:00

2779 lines
77 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import * as editElementsGate from '@/lib/agent/tools/edit-elements-gate';
import sceneSchemaJson from '@openmaic/dsl/schema/scene.schema.json';
import {
ALLOWED_EDIT_PROPS,
buildElementInventory,
clampUpdateProps,
elementInventoryFingerprint,
getEditablePropSchema,
mapProposalsToEditIntents,
normalizeRotate,
validateJsonSchemaSubset,
type ElementInventoryItem,
type JsonSchema,
} from '@/lib/agent/tools/edit-elements-gate';
import type { PPTElement } from '@openmaic/dsl';
type SubsetValidator = (
value: unknown,
schema: Record<string, unknown>,
path: string,
) => string | null;
function textEl(overrides: Partial<PPTElement> & { id: string }): ElementInventoryItem {
return {
id: overrides.id,
type: 'text',
left: 100,
top: 80,
width: 400,
height: 60,
rotate: 0,
lock: false,
label: 'Title',
style: { defaultColor: '#333333' },
...('lock' in overrides ? { lock: !!overrides.lock } : {}),
};
}
const inventory: ElementInventoryItem[] = [
textEl({ id: 'title-1' }),
{
id: 'fig-1',
type: 'shape',
left: 200,
top: 200,
width: 120,
height: 120,
rotate: 0,
lock: false,
label: 'figure',
style: { fill: '#eeeeee' },
},
{
id: 'locked-1',
type: 'text',
left: 10,
top: 10,
width: 100,
height: 40,
rotate: 0,
lock: true,
label: 'locked',
style: {},
},
];
describe('edit-elements-gate', () => {
it('refuses an empty update batch', () => {
expect(mapProposalsToEditIntents([], inventory)).toEqual({
ok: false,
reason: 'no element updates proposed',
});
});
it('maps a single color+position update to element.update', () => {
const result = mapProposalsToEditIntents(
[{ id: 'title-1', props: { defaultColor: '#0000ff', top: 40 } }],
inventory,
);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.intents).toEqual([
{
type: 'element.update',
id: 'title-1',
props: { defaultColor: '#0000ff', top: 40 },
},
]);
});
it('refuses edits to the renderer-managed text sizing axis', () => {
const horizontal = textEl({ id: 'horizontal' });
const vertical = { ...textEl({ id: 'vertical' }), style: { vertical: true } };
const horizontalHeight = mapProposalsToEditIntents(
[{ id: 'horizontal', props: { height: 120 } }],
[horizontal],
);
const verticalWidth = mapProposalsToEditIntents(
[{ id: 'vertical', props: { width: 120 } }],
[vertical],
);
expect(horizontalHeight.ok).toBe(false);
expect(verticalWidth.ok).toBe(false);
if (!horizontalHeight.ok) expect(horizontalHeight.reason).toMatch(/automatic height/i);
if (!verticalWidth.ok) expect(verticalWidth.reason).toMatch(/automatic width/i);
expect(
mapProposalsToEditIntents([{ id: 'horizontal', props: { width: 500 } }], [horizontal]).ok,
).toBe(true);
expect(
mapProposalsToEditIntents([{ id: 'vertical', props: { height: 500 } }], [vertical]).ok,
).toBe(true);
});
it('refuses defaultColor when inline text color would override it', () => {
const [inlineColored] = buildElementInventory([
{
id: 'imported-title',
type: 'text',
left: 100,
top: 80,
width: 400,
height: 60,
rotate: 0,
content: '<p><span style="font-size: 28px; color: #123456">Title</span></p>',
defaultColor: '#333333',
defaultFontName: 'Arial',
} as PPTElement,
]);
const result = mapProposalsToEditIntents(
[{ id: 'imported-title', props: { defaultColor: '#0000ff' } }],
[inlineColored],
);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/inline text color/i);
});
it('refuses defaultFontName when inline font-family would override it', () => {
const [inlineFont] = buildElementInventory([
{
id: 'imported-title',
type: 'text',
left: 100,
top: 80,
width: 400,
height: 60,
rotate: 0,
content: '<p><span style="font-family: Aptos; font-size: 28px">Title</span></p>',
defaultColor: '#333333',
defaultFontName: 'Arial',
} as PPTElement,
]);
const result = mapProposalsToEditIntents(
[{ id: 'imported-title', props: { defaultFontName: 'Inter' } }],
[inlineFont],
);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/inline font-family/i);
});
it('refuses text spacing props hidden by descendant inline styles', () => {
const [inlineSpacing] = buildElementInventory([
{
id: 'imported-title',
type: 'text',
left: 100,
top: 80,
width: 400,
height: 60,
rotate: 0,
content:
'<p style="line-height: 1.8; margin-bottom: 12px"><span style="letter-spacing: 2px">Title</span></p>',
defaultColor: '#333333',
defaultFontName: 'Arial',
} as PPTElement,
]);
for (const props of [{ lineHeight: 1.5 }, { wordSpace: 4 }, { paragraphSpace: 8 }]) {
const result = mapProposalsToEditIntents([{ id: 'imported-title', props }], [inlineSpacing]);
expect(result.ok, JSON.stringify(props)).toBe(false);
if (!result.ok) expect(result.reason).toMatch(/inline/i);
}
});
it('detects inline spacing overrides inside shape labels', () => {
const [shapeLabel] = buildElementInventory([
{
id: 'shape-label',
type: 'shape',
left: 0,
top: 0,
width: 100,
height: 80,
rotate: 0,
viewBox: [100, 80],
path: 'M0 0',
fixedRatio: false,
fill: '#fff',
text: {
content: '<p style="line-height: 2; margin-bottom: 6px">Label</p>',
defaultFontName: 'Arial',
defaultColor: '#111',
align: 'middle',
},
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: 'shape-label', props: { lineHeight: 1.5 } }], [shapeLabel])
.ok,
).toBe(false);
expect(
mapProposalsToEditIntents(
[{ id: 'shape-label', props: { paragraphSpace: 10 } }],
[shapeLabel],
).ok,
).toBe(false);
});
it('does not mistake background-color for inline text color', () => {
const [backgroundOnly] = buildElementInventory([
{
id: 'title',
type: 'text',
left: 100,
top: 80,
width: 400,
height: 60,
rotate: 0,
content: '<p><span style="background-color: #fff">Title</span></p>',
defaultColor: '#333333',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: 'title', props: { defaultColor: '#0000ff' } }],
[backgroundOnly],
).ok,
).toBe(true);
});
it('maps mixed-target updates to one element.updateMany', () => {
const result = mapProposalsToEditIntents(
[
{ id: 'title-1', props: { defaultColor: '#0000ff' } },
{ id: 'fig-1', props: { left: 200, top: 260 } },
],
inventory,
);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.intents).toHaveLength(1);
expect(result.intents[0].type).toBe('element.updateMany');
});
it('refuses unknown element ids (nothing partial)', () => {
const result = mapProposalsToEditIntents([{ id: 'nope', props: { top: 10 } }], inventory);
expect(result).toEqual({
ok: false,
reason: 'unknown element id "nope"',
});
});
it('refuses locked elements', () => {
const result = mapProposalsToEditIntents([{ id: 'locked-1', props: { top: 20 } }], inventory);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/locked/);
});
it('refuses content props like content/src', () => {
const result = mapProposalsToEditIntents(
[{ id: 'title-1', props: { content: '<p>hi</p>' } }],
inventory,
);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/not editable/);
});
it('refuses out-of-contract props', () => {
const result = mapProposalsToEditIntents([{ id: 'title-1', props: { mystery: 1 } }], inventory);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/out of contract/);
});
it('refuses empty props', () => {
const result = mapProposalsToEditIntents([{ id: 'title-1', props: {} }], inventory);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/empty props/i);
});
it('refuses the whole batch when one update is bad', () => {
const result = mapProposalsToEditIntents(
[
{ id: 'title-1', props: { top: 10 } },
{ id: 'nope', props: { top: 10 } },
],
inventory,
);
expect(result.ok).toBe(false);
});
it('clamps width to MIN_SIZE for text (40)', () => {
expect(clampUpdateProps('text', { width: 5 })).toEqual({
width: 40,
});
});
it('normalizes rotate into (-180, 180]', () => {
expect(normalizeRotate(270)).toBe(-90);
expect(normalizeRotate(-270)).toBe(90);
expect(normalizeRotate(180)).toBe(180);
});
it('refuses coordinates outside the canvas sanity bounds', () => {
const result = mapProposalsToEditIntents([{ id: 'title-1', props: { left: 1e15 } }], inventory);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/left out of bounds/i);
});
it('refuses non-finite rotate values', () => {
for (const rotate of [Number.NaN, Number.POSITIVE_INFINITY]) {
const result = mapProposalsToEditIntents([{ id: 'title-1', props: { rotate } }], inventory);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/rotate must be a finite number/i);
}
});
it('builds inventory labels from text content', () => {
const els = [
{
id: 't1',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '<p>Hello <b>World</b></p>',
defaultFontName: 'Arial',
defaultColor: '#111',
},
] as unknown as PPTElement[];
const inv = buildElementInventory(els);
expect(inv[0].label).toBe('Hello World');
expect(inv[0].style.defaultColor).toBe('#111');
});
it('maps shape text vertical alignment into inventory and fingerprints', () => {
const shape = {
id: 'shape-label',
type: 'shape',
left: 0,
top: 0,
width: 100,
height: 80,
rotate: 0,
viewBox: [100, 80],
path: 'M0 0',
fixedRatio: false,
fill: '#fff',
text: {
content: 'Label',
defaultFontName: 'Arial',
defaultColor: '#111',
align: 'bottom',
},
} as PPTElement;
expect(buildElementInventory([shape])[0].style.vAlign).toBe('bottom');
});
it('fingerprints shape patterns that fill and gradient edits would replace', () => {
const shape = {
id: 'pattern-shape',
type: 'shape',
left: 0,
top: 0,
width: 100,
height: 80,
rotate: 0,
viewBox: [100, 80],
path: 'M0 0',
fixedRatio: false,
fill: '#ffffff',
pattern: 'https://example.com/a.png',
} as PPTElement;
const changed = { ...shape, pattern: 'https://example.com/b.png' } as PPTElement;
expect(elementInventoryFingerprint(shape)).not.toBe(elementInventoryFingerprint(changed));
const collisionA = {
...shape,
pattern: 'https://e.test/1fiw1ko1qmpx6m',
} as PPTElement;
const collisionB = {
...shape,
pattern: 'https://e.test/17zdu9zpfflr',
} as PPTElement;
expect(elementInventoryFingerprint(collisionA)).not.toBe(
elementInventoryFingerprint(collisionB),
);
const [item] = buildElementInventory([shape]);
expect(
mapProposalsToEditIntents([{ id: shape.id, props: { fill: '#ffffff' } }], [item]).ok,
).toBe(true);
});
it('fingerprints hidden state consumed by resize application', () => {
const tableData = [[{ id: 'cell-1', text: 'x' }]];
const table = {
id: 'resize-table',
type: 'table',
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
data: tableData,
colWidths: [1],
rowHeights: [20],
cellMinHeight: 36,
} as unknown as PPTElement;
expect(elementInventoryFingerprint(table)).not.toBe(
elementInventoryFingerprint({ ...table, rowHeights: [80] } as PPTElement),
);
expect(elementInventoryFingerprint(table)).not.toBe(
elementInventoryFingerprint({ ...table, cellMinHeight: 72 } as PPTElement),
);
expect(elementInventoryFingerprint(table)).not.toBe(
elementInventoryFingerprint({ ...table, data: [...tableData, []] } as PPTElement),
);
const shape = {
id: 'resize-shape',
type: 'shape',
left: 0,
top: 0,
width: 100,
height: 80,
rotate: 0,
viewBox: [100, 80],
path: 'M0 0',
pathFormula: 'rect',
keypoints: [1],
fixedRatio: false,
} as unknown as PPTElement;
expect(elementInventoryFingerprint(shape)).not.toBe(
elementInventoryFingerprint({
...shape,
pathFormula: 'triangle',
keypoints: [99],
} as PPTElement),
);
});
it('fingerprints exact HTML dependencies used by composed visibility', () => {
const text = {
id: 'visibility-fingerprint',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
defaultColor: 'transparent',
defaultFontName: 'Arial',
content:
'<span style="-webkit-text-fill-color:transparent;text-shadow:0 0 2px currentColor">x</span>',
} as PPTElement;
const changed = {
...text,
content:
'<span style="-webkit-text-fill-color:transparent;text-shadow:0 0 2px transparent">x</span>',
} as PPTElement;
expect(JSON.stringify(buildElementInventory([text])[0])).toBe(
JSON.stringify(buildElementInventory([changed])[0]),
);
expect(elementInventoryFingerprint(text)).not.toBe(elementInventoryFingerprint(changed));
});
it('refuses malformed prop values (outline/shadow/color)', () => {
expect(
mapProposalsToEditIntents([{ id: 'title-1', props: { outline: 17 } }], inventory).ok,
).toBe(false);
expect(
mapProposalsToEditIntents([{ id: 'title-1', props: { shadow: 'big' } }], inventory).ok,
).toBe(false);
for (const invalidColor of [
'ff0000',
'rgb(1,2,3) trailing',
'rgb(1,2,3 junk)',
'rgb(1,,2,3)',
'rgb(10%, 20, 30%)',
'rgba(10%, 20, 30%, .5)',
'rgba(1, 2, 3, 50%)',
'hsla(10, 20%, 30%, 50%)',
'hsl(10,20,30)',
]) {
expect(
mapProposalsToEditIntents(
[{ id: 'title-1', props: { defaultColor: invalidColor } }],
inventory,
).ok,
).toBe(false);
}
expect(
mapProposalsToEditIntents([{ id: 'title-1', props: { defaultColor: 12 } }], inventory).ok,
).toBe(false);
expect(
mapProposalsToEditIntents(
[{ id: 'title-1', props: { rotate: '45' as unknown as number } }],
inventory,
).ok,
).toBe(false);
});
it('validates lineHeight as a multiplier independently from pixel spacing', () => {
for (const lineHeight of [1, 1.5, 3]) {
expect(
mapProposalsToEditIntents([{ id: 'title-1', props: { lineHeight } }], inventory).ok,
).toBe(true);
}
for (const lineHeight of [0.9, 3.1, 20]) {
const result = mapProposalsToEditIntents(
[{ id: 'title-1', props: { lineHeight } }],
inventory,
);
expect(result.ok, String(lineHeight)).toBe(false);
if (!result.ok) expect(result.reason).toMatch(/lineHeight.*1\.\.3/i);
}
expect(
mapProposalsToEditIntents(
[{ id: 'title-1', props: { wordSpace: 20, paragraphSpace: 20 } }],
inventory,
).ok,
).toBe(true);
});
it('refuses negative shadow blur that would produce invalid CSS', () => {
const result = mapProposalsToEditIntents(
[
{
id: 'title-1',
props: { shadow: { h: 2, v: 2, blur: -10, color: '#000000' } },
},
],
inventory,
);
expect(result.ok).toBe(false);
if (!result.ok) expect(result.reason).toMatch(/shadow\.blur/i);
});
it('refuses junk nested inside gradient', () => {
const result = mapProposalsToEditIntents(
[
{
id: 'fig-1',
props: {
gradient: { type: 'linear', colors: ['#f00', '#00f'], rotate: 0 },
},
},
],
inventory,
);
expect(result.ok).toBe(false);
});
it('accepts a well-formed gradient on shapes', () => {
const result = mapProposalsToEditIntents(
[
{
id: 'fig-1',
props: {
gradient: {
type: 'linear',
colors: [
{ pos: 0, color: '#f00' },
{ pos: 100, color: '#00f' },
],
rotate: 0,
},
},
},
],
inventory,
);
expect(result.ok).toBe(true);
});
it('refuses color on text and shape elements because the DSL has no top-level color there', () => {
const textResult = mapProposalsToEditIntents(
[{ id: 'title-1', props: { color: '#f00' } }],
inventory,
);
expect(textResult.ok).toBe(false);
if (textResult.ok) return;
expect(textResult.reason).toMatch(/color is not valid on text elements/i);
const shapeResult = mapProposalsToEditIntents(
[{ id: 'fig-1', props: { color: '#f00' } }],
inventory,
);
expect(shapeResult.ok).toBe(false);
if (shapeResult.ok) return;
expect(shapeResult.reason).toMatch(/color is not valid on shape elements/i);
});
it('allows fill on chart elements because the DSL chart schema owns it', () => {
const chartInventory: ElementInventoryItem[] = [
{
id: 'chart-1',
type: 'chart',
left: 0,
top: 0,
width: 320,
height: 180,
rotate: 0,
lock: false,
label: 'chart',
style: { themeColors: ['#f00'] },
},
];
const result = mapProposalsToEditIntents(
[{ id: 'chart-1', props: { fill: '#ffffff' } }],
chartInventory,
);
expect(result.ok).toBe(true);
});
it('refuses vertical on shapes because ShapeText has no vertical prop', () => {
const result = mapProposalsToEditIntents(
[{ id: 'fig-1', props: { vertical: true } }],
inventory,
);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/vertical is not valid on shape elements/i);
});
it('refuses textType because it is semantic metadata, not a visible Pro edit', () => {
const result = mapProposalsToEditIntents(
[{ id: 'title-1', props: { textType: 'title' } }],
inventory,
);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/textType/i);
});
it('derives editable prop schemas from the DSL schema', () => {
for (const key of ALLOWED_EDIT_PROPS) {
const owningTypes = [
'text',
'image',
'shape',
'line',
'chart',
'table',
'latex',
'video',
'audio',
'code',
].filter((type) => getEditablePropSchema(type, key));
expect(owningTypes, `${key} should resolve for at least one element type`).not.toHaveLength(
0,
);
}
expect(getEditablePropSchema('text', 'notARealProp')).toBeNull();
expect(getEditablePropSchema('shape', 'notARealProp')).toBeNull();
});
it('resolves asset-reference property indirection to strings', () => {
const definitions = (sceneSchemaJson as { definitions: Record<string, JsonSchema> })
.definitions;
for (const [definition, property] of [
['PPTImageElement', 'src'],
['PPTVideoElement', 'src'],
['PPTVideoElement', 'mediaRef'],
['SpeechAction', 'audioId'],
] as const) {
const propertySchema = definitions[definition]?.properties?.[property];
expect(propertySchema, `${definition}.${property} should exist`).toBeDefined();
expect(validateJsonSchemaSubset('asset-ref', propertySchema ?? {}, property)).toBeNull();
expect(validateJsonSchemaSubset(1, propertySchema ?? {}, property)).toBe(
`${property} must be string`,
);
}
});
it('keeps layered policy on top of schema-derived object validation', () => {
const tooManyStops = Array.from({ length: 11 }, (_, i) => ({
pos: i * 10,
color: '#f00',
}));
const gradientResult = mapProposalsToEditIntents(
[
{
id: 'fig-1',
props: {
gradient: { type: 'linear', colors: tooManyStops, rotate: 0 },
},
},
],
inventory,
);
expect(gradientResult.ok).toBe(false);
if (gradientResult.ok) return;
expect(gradientResult.reason).toMatch(/gradient.colors/i);
const imageInventory: ElementInventoryItem[] = [
{
id: 'img-1',
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'pic',
style: {},
},
];
const filterResult = mapProposalsToEditIntents(
[{ id: 'img-1', props: { filters: { blur: 'x'.repeat(41) } } }],
imageInventory,
);
expect(filterResult.ok).toBe(false);
if (filterResult.ok) return;
expect(filterResult.reason).toMatch(/filters.blur/i);
});
it('normalizes image filter values to canonical unitless strings', () => {
const imageInventory: ElementInventoryItem[] = [
{
id: 'img-1',
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'pic',
style: {},
},
];
const result = mapProposalsToEditIntents(
[{ id: 'img-1', props: { filters: { brightness: '120%', blur: '2px' } } }],
imageInventory,
);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.intents).toEqual([
{
type: 'element.update',
id: 'img-1',
props: { filters: { brightness: '120', blur: '2' } },
},
]);
});
it('refuses image filter values outside renderable CSS ranges', () => {
const imageInventory: ElementInventoryItem[] = [
{
id: 'img-1',
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'pic',
style: {},
},
];
for (const filters of [
{ blur: '-2' },
{ brightness: '-20%' },
{ opacity: '101%' },
{ invert: '120' },
]) {
const result = mapProposalsToEditIntents(
[{ id: 'img-1', props: { filters } }],
imageInventory,
);
expect(result.ok, JSON.stringify(filters)).toBe(false);
}
});
it('keeps themeColors non-empty', () => {
const chartInventory: ElementInventoryItem[] = [
{
id: 'chart-1',
type: 'chart',
left: 0,
top: 0,
width: 320,
height: 180,
rotate: 0,
lock: false,
label: 'chart',
style: { themeColors: ['#f00'] },
},
];
const result = mapProposalsToEditIntents(
[{ id: 'chart-1', props: { themeColors: [] } }],
chartInventory,
);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/themeColors/i);
});
it('refuses defaultColor on image elements', () => {
const inv: ElementInventoryItem[] = [
{
id: 'img-1',
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'pic',
style: {},
},
];
expect(
mapProposalsToEditIntents([{ id: 'img-1', props: { defaultColor: '#f00' } }], inv).ok,
).toBe(false);
});
it('clamps line stroke width with min 1, not box MIN_SIZE', () => {
expect(clampUpdateProps('line', { width: 0.5 })).toEqual({ width: 1 });
expect(clampUpdateProps('line', { width: 4 })).toEqual({ width: 4 });
});
it('clamps opacity overshoot on valid opacity props', () => {
const faded = [{ ...inventory[0], style: { ...inventory[0].style, opacity: 0.5 } }];
const result = mapProposalsToEditIntents([{ id: 'title-1', props: { opacity: 1.5 } }], faded);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.intents).toEqual([
{
type: 'element.update',
id: 'title-1',
props: { opacity: 1 },
},
]);
expect(clampUpdateProps('text', { opacity: -0.25 })).toEqual({
opacity: 0,
});
});
it('fails closed for schema refs and constructs the subset checker cannot validate', () => {
const validateJsonSchemaSubset = (
editElementsGate as typeof editElementsGate & {
validateJsonSchemaSubset?: SubsetValidator;
}
).validateJsonSchemaSubset;
expect(
validateJsonSchemaSubset?.(
'anything',
{ $ref: '#/definitions/DefinitelyMissing' },
'prop fill',
),
).toBe('prop fill uses a schema construct the gate cannot validate');
expect(
validateJsonSchemaSubset?.('anything', { oneOf: [{ type: 'string' }] }, 'prop fill'),
).toBe('prop fill uses a schema construct the gate cannot validate');
expect(validateJsonSchemaSubset?.('anything', {}, 'prop fill')).toBeNull();
});
it('refuses partial group updates', () => {
const grouped: ElementInventoryItem[] = [
{ ...textEl({ id: 'g1' }), groupId: 'grp' },
{
id: 'g2',
type: 'shape',
left: 0,
top: 0,
width: 50,
height: 50,
rotate: 0,
lock: false,
label: 'icon',
style: { fill: '#5b9bd5' },
groupId: 'grp',
},
];
const result = mapProposalsToEditIntents([{ id: 'g1', props: { left: 10 } }], grouped);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/group/i);
});
it('allows updating every member of a group together', () => {
const grouped: ElementInventoryItem[] = [
{ ...textEl({ id: 'g1' }), groupId: 'grp' },
{
id: 'g2',
type: 'shape',
left: 0,
top: 0,
width: 50,
height: 50,
rotate: 0,
lock: false,
label: 'icon',
style: { fill: '#5b9bd5' },
groupId: 'grp',
},
];
const result = mapProposalsToEditIntents(
[
{ id: 'g1', props: { left: 110, top: 100 } },
{ id: 'g2', props: { left: 10, top: 20 } },
],
grouped,
);
expect(result.ok).toBe(true);
});
it('refuses non-rigid group movement and grouped resize', () => {
const grouped: ElementInventoryItem[] = [
{ ...textEl({ id: 'g1' }), groupId: 'grp' },
{
id: 'g2',
type: 'shape',
left: 0,
top: 0,
width: 50,
height: 50,
rotate: 0,
lock: false,
label: 'icon',
style: { fill: '#5b9bd5' },
groupId: 'grp',
},
];
const nonRigid = mapProposalsToEditIntents(
[
{ id: 'g1', props: { left: 110 } },
{ id: 'g2', props: { left: 20 } },
],
grouped,
);
const resize = mapProposalsToEditIntents(
[
{ id: 'g1', props: { width: 500 } },
{ id: 'g2', props: { width: 60 } },
],
grouped,
);
expect(nonRigid.ok).toBe(false);
if (!nonRigid.ok) expect(nonRigid.reason).toMatch(/rigid translation/i);
expect(resize.ok).toBe(false);
if (!resize.ok) expect(resize.reason).toMatch(/resize or rotate/i);
});
it('allows rigid group translation with decimal canvas coordinates', () => {
const grouped: ElementInventoryItem[] = [
{ ...textEl({ id: 'g1' }), left: 0.1, groupId: 'grp' },
{
id: 'g2',
type: 'shape',
left: 100.2,
top: 0,
width: 50,
height: 50,
rotate: 0,
lock: false,
label: 'icon',
style: { fill: '#5b9bd5' },
groupId: 'grp',
},
];
const result = mapProposalsToEditIntents(
[
{ id: 'g1', props: { left: 10.2 } },
{ id: 'g2', props: { left: 110.3 } },
],
grouped,
);
expect(result.ok).toBe(true);
});
it('surfaces groupId on inventory items', () => {
const els = [
{
id: 't1',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
groupId: 'g',
content: 'x',
defaultFontName: 'Arial',
defaultColor: '#111',
},
] as unknown as PPTElement[];
expect(buildElementInventory(els)[0].groupId).toBe('g');
});
it('refuses props the active Pro editor cannot render', () => {
const unsupported: ElementInventoryItem[] = [
{
id: 'code-1',
type: 'code',
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
lock: false,
label: 'code',
style: { fontSize: 16 },
},
{
id: 'audio-1',
type: 'audio',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
lock: false,
label: 'audio',
style: { color: '#000000' },
},
];
expect(
mapProposalsToEditIntents([{ id: 'code-1', props: { fontSize: 20 } }], unsupported).ok,
).toBe(false);
expect(
mapProposalsToEditIntents([{ id: 'audio-1', props: { color: '#ff0000' } }], unsupported).ok,
).toBe(false);
expect(
mapProposalsToEditIntents([{ id: 'title-1', props: { vAlign: 'bottom' } }], inventory).ok,
).toBe(false);
expect(
mapProposalsToEditIntents([{ id: 'title-1', props: { textType: 'title' } }], inventory).ok,
).toBe(false);
});
it('refuses visually equivalent color and filter updates as no-ops', () => {
const colorEquivalent = mapProposalsToEditIntents(
[{ id: 'title-1', props: { defaultColor: 'rgb(51, 51, 51)' } }],
inventory,
);
expect(colorEquivalent.ok).toBe(false);
if (!colorEquivalent.ok) expect(colorEquivalent.reason).toMatch(/no effective change/i);
const nestedColorInventory: ElementInventoryItem[] = [
{
...inventory[1],
style: { outline: { width: 2, color: '#ff0000' } },
},
];
const nestedColorEquivalent = mapProposalsToEditIntents(
[{ id: 'fig-1', props: { outline: { color: 'red' } } }],
nestedColorInventory,
);
expect(nestedColorEquivalent.ok).toBe(false);
if (!nestedColorEquivalent.ok)
expect(nestedColorEquivalent.reason).toMatch(/no effective change/i);
const filterInventory: ElementInventoryItem[] = [
{
id: 'image-1',
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'image',
imageClipShape: 'rect',
style: { filters: { blur: '2px' } },
},
];
const filterEquivalent = mapProposalsToEditIntents(
[{ id: 'image-1', props: { filters: { blur: '2' } } }],
filterInventory,
);
expect(filterEquivalent.ok).toBe(false);
if (!filterEquivalent.ok) expect(filterEquivalent.reason).toMatch(/no effective change/i);
const transparentInventory = [
{ ...inventory[0], id: 'transparent', style: { defaultColor: 'transparent' } },
];
const transparentEquivalent = mapProposalsToEditIntents(
[{ id: 'transparent', props: { defaultColor: 'rgba(255, 0, 0, 0)' } }],
transparentInventory,
);
expect(transparentEquivalent.ok).toBe(false);
if (!transparentEquivalent.ok)
expect(transparentEquivalent.reason).toMatch(/no effective change/i);
});
it('matches shared outline renderer defaults and dotted semantics', () => {
const outlined: ElementInventoryItem[] = [
{
...inventory[1],
style: { outline: { width: 2 } },
},
];
expect(
mapProposalsToEditIntents(
[{ id: 'fig-1', props: { outline: { color: '#000000' } } }],
outlined,
).ok,
).toBe(true);
expect(
mapProposalsToEditIntents(
[{ id: 'fig-1', props: { outline: { color: '#d14424' } } }],
outlined,
).ok,
).toBe(false);
expect(
mapProposalsToEditIntents(
[{ id: 'fig-1', props: { outline: { style: 'dotted' } } }],
outlined,
).ok,
).toBe(true);
});
it('normalizes gradient rotation according to renderer semantics', () => {
const colors = [
{ pos: 0, color: '#ff0000' },
{ pos: 100, color: '#0000ff' },
];
for (const [type, currentRotate, nextRotate] of [
['radial', 0, 90],
['linear', 0, 360],
] as const) {
const item: ElementInventoryItem = {
...inventory[1],
id: `${type}-gradient`,
style: { gradient: { type, rotate: currentRotate, colors } },
};
const result = mapProposalsToEditIntents(
[{ id: item.id, props: { gradient: { type, rotate: nextRotate, colors } } }],
[item],
);
expect(result.ok).toBe(false);
if (!result.ok) expect(result.reason).toMatch(/no effective change/i);
}
});
it('normalizes monochrome gradients as solid paint', () => {
const item: ElementInventoryItem = {
...inventory[1],
id: 'solid-gradient',
style: {
gradient: {
type: 'linear',
rotate: 0,
colors: [
{ pos: 0, color: '#ff0000' },
{ pos: 100, color: 'red' },
],
},
},
};
const result = mapProposalsToEditIntents(
[
{
id: item.id,
props: {
gradient: {
type: 'linear',
rotate: 45,
colors: [
{ pos: 25, color: '#ff0000' },
{ pos: 75, color: '#ff0000' },
],
},
},
},
],
[item],
);
expect(result.ok).toBe(false);
if (!result.ok) expect(result.reason).toMatch(/no effective change/i);
const solid: ElementInventoryItem = {
...inventory[1],
id: 'solid-fill',
style: { fill: '#ff0000' },
};
expect(
mapProposalsToEditIntents(
[
{
id: solid.id,
props: {
gradient: {
type: 'radial',
rotate: 90,
colors: [{ pos: 50, color: 'red' }],
},
},
},
],
[solid],
).ok,
).toBe(false);
const gradient: ElementInventoryItem = {
...inventory[1],
id: 'solid-gradient-to-fill',
style: {
fill: '#ff0000',
gradient: { type: 'linear', rotate: 45, colors: [{ pos: 0, color: 'red' }] },
},
};
expect(
mapProposalsToEditIntents([{ id: gradient.id, props: { fill: '#ff0000' } }], [gradient]).ok,
).toBe(false);
const unpainted: ElementInventoryItem = {
...inventory[1],
id: 'unpainted',
style: {},
};
expect(
mapProposalsToEditIntents(
[
{
id: unpainted.id,
props: {
gradient: {
type: 'linear',
rotate: 30,
colors: [
{ pos: 0, color: 'transparent' },
{ pos: 100, color: 'rgba(255, 0, 0, 0)' },
],
},
},
},
],
[unpainted],
).ok,
).toBe(false);
});
it('normalizes renderer defaults before deciding whether an edit is visible', () => {
const defaultCases: Array<[ElementInventoryItem, Record<string, unknown>]> = [
[textEl({ id: 'opacity-default' }), { opacity: 1 }],
[textEl({ id: 'rotate-default' }), { rotate: 0 }],
[
{ ...inventory[1], id: 'shape-word-default', style: {}, hasShapeText: true },
{ wordSpace: 0 },
],
[
{ ...inventory[1], id: 'shape-paragraph-default', style: {}, hasShapeText: true },
{ paragraphSpace: 5 },
],
[
{ ...inventory[1], id: 'shape-align-default', style: {}, hasShapeText: true },
{ vAlign: 'middle' },
],
[
{
id: 'image-defaults',
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'image',
imageClipShape: 'rect',
style: {},
},
{ flipH: false },
],
[
{
id: 'image-filter-default',
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'image',
imageClipShape: 'rect',
style: {},
},
{ filters: { blur: '0', brightness: '100' } },
],
];
for (const [item, props] of defaultCases) {
const result = mapProposalsToEditIntents([{ id: item.id, props }], [item]);
expect(result.ok, `${item.id} should be a visual no-op`).toBe(false);
if (!result.ok) expect(result.reason).toMatch(/no effective change/i);
}
});
it('refuses computed-equivalent rotation and image radius updates', () => {
const rotated = [{ ...inventory[1], rotate: 360 }];
expect(mapProposalsToEditIntents([{ id: 'fig-1', props: { rotate: 0 } }], rotated).ok).toBe(
false,
);
const roundedImage: ElementInventoryItem[] = [
{
id: 'rounded-image',
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'image',
imageClipShape: 'roundRect',
style: { radius: 100 },
},
];
expect(
mapProposalsToEditIntents([{ id: 'rounded-image', props: { radius: 500 } }], roundedImage).ok,
).toBe(false);
});
it('refuses transparent chrome that has no visible effect', () => {
const image: ElementInventoryItem = {
id: 'transparent-image',
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'image',
imageClipShape: 'rect',
style: {},
};
for (const props of [
{ colorMask: 'transparent' },
{ outline: { width: 2, color: 'transparent' } },
{ shadow: { h: 1, v: 1, blur: 2, color: 'transparent' } },
]) {
expect(mapProposalsToEditIntents([{ id: image.id, props }], [image]).ok).toBe(false);
}
expect(
mapProposalsToEditIntents([{ id: 'title-1', props: { fill: 'transparent' } }], inventory).ok,
).toBe(false);
});
it('refuses mutually exclusive shape paint props in one update', () => {
expect(
mapProposalsToEditIntents(
[
{
id: 'fig-1',
props: {
fill: '#ff0000',
gradient: {
type: 'linear',
rotate: 0,
colors: [
{ pos: 0, color: '#ff0000' },
{ pos: 100, color: '#0000ff' },
],
},
},
},
],
inventory,
).ok,
).toBe(false);
});
it('refuses latex geometry that renders inconsistently across surfaces', () => {
const [latex] = buildElementInventory([
{
id: 'latex-size',
type: 'latex',
left: 0,
top: 0,
width: 100,
height: 50,
rotate: 0,
latex: 'x',
html: '<span>x</span>',
color: '#000000',
} as PPTElement,
]);
expect(mapProposalsToEditIntents([{ id: latex.id, props: { width: 200 } }], [latex]).ok).toBe(
false,
);
const [svgLatex] = buildElementInventory([
{
id: 'latex-svg-size',
type: 'latex',
left: 0,
top: 0,
width: 100,
height: 50,
rotate: 0,
latex: 'x',
path: 'M0 0 L10 10',
viewBox: [10, 10],
color: '#000000',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: svgLatex.id, props: { width: 200 } }], [svgLatex]).ok,
).toBe(true);
});
it('refuses shape text chrome when the shape has no visible label', () => {
const [unlabelled] = buildElementInventory([
{
id: 'plain-shape',
type: 'shape',
left: 0,
top: 0,
width: 100,
height: 80,
rotate: 0,
viewBox: [100, 80],
path: 'M0 0',
fixedRatio: false,
fill: '#ffffff',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: unlabelled.id, props: { wordSpace: 2 } }], [unlabelled]).ok,
).toBe(false);
});
it('refuses glyph-only edits for empty or explicitly hidden text', () => {
for (const content of [
'<p></p>',
'<span style="display:none">hidden</span>',
'<p title="a > b"></p>',
'<span style="visibility:hidden!important;visibility:visible">hidden</span>',
'<span style="font-size:0!important;font-size:12px">hidden</span>',
]) {
const [text] = buildElementInventory([
{
id: `empty-${content.length}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: text.id, props: { defaultColor: '#ff0000' } }], [text]).ok,
).toBe(false);
expect(mapProposalsToEditIntents([{ id: text.id, props: { opacity: 0.5 } }], [text]).ok).toBe(
false,
);
}
});
it('keeps visible text editable when only a descendant is hidden or translucent', () => {
for (const content of [
'<p>visible <span style="opacity:0.5">dim</span><span style="display:none">hidden</span></p>',
'<span style="visibility:hidden">hidden <b style="visibility:visible">shown</b></span>',
'<span style="font-size:0">hidden <b style="font-size:12px">shown</b></span>',
'<span style="opacity:0;opacity:1">shown</span>',
'<span style="display:none;display:block">shown</span>',
'<span class="hidden" style="display:block">shown</span>',
'<span style="visibility:hidden"><b style="visibility:initial">shown</b></span>',
'<span style="font-size:0"><b style="font-size:initial">shown</b></span>',
'<span style="color:transparent"><b style="color:initial">shown</b></span>',
'<span style="-webkit-text-fill-color:transparent"><b style="-webkit-text-fill-color:initial;color:red">shown</b></span>',
]) {
const [text] = buildElementInventory([
{
id: `partly-visible-${content.length}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
const props = content.includes('color:') ? { wordSpace: 4 } : { defaultColor: '#ff0000' };
expect(mapProposalsToEditIntents([{ id: text.id, props }], [text]).ok).toBe(true);
}
});
it('refuses glyph-only edits when text paint is fully transparent', () => {
for (const content of [
'<span style="color:transparent">x</span>',
'<span style="-webkit-text-fill-color:rgba(255, 0, 0, 0)">x</span>',
'<span style="color:rgba(0, 0, 0, 0%)">x</span>',
'<span style="color:rgb(255 0 0 / 0)">x</span>',
'<span style="color:hsl(0 100% 50% / 0%)">x</span>',
'<span style="color:transparent!important;color:red">x</span>',
'<span style="color:transparent"><b style="text-shadow:0 0 2px">x</b></span>',
]) {
const [text] = buildElementInventory([
{
id: `transparent-${content.length}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(mapProposalsToEditIntents([{ id: text.id, props: { wordSpace: 4 } }], [text]).ok).toBe(
false,
);
}
});
it('keeps transparent glyphs editable when a visible text shadow paints them', () => {
const [text] = buildElementInventory([
{
id: 'shadow-glyph',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '<span style="color:transparent;text-shadow:0 0 2px red">x</span>',
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(mapProposalsToEditIntents([{ id: text.id, props: { wordSpace: 4 } }], [text]).ok).toBe(
true,
);
});
it('applies CSS-wide resets and inherited text-shadow using the child currentColor', () => {
const [resetVisible] = buildElementInventory([
{
id: 'all-reset-visible',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '<span style="visibility:hidden"><b style="all:initial">x</b></span>',
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(resetVisible.hasTextGlyphs).toBe(true);
for (const content of [
'<span style="color:red;-webkit-text-fill-color:transparent;text-shadow:initial">x</span>',
'<span style="color:red;text-shadow:0 0 2px"><b style="color:transparent">x</b></span>',
]) {
const [hidden] = buildElementInventory([
{
id: `hidden-shadow-${content.length}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(hidden.hasTextGlyphs).toBe(false);
}
const [multiShadow] = buildElementInventory([
{
id: 'multi-shadow-visible',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content:
'<span style="color:red;-webkit-text-fill-color:transparent;text-shadow:0 0 transparent,1px 1px currentColor">x</span>',
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(multiShadow.hasTextGlyphs).toBe(true);
for (const content of [
'<span style="visibility:hidden"><b style="all:revert">x</b></span>',
'<span style="visibility:hidden"><b style="all:red">x</b></span>',
'<span class="invisible" style="visibility:revert-layer">x</span>',
]) {
const [hidden] = buildElementInventory([
{
id: `revert-hidden-${content.length}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(hidden.hasTextGlyphs).toBe(false);
}
const [invalidAll] = buildElementInventory([
{
id: 'invalid-all-does-not-override',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '<span style="all:red">x</span>',
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: invalidAll.id, props: { wordSpace: 4 } }], [invalidAll]).ok,
).toBe(true);
const [revertLayerColor] = buildElementInventory([
{
id: 'revert-layer-color',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '<span style="color:revert-layer">x</span>',
defaultColor: '#0000ff',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: revertLayerColor.id, props: { defaultColor: '#ff0000' } }],
[revertLayerColor],
).ok,
).toBe(true);
});
it('ignores invalid later CSS declarations instead of replacing valid winners', () => {
for (const content of [
'<span style="visibility:hidden;visibility:nope">x</span>',
'<span style="display:none;display:nope">x</span>',
'<span style="opacity:0;opacity:nope">x</span>',
'<span style="color:transparent;color:nope">x</span>',
'<span style="font-size:0;font-size:nope">x</span>',
]) {
const [hidden] = buildElementInventory([
{
id: `invalid-later-${content.length}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(hidden.hasTextGlyphs).toBe(false);
}
});
it('includes font shorthand in computed font-size visibility', () => {
for (const [content, visible] of [
['<span style="font:0 Arial">x</span>', false],
['<span style="font-size:0"><b style="font:initial">x</b></span>', true],
['<span style="font:0 Arial;font:caption">x</span>', true],
] as const) {
const [text] = buildElementInventory([
{
id: `font-shorthand-${content.length}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(text.hasTextGlyphs).toBe(visible);
}
const [systemFont] = buildElementInventory([
{
id: 'system-font-override',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '<span style="font:caption">x</span>',
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: systemFont.id, props: { defaultFontName: 'Inter' } }],
[systemFont],
).ok,
).toBe(false);
});
it('fails closed for computed CSS values it cannot fully evaluate', () => {
for (const content of [
'<span style="opacity:calc(0)">x</span>',
'<span style="font-size:calc(0px)">x</span>',
'<span style="color:color-mix(in srgb, transparent 100%, red 0%)">x</span>',
]) {
const [hidden] = buildElementInventory([
{
id: `computed-hidden-${content.length}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(hidden.hasTextGlyphs).toBe(false);
}
const [visibleDisplay] = buildElementInventory([
{
id: 'multi-keyword-display',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '<span style="display:none;display:inline flex">x</span>',
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(visibleDisplay.hasTextGlyphs).toBe(true);
for (const display of ['block list-item', 'inline list-item', 'flow-root list-item']) {
const [listItem] = buildElementInventory([
{
id: `list-item-${display}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: `<span style="display:none;display:${display}">x</span>`,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(listItem.hasTextGlyphs).toBe(true);
}
for (const display of ['flex list-item', 'table list-item', 'ruby list-item']) {
const [invalidListItem] = buildElementInventory([
{
id: `invalid-list-item-${display}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: `<span style="display:none;display:${display}">x</span>`,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(invalidListItem.hasTextGlyphs).toBe(false);
}
});
it('recognizes unitless-zero text shadows as inline overrides', () => {
const [text] = buildElementInventory([
{
id: 'unitless-shadow',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '<span style="text-shadow:0 0">x</span>',
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: text.id, props: { shadow: { h: 1, v: 1, blur: 2, color: 'red' } } }],
[text],
).ok,
).toBe(false);
const [invalid] = buildElementInventory([
{
id: 'invalid-one-length-shadow',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '<span style="text-shadow:0">x</span>',
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: invalid.id, props: { shadow: { h: 1, v: 1, blur: 2, color: 'red' } } }],
[invalid],
).ok,
).toBe(true);
const [tooManyColors] = buildElementInventory([
{
id: 'invalid-two-color-shadow',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '<span style="text-shadow:red blue 0 0">x</span>',
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: tooManyColors.id, props: { shadow: { h: 1, v: 1, blur: 2, color: 'red' } } }],
[tooManyColors],
).ok,
).toBe(true);
});
it('fails closed for unresolved or modern text-shadow colors', () => {
for (const shadow of [
'var(--missing)',
'var(--missing, none)',
'0 0 color-mix(in srgb, transparent 100%, red 0%)',
]) {
const [text] = buildElementInventory([
{
id: `unresolved-shadow-${shadow.length}`,
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: `<span style="-webkit-text-fill-color:transparent;text-shadow:${shadow}">x</span>`,
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(text.hasTextGlyphs).toBe(false);
}
});
it('includes element-level text paint in glyph visibility and composed shadow edits', () => {
const base = {
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: 'x',
defaultFontName: 'Arial',
};
const [transparent] = buildElementInventory([
{ ...base, id: 'root-transparent', defaultColor: 'transparent' } as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: transparent.id, props: { wordSpace: 4 } }], [transparent])
.ok,
).toBe(false);
expect(
mapProposalsToEditIntents(
[
{
id: transparent.id,
props: { shadow: { h: 1, v: 1, blur: 2, color: '#ff0000' } },
},
],
[transparent],
).ok,
).toBe(true);
const [shadowVisible] = buildElementInventory([
{
...base,
id: 'root-shadow-visible',
content: '<span style="-webkit-text-fill-color:transparent">x</span>',
defaultColor: '#ff0000',
shadow: { h: 1, v: 1, blur: 2, color: '#ff0000' },
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: shadowVisible.id, props: { wordSpace: 4 } }],
[shadowVisible],
).ok,
).toBe(true);
for (const content of [
'<span style="-webkit-text-fill-color:initial">x</span>',
'<span style="-webkit-text-fill-color:currentColor">x</span>',
'<span style="-webkit-text-fill-color:transparent;text-shadow:0 0 2px currentColor">x</span>',
]) {
const [colorDependent] = buildElementInventory([
{
...base,
id: `root-color-dependent-${content.length}`,
content,
defaultColor: 'transparent',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: colorDependent.id, props: { defaultColor: '#ff0000' } }],
[colorDependent],
).ok,
).toBe(true);
}
const [shadowDependent] = buildElementInventory([
{
...base,
id: 'root-shadow-dependent',
content: '<span style="text-shadow:inherit">x</span>',
defaultColor: 'transparent',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[
{
id: shadowDependent.id,
props: { shadow: { h: 1, v: 1, blur: 2, color: '#ff0000' } },
},
],
[shadowDependent],
).ok,
).toBe(true);
});
it('treats element opacity zero as unpainted while allowing a composed reveal', () => {
const [text] = buildElementInventory([
{
id: 'root-opacity-zero',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
opacity: 0,
content: 'x',
defaultColor: '#000000',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(mapProposalsToEditIntents([{ id: text.id, props: { wordSpace: 4 } }], [text]).ok).toBe(
false,
);
expect(
mapProposalsToEditIntents([{ id: text.id, props: { opacity: 1, wordSpace: 4 } }], [text]).ok,
).toBe(true);
const [transparent] = buildElementInventory([
{
id: 'root-opacity-and-color-zero',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
opacity: 0,
content: 'x',
defaultColor: 'transparent',
defaultFontName: 'Arial',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: transparent.id, props: { shadow: { h: 1, v: 1, blur: 2, color: 'red' } } }],
[transparent],
).ok,
).toBe(false);
for (const paint of [
{ defaultColor: '#ff0000' },
{ shadow: { h: 1, v: 1, blur: 2, color: '#ff0000' } },
]) {
expect(
mapProposalsToEditIntents(
[{ id: transparent.id, props: { ...paint, opacity: 1, wordSpace: 4 } }],
[transparent],
).ok,
).toBe(true);
}
});
it('evaluates empty text box paint from the composed proposal', () => {
const base = {
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
opacity: 0,
content: '',
defaultColor: 'transparent',
defaultFontName: 'Arial',
};
const [empty] = buildElementInventory([{ ...base, id: 'empty-box' } as PPTElement]);
expect(
mapProposalsToEditIntents([{ id: empty.id, props: { opacity: 1, fill: '#ff0000' } }], [empty])
.ok,
).toBe(true);
expect(
mapProposalsToEditIntents([{ id: empty.id, props: { fill: '#ff0000' } }], [empty]).ok,
).toBe(false);
for (const element of [
{ ...base, id: 'cancel-fill', fill: '#ff0000' },
{
...base,
id: 'cancel-outline',
outline: { width: 2, color: '#ff0000', style: 'solid' },
},
]) {
const [item] = buildElementInventory([element as PPTElement]);
const paintRemoval =
element.id === 'cancel-fill' ? { fill: 'transparent' } : { outline: { width: 0 } };
expect(
mapProposalsToEditIntents([{ id: item.id, props: { opacity: 1, ...paintRemoval } }], [item])
.ok,
).toBe(false);
}
for (const paintRemoval of [{ opacity: 0 }, { fill: 'transparent' }]) {
const [visible] = buildElementInventory([
{
...base,
id: `remove-box-${Object.keys(paintRemoval)[0]}`,
opacity: 1,
fill: 'red',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: visible.id, props: paintRemoval }], [visible]).ok,
).toBe(true);
}
const [glyphReveal] = buildElementInventory([
{
...base,
id: 'glyph-reveal-outline-no-width',
opacity: 1,
content: 'x',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[
{
id: glyphReveal.id,
props: { defaultColor: 'red', outline: { color: 'red' } },
},
],
[glyphReveal],
).ok,
).toBe(false);
});
it('rejects mutually cancelling paint changes on shapes and invisible line edits', () => {
const shape = {
id: 'cancel-shape-paint',
type: 'shape',
left: 0,
top: 0,
width: 100,
height: 80,
rotate: 0,
viewBox: [100, 80],
path: 'M0 0 H100 V80 H0 Z',
fixedRatio: false,
fill: 'red',
opacity: 0,
} as PPTElement;
const [shapeItem] = buildElementInventory([shape]);
expect(
mapProposalsToEditIntents(
[{ id: shape.id, props: { opacity: 1, fill: 'transparent' } }],
[shapeItem],
).ok,
).toBe(false);
expect(
mapProposalsToEditIntents([{ id: shape.id, props: { opacity: 1, fill: 'red' } }], [shapeItem])
.ok,
).toBe(true);
const line = {
id: 'transparent-line',
type: 'line',
left: 0,
top: 0,
width: 2,
start: [0, 0],
end: [100, 0],
style: 'solid',
color: 'transparent',
points: ['', ''],
} as unknown as PPTElement;
const [lineItem] = buildElementInventory([line]);
expect(mapProposalsToEditIntents([{ id: line.id, props: { left: 10 } }], [lineItem]).ok).toBe(
false,
);
expect(
mapProposalsToEditIntents([{ id: line.id, props: { color: 'red' } }], [lineItem]).ok,
).toBe(true);
for (const invisibleLine of [
{ ...line, id: 'zero-width-line', width: 0, color: 'red' },
{
...line,
id: 'zero-length-line',
width: 2,
color: 'red',
start: [0, 0],
end: [0, 0],
},
]) {
const [item] = buildElementInventory([invisibleLine as PPTElement]);
expect(mapProposalsToEditIntents([{ id: item.id, props: { left: 10 } }], [item]).ok).toBe(
false,
);
}
for (const control of [
{ broken: [50, 50] },
{ broken2: [50, 50] },
{ curve: [50, 50] },
{
cubic: [
[25, 50],
[75, 50],
],
},
]) {
const controlledLine = {
...line,
id: `controlled-line-${Object.keys(control)[0]}`,
color: 'red',
start: [0, 0],
end: [0, 0],
...control,
} as unknown as PPTElement;
const [item] = buildElementInventory([controlledLine]);
expect(mapProposalsToEditIntents([{ id: item.id, props: { left: 10 } }], [item]).ok).toBe(
true,
);
}
const rendererZeroBroken2 = {
...line,
id: 'renderer-zero-broken2',
color: 'red',
start: [10, 20],
end: [10, 20],
broken2: [50, 20],
} as unknown as PPTElement;
const [rendererZeroBroken2Item] = buildElementInventory([rendererZeroBroken2]);
expect(
mapProposalsToEditIntents(
[{ id: rendererZeroBroken2.id, props: { left: 10 } }],
[rendererZeroBroken2Item],
).ok,
).toBe(false);
const priorityDegenerateLine = {
...line,
id: 'priority-degenerate-line',
color: 'red',
start: [0, 0],
end: [0, 0],
broken: [0, 0],
broken2: [50, 50],
curve: [50, 50],
cubic: [
[25, 50],
[75, 50],
],
} as unknown as PPTElement;
const [priorityDegenerateItem] = buildElementInventory([priorityDegenerateLine]);
expect(
mapProposalsToEditIntents(
[{ id: priorityDegenerateLine.id, props: { left: 10 } }],
[priorityDegenerateItem],
).ok,
).toBe(false);
const emptyShape = { ...shape, id: 'empty-fill-shape', fill: '', opacity: 1 } as PPTElement;
const [emptyShapeItem] = buildElementInventory([emptyShape]);
expect(
mapProposalsToEditIntents([{ id: emptyShape.id, props: { left: 20 } }], [emptyShapeItem]).ok,
).toBe(false);
expect(
mapProposalsToEditIntents(
[{ id: emptyShape.id, props: { fill: '#ff0000' } }],
[emptyShapeItem],
).ok,
).toBe(true);
const [emptyText] = buildElementInventory([
{
id: 'empty-fill-text',
type: 'text',
left: 0,
top: 0,
width: 100,
height: 40,
rotate: 0,
content: '',
defaultColor: 'transparent',
defaultFontName: 'Arial',
fill: '',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: emptyText.id, props: { left: 20 } }], [emptyText]).ok,
).toBe(false);
});
it('does not treat an empty shape pattern as visible paint', () => {
const [shape] = buildElementInventory([
{
id: 'empty-pattern',
type: 'shape',
left: 0,
top: 0,
width: 100,
height: 80,
rotate: 0,
viewBox: [100, 80],
path: 'M0 0',
fixedRatio: false,
fill: '#ff0000',
pattern: '',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: shape.id, props: { fill: '#ff0000' } }], [shape]).ok,
).toBe(false);
});
it('allows composed color reveals for transparent shape labels and latex HTML', () => {
const shape = {
id: 'transparent-shape-label',
type: 'shape',
left: 0,
top: 0,
width: 100,
height: 80,
rotate: 0,
viewBox: [100, 80],
path: 'M0 0 H100 V80 H0 Z',
fixedRatio: false,
fill: 'transparent',
text: {
content: 'Label',
defaultColor: 'transparent',
defaultFontName: 'Arial',
},
} as PPTElement;
const [shapeItem] = buildElementInventory([shape]);
expect(
mapProposalsToEditIntents([{ id: shape.id, props: { defaultColor: '#ff0000' } }], [shapeItem])
.ok,
).toBe(true);
const latex = {
id: 'transparent-latex-html',
type: 'latex',
left: 0,
top: 0,
width: 100,
height: 50,
rotate: 0,
latex: 'x',
html: '<span>x</span>',
color: 'transparent',
} as PPTElement;
const [latexItem] = buildElementInventory([latex]);
expect(
mapProposalsToEditIntents([{ id: latex.id, props: { color: '#ff0000' } }], [latexItem]).ok,
).toBe(true);
});
it('fails closed when inline KaTeX color would override latex color', () => {
const [latex] = buildElementInventory([
{
id: 'latex-colored',
type: 'latex',
left: 0,
top: 0,
width: 100,
height: 50,
rotate: 0,
latex: String.raw`\color{blue}{x}`,
html: '<span style="color: blue">x</span>',
color: '#000000',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: latex.id, props: { color: '#ff0000' } }], [latex]).ok,
).toBe(false);
});
it('refuses color edits for latex elements with no renderable branch', () => {
const [latex] = buildElementInventory([
{
id: 'latex-empty',
type: 'latex',
left: 0,
top: 0,
width: 100,
height: 50,
rotate: 0,
latex: 'x',
color: '#000000',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: latex.id, props: { color: '#ff0000' } }], [latex]).ok,
).toBe(false);
expect(mapProposalsToEditIntents([{ id: latex.id, props: { left: 20 } }], [latex]).ok).toBe(
false,
);
});
it('uses renderer-specific table and chart capability constraints', () => {
const tableBase = {
id: 'table-1',
type: 'table',
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
data: [[{ id: 'cell-1', text: 'x', borders: { top: { color: '#000000', width: 1 } } }]],
colWidths: [1],
cellMinHeight: 36,
outline: { color: '#000000' },
} as unknown as PPTElement;
const [borderedTable] = buildElementInventory([tableBase]);
expect(
mapProposalsToEditIntents(
[{ id: borderedTable.id, props: { outline: { color: '#ff0000' } } }],
[borderedTable],
).ok,
).toBe(false);
const [plainTable] = buildElementInventory([
{ ...tableBase, id: 'plain-table', data: [[{ id: 'cell-1', text: 'x' }]] } as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: plainTable.id, props: { outline: { color: '#ff0000' } } }],
[plainTable],
).ok,
).toBe(true);
expect(
mapProposalsToEditIntents(
[{ id: plainTable.id, props: { outline: { style: 'dotted' } } }],
[plainTable],
).ok,
).toBe(false);
for (const chartType of ['pie', 'ring']) {
const [chart] = buildElementInventory([
{
id: `chart-${chartType}`,
type: 'chart',
chartType,
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
data: { labels: ['A'], legends: ['S'], series: [[1]] },
themeColors: ['#000000'],
lineColor: '#000000',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: chart.id, props: { lineColor: '#ff0000' } }], [chart]).ok,
).toBe(false);
}
const [emptyChart] = buildElementInventory([
{
id: 'empty-chart',
type: 'chart',
chartType: 'bar',
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
data: { labels: [], legends: [], series: [] },
themeColors: ['#000000'],
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: emptyChart.id, props: { themeColors: ['#ff0000'] } }],
[emptyChart],
).ok,
).toBe(false);
const [singleSlice] = buildElementInventory([
{
id: 'single-slice',
type: 'chart',
chartType: 'pie',
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
data: { labels: ['A'], legends: ['S'], series: [[1]] },
themeColors: ['#ff0000', '#0000ff'],
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: singleSlice.id, props: { themeColors: ['#ff0000', '#00ff00'] } }],
[singleSlice],
).ok,
).toBe(false);
const [scatter] = buildElementInventory([
{
id: 'scatter',
type: 'chart',
chartType: 'scatter',
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
data: { labels: ['A'], legends: ['S'], series: [[1], [2]] },
themeColors: ['#ff0000', '#0000ff'],
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: scatter.id, props: { themeColors: ['#ff0000', '#00ff00'] } }],
[scatter],
).ok,
).toBe(false);
const [singleSeriesScatter] = buildElementInventory([
{
id: 'single-series-scatter',
type: 'chart',
chartType: 'scatter',
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
data: { labels: ['A'], legends: ['S'], series: [[1, 2]] },
themeColors: ['#ff0000'],
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: singleSeriesScatter.id, props: { themeColors: ['#00ff00'] } }],
[singleSeriesScatter],
).ok,
).toBe(true);
const [emptySeriesChart] = buildElementInventory([
{
id: 'empty-series',
type: 'chart',
chartType: 'bar',
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
data: { labels: [], legends: ['S'], series: [[]] },
themeColors: ['#ff0000'],
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[{ id: emptySeriesChart.id, props: { themeColors: ['#00ff00'] } }],
[emptySeriesChart],
).ok,
).toBe(false);
const [emptyPie] = buildElementInventory([
{
id: 'empty-pie',
type: 'chart',
chartType: 'pie',
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
data: { labels: [], legends: [], series: [[]] },
themeColors: ['#ff0000'],
textColor: '#000000',
} as PPTElement,
]);
expect(
mapProposalsToEditIntents([{ id: emptyPie.id, props: { textColor: '#ff0000' } }], [emptyPie])
.ok,
).toBe(false);
const [threeSeries] = buildElementInventory([
{
id: 'three-series',
type: 'chart',
chartType: 'bar',
left: 0,
top: 0,
width: 200,
height: 100,
rotate: 0,
data: { labels: ['A'], legends: ['A', 'B', 'C'], series: [[1], [2], [3]] },
themeColors: ['#ff0000'],
} as PPTElement,
]);
expect(
mapProposalsToEditIntents(
[
{
id: threeSeries.id,
props: { themeColors: ['rgb(255, 0, 0)', 'rgb(255, 0, 204)', 'rgb(255, 0, 153)'] },
},
],
[threeSeries],
).ok,
).toBe(false);
});
it('fails closed for CSS shorthands and inherited override aliases', () => {
const rawText = (id: string, content: string) =>
({
id,
type: 'text',
left: 0,
top: 0,
width: 300,
height: 60,
rotate: 0,
content,
defaultFontName: 'Arial',
defaultColor: '#111111',
}) as PPTElement;
const cases: Array<[string, string, Record<string, unknown>]> = [
[
'font-family',
'<span style="font: italic 16px/1.8 Calibri">x</span>',
{ defaultFontName: 'Inter' },
],
[
'font-line-height',
'<span style="font: italic 16px/1.8 Calibri">x</span>',
{ lineHeight: 1.5 },
],
['margin', '<p style="margin: 0 0 12pt">x</p>', { paragraphSpace: 5 }],
[
'vendor-color',
'<span style="-webkit-text-fill-color: red">x</span>',
{ defaultColor: '#0000ff' },
],
['writing-mode', '<span style="writing-mode: horizontal-tb">x</span>', { vertical: true }],
[
'text-shadow',
'<span style="text-shadow: 1px 1px #000">x</span>',
{ shadow: { h: 2, v: 2, blur: 2, color: '#000000' } },
],
['all', '<span style="all: initial">x</span>', { wordSpace: 4 }],
];
for (const [id, content, props] of cases) {
const item = buildElementInventory([rawText(id, content)]);
const result = mapProposalsToEditIntents([{ id, props }], item);
expect(result.ok, id).toBe(false);
if (!result.ok) expect(result.reason).toMatch(/inline/i);
}
});
it('refuses invalid CSS colors across top-level and nested color contracts', () => {
expect(
mapProposalsToEditIntents(
[{ id: 'title-1', props: { defaultColor: 'definitely-not-a-color' } }],
inventory,
).ok,
).toBe(false);
expect(
mapProposalsToEditIntents(
[{ id: 'title-1', props: { outline: { width: 2, color: 'not-a-color' } } }],
inventory,
).ok,
).toBe(false);
expect(
mapProposalsToEditIntents(
[
{
id: 'fig-1',
props: {
gradient: {
type: 'linear',
colors: [
{ pos: 0, color: '#000000' },
{ pos: 100, color: 'invalid-gradient-color' },
],
rotate: 0,
},
},
},
],
inventory,
).ok,
).toBe(false);
});
it('refuses empty or visually ineffective nested style patches', () => {
const image: ElementInventoryItem = {
id: 'img-1',
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'image',
style: {},
};
for (const props of [{ outline: {} }, { outline: { color: '#ff0000' } }, { filters: {} }]) {
expect(mapProposalsToEditIntents([{ id: 'img-1', props }], [image]).ok).toBe(false);
}
expect(
mapProposalsToEditIntents(
[{ id: 'img-1', props: { outline: { width: 2, color: '#ff0000' } } }],
[image],
).ok,
).toBe(true);
});
it('rejects image radius when the active clip cannot render it', () => {
const image = (shape: string, radius?: number): ElementInventoryItem => ({
id: `img-${shape}`,
type: 'image',
left: 0,
top: 0,
width: 100,
height: 100,
rotate: 0,
lock: false,
label: 'image',
style: radius === undefined ? {} : { radius },
imageClipShape: shape,
});
expect(
mapProposalsToEditIntents([{ id: 'img-ellipse', props: { radius: 20 } }], [image('ellipse')])
.ok,
).toBe(false);
expect(
mapProposalsToEditIntents(
[{ id: 'img-roundRect', props: { radius: 0 } }],
[image('roundRect')],
).ok,
).toBe(true);
expect(
mapProposalsToEditIntents([{ id: 'img-rect', props: { radius: 0 } }], [image('rect')]).ok,
).toBe(false);
});
it('refuses proposals that do not change effective element state', () => {
expect(
mapProposalsToEditIntents([{ id: 'title-1', props: { defaultColor: '#333333' } }], inventory)
.ok,
).toBe(false);
});
});