fix(pen-core/variables): resolveNodeForCanvas — fontWeight + empty-vars fallback

GAP-1: add 'fontWeight' to the text-node key list in resolveNodeForCanvas so
$type-*-weight refs resolve to a number before reaching the renderer.

GAP-2: replace the early-exit `if (!variables || Object.keys(variables).length === 0)
return node` with `if (!variables) variables = {}` so DEFAULT_PALETTE_FALLBACK
fires even when the document has an empty variables map (un-seeded v1 docs).

Adds 2 new tests to fallback-equivalence.test.ts covering both gaps.
This commit is contained in:
Fini 2026-04-29 09:50:32 +08:00
parent aa2658fcd1
commit dc656be409
2 changed files with 46 additions and 3 deletions

View file

@ -1,5 +1,7 @@
import { describe, it, expect } from 'vitest';
import { resolveVariableRef } from '../variables/resolve.js';
import { resolveVariableRef, resolveNodeForCanvas } from '../variables/resolve.js';
import { createEmptyDocument } from '../tree-utils.js';
import type { PenNode } from '@zseven-w/pen-types';
describe('resolver-side fallback (un-seeded doc, P1.6)', () => {
const emptyVars = {};
@ -75,3 +77,44 @@ describe('resolver-side fallback (un-seeded doc, P1.6)', () => {
});
});
});
describe('GAP fixes — resolveNodeForCanvas covers all numeric refs', () => {
it('resolves fontWeight ref via fallback', () => {
const doc = createEmptyDocument();
const textNode: PenNode = {
id: 'h',
type: 'text',
name: 'H1',
content: 'Hello',
fontSize: '$type-h1-size' as unknown as number,
fontWeight: '$type-h1-weight' as unknown as number,
fill: [{ type: 'solid', color: '$color-text-primary' }],
};
const resolved = resolveNodeForCanvas(
textNode,
doc.variables ?? {},
undefined,
) as unknown as Record<string, unknown>;
expect(typeof resolved.fontWeight).toBe('number');
expect(resolved.fontWeight).toBe(600);
});
it('resolves refs in empty-vars doc (fallback fires past early-exit)', () => {
// createEmptyDocument() returns doc.variables = undefined; passing undefined as vars
// exercises the GAP-2 fix (the early-exit was `if (!variables || Object.keys...)`).
const textNode: PenNode = {
id: 't',
type: 'text',
name: 'T',
content: 'Hi',
fill: [{ type: 'solid', color: '$color-text-primary' }],
};
const resolved = resolveNodeForCanvas(
textNode,
undefined as unknown as Record<string, import('@zseven-w/pen-types').VariableDefinition>,
undefined,
) as unknown as Record<string, unknown>;
const fill = (resolved.fill as Array<{ type: string; color: string }>)[0];
expect(fill.color).toBe('#0F172A');
});
});

View file

@ -214,7 +214,7 @@ function resolveEffectsForCanvas(
* Returns the same object reference when no variables are present.
*/
export function resolveNodeForCanvas(node: PenNode, variables: Vars, activeTheme?: Theme): PenNode {
if (!variables || Object.keys(variables).length === 0) return node;
if (!variables) variables = {};
let changed = false;
const out: Record<string, unknown> = { ...node };
@ -278,7 +278,7 @@ export function resolveNodeForCanvas(node: PenNode, variables: Vars, activeTheme
// since both paths use `node.fontSize ?? 16` (not type-guarded for strings).
if (node.type === 'text') {
const n = node as unknown as Record<string, unknown>;
for (const key of ['fontSize', 'lineHeight', 'letterSpacing'] as const) {
for (const key of ['fontSize', 'fontWeight', 'lineHeight', 'letterSpacing'] as const) {
if (typeof n[key] === 'string' && isVariableRef(n[key] as string)) {
const resolved = resolveNumericRef(n[key] as string, variables, activeTheme);
if (resolved !== undefined) {