Merge pull request #605 from phoenixweiss/codex/fig-component-property-guid-stability
test(fig): compare instance swap targets semantically
This commit is contained in:
commit
bdccf2dfe7
|
|
@ -18,6 +18,8 @@ import {
|
|||
type FixtureSpec,
|
||||
type CompareOptions,
|
||||
type Verifier,
|
||||
buildComponentPropertyDefinitionIndex,
|
||||
buildNodePathIndex,
|
||||
isColorObj,
|
||||
SCENE_VERIFIERS,
|
||||
RAW_VERIFIERS
|
||||
|
|
@ -230,6 +232,10 @@ function compareSceneProps(
|
|||
bNodes,
|
||||
aGraph,
|
||||
bGraph,
|
||||
aNodePaths: buildNodePathIndex(aNodes),
|
||||
bNodePaths: buildNodePathIndex(bNodes),
|
||||
aComponentPropertyDefinitions: buildComponentPropertyDefinitionIndex(aGraph),
|
||||
bComponentPropertyDefinitions: buildComponentPropertyDefinitionIndex(bGraph),
|
||||
errors,
|
||||
fixture,
|
||||
verifiers,
|
||||
|
|
@ -265,6 +271,10 @@ function compareRawNodeFields(
|
|||
bNodes,
|
||||
aGraph,
|
||||
bGraph,
|
||||
aNodePaths: buildNodePathIndex(aNodes),
|
||||
bNodePaths: buildNodePathIndex(bNodes),
|
||||
aComponentPropertyDefinitions: buildComponentPropertyDefinitionIndex(aGraph),
|
||||
bComponentPropertyDefinitions: buildComponentPropertyDefinitionIndex(bGraph),
|
||||
errors,
|
||||
fixture,
|
||||
verifiers,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { SceneGraph, SceneNode } from '@open-pencil/core'
|
||||
import type { ComponentPropertyDefinition } from '@open-pencil/scene-graph'
|
||||
import type { JSONObject } from '@open-pencil/scene-graph/primitives'
|
||||
|
||||
import { verifyComponentPropDefs, verifyDerivedTextData } from './raw-verifiers/helpers'
|
||||
|
|
@ -33,6 +34,10 @@ export interface VerifierContext {
|
|||
bNodes: Map<string, SceneNode>
|
||||
aGraph: SceneGraph
|
||||
bGraph: SceneGraph
|
||||
aNodePaths: ReadonlyMap<string, string>
|
||||
bNodePaths: ReadonlyMap<string, string>
|
||||
aComponentPropertyDefinitions: ReadonlyMap<string, ComponentPropertyDefinition>
|
||||
bComponentPropertyDefinitions: ReadonlyMap<string, ComponentPropertyDefinition>
|
||||
errors: Mismatch[]
|
||||
fixture: FixtureSpec
|
||||
label: string
|
||||
|
|
@ -60,6 +65,122 @@ export function isColorObj(v: unknown): v is Record<string, number> {
|
|||
)
|
||||
}
|
||||
|
||||
export function buildNodePathIndex(nodes: ReadonlyMap<string, SceneNode>): Map<string, string> {
|
||||
return new Map([...nodes].map(([path, node]) => [node.id, path]))
|
||||
}
|
||||
|
||||
export function buildComponentPropertyDefinitionIndex(
|
||||
graph: SceneGraph
|
||||
): Map<string, ComponentPropertyDefinition> {
|
||||
const definitions = new Map<string, ComponentPropertyDefinition>()
|
||||
for (const node of graph.getAllNodes()) {
|
||||
for (const definition of node.componentPropertyDefinitions) {
|
||||
if (!definitions.has(definition.id)) definitions.set(definition.id, definition)
|
||||
}
|
||||
}
|
||||
return definitions
|
||||
}
|
||||
|
||||
function sameNodeReference(ctx: VerifierContext, a: string, b: string): boolean {
|
||||
const aPath = ctx.aNodePaths.get(a)
|
||||
const bPath = ctx.bNodePaths.get(b)
|
||||
return aPath !== undefined && aPath === bPath
|
||||
}
|
||||
|
||||
function sameNodeReferences(
|
||||
ctx: VerifierContext,
|
||||
a: string[] | undefined,
|
||||
b: string[] | undefined
|
||||
): boolean {
|
||||
if (a === undefined || b === undefined) return a === b
|
||||
return (
|
||||
a.length === b.length &&
|
||||
a.every((value, index) => {
|
||||
const other = b[index]
|
||||
return other !== undefined && sameNodeReference(ctx, value, other)
|
||||
})
|
||||
)
|
||||
}
|
||||
function isComponentPropertyDefinitions(value: unknown): value is ComponentPropertyDefinition[] {
|
||||
if (!Array.isArray(value)) return false
|
||||
return value.every(isComponentPropertyDefinition)
|
||||
}
|
||||
|
||||
function isComponentPropertyDefinition(value: unknown): value is ComponentPropertyDefinition {
|
||||
if (!value || typeof value !== 'object') return false
|
||||
const candidate = value as Partial<ComponentPropertyDefinition>
|
||||
return (
|
||||
typeof candidate.id === 'string' &&
|
||||
typeof candidate.name === 'string' &&
|
||||
(candidate.type === 'VARIANT' ||
|
||||
candidate.type === 'TEXT' ||
|
||||
candidate.type === 'BOOLEAN' ||
|
||||
candidate.type === 'INSTANCE_SWAP') &&
|
||||
typeof candidate.defaultValue === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
function verifyComponentPropertyDefinitions(ctx: VerifierContext): boolean {
|
||||
if (!isComponentPropertyDefinitions(ctx.a) || !isComponentPropertyDefinitions(ctx.b)) {
|
||||
return false
|
||||
}
|
||||
const aDefinitions = ctx.a
|
||||
const bDefinitions = ctx.b
|
||||
if (aDefinitions.length !== bDefinitions.length) return false
|
||||
|
||||
return aDefinitions.every((value, index) => {
|
||||
const definition = value
|
||||
const other = bDefinitions[index]
|
||||
if (!other || definition.type !== other.type || definition.name !== other.name) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { defaultValue, preferredValues, ...rest } = definition
|
||||
const {
|
||||
defaultValue: otherDefaultValue,
|
||||
preferredValues: otherPreferredValues,
|
||||
...otherRest
|
||||
} = other
|
||||
if (JSON.stringify(rest) !== JSON.stringify(otherRest)) return false
|
||||
|
||||
if (definition.type !== 'INSTANCE_SWAP') return defaultValue === otherDefaultValue
|
||||
return (
|
||||
typeof defaultValue === 'string' &&
|
||||
typeof otherDefaultValue === 'string' &&
|
||||
sameNodeReference(ctx, defaultValue, otherDefaultValue) &&
|
||||
sameNodeReferences(ctx, preferredValues, otherPreferredValues)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function isComponentPropertyAssignments(value: unknown): value is ComponentPropertyAssignments {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return false
|
||||
return Object.values(value).every((entry) => typeof entry === 'string')
|
||||
}
|
||||
|
||||
function verifyComponentPropertyAssignments(ctx: VerifierContext): boolean {
|
||||
if (!isComponentPropertyAssignments(ctx.a) || !isComponentPropertyAssignments(ctx.b)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const aAssignments = ctx.a
|
||||
const bAssignments = ctx.b
|
||||
const propertyIds = new Set([...Object.keys(aAssignments), ...Object.keys(bAssignments)])
|
||||
for (const propertyId of propertyIds) {
|
||||
const a = aAssignments[propertyId]
|
||||
const b = bAssignments[propertyId]
|
||||
if (a === b) continue
|
||||
if (typeof a !== 'string' || typeof b !== 'string') return false
|
||||
const aDefinition = ctx.aComponentPropertyDefinitions.get(propertyId)
|
||||
const bDefinition = ctx.bComponentPropertyDefinitions.get(propertyId)
|
||||
if (aDefinition?.type !== 'INSTANCE_SWAP' || bDefinition?.type !== 'INSTANCE_SWAP') {
|
||||
return false
|
||||
}
|
||||
if (!sameNodeReference(ctx, a, b)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const SCENE_VERIFIERS = new Map<string, Verifier>([
|
||||
[
|
||||
'pluginData',
|
||||
|
|
@ -93,7 +214,9 @@ export const SCENE_VERIFIERS = new Map<string, Verifier>([
|
|||
if (!ctx.key.includes('componentPropertyDefinitions')) return false
|
||||
return ctx.a === 'VARIANT' && ctx.b === 'TEXT'
|
||||
}
|
||||
]
|
||||
],
|
||||
['componentPropertyDefinitions', verifyComponentPropertyDefinitions],
|
||||
['componentPropertyAssignments', verifyComponentPropertyAssignments]
|
||||
])
|
||||
|
||||
function verifyAEntries(
|
||||
|
|
|
|||
Loading…
Reference in a new issue