@itwin/core-backend
Version:
iTwin.js backend components
475 lines • 30 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { DbResult } from "@itwin/core-bentley";
import { IModelHost, SnapshotDb } from "../../core-backend";
import { ClassModifier, ClassType, ECClassModifier, Mixin, SchemaItemType } from "@itwin/ecschema-metadata";
import { assert, expect } from "chai";
import * as path from "path";
import { KnownTestLocations } from "../KnownTestLocations";
import { TestUtils } from "../TestUtils";
/** Schemas excluded from the SchemaView binary blob by the C++ writer.
* Must stay in sync with `IsExcludedSchema()` in `SchemaViewWriter.cpp`.
*/
const excludedSchemaViewSchemas = new Set([
"BisCustomAttributes",
"CoreCustomAttributes",
"ECDbFileInfo",
"ECDbMap",
"ECDbSchemaPolicies",
"ECDbSystem",
"ECv3ConversionAttributes",
"EditorCustomAttributes",
"Formats",
"SchemaLocalizationCustomAttributes",
"SchemaUpgradeCustomAttributes",
"Units",
]);
/**
* Cross-validation test: walks ecschema-metadata and SchemaView in parallel,
* asserting that every schema, class, property, enumeration, KoQ, and category returns
* identical information from both layers.
*
* This test is iModel-agnostic - it works for any test bim. No hard-coded expected values.
* To validate against a larger real iModel, just add its path to the `iModelPaths` array.
*/
describe("SchemaView cross-validation", () => {
before(async () => {
if (!IModelHost.isValid)
await TestUtils.startBackend();
});
/** All test iModels to validate. Add paths here to test against larger/real iModels. */
const iModelPaths = [
path.join(KnownTestLocations.assetsDir, "test.bim"),
// To temporarily test with a larger iModel:
// "/path/to/real/imodel.bim",
];
for (const iModelPath of iModelPaths) {
const iModelName = path.basename(iModelPath);
describe(iModelName, () => {
let iModel;
let schemaView;
before(async () => {
iModel = SnapshotDb.openFile(iModelPath);
schemaView = await iModel.getSchemaView();
});
after(() => {
iModel.close();
});
it("should have matching schema count and names", () => {
// Force all schemas to load via the schemaContext by looking up a class from each SchemaView schema.
// ecschema-metadata loads schemas lazily - getKnownSchemas() only returns already-loaded ones.
for (const rSchema of schemaView.getSchemas()) {
// Looking up any item from this schema forces it to load in ecschema-metadata
const firstClass = rSchema.getClasses().next();
if (!firstClass.done) {
const metaClass = iModel.schemaContext.getSchemaItemSync(rSchema.name, firstClass.value.name);
assert.isDefined(metaClass, `Schema '${rSchema.name}' could not be loaded in ecschema-metadata`);
}
}
});
it("should have matching classes for every schema", () => {
for (const rSchema of schemaView.getSchemas()) {
for (const rClass of rSchema.getClasses()) {
const metaClass = iModel.schemaContext.getSchemaItemSync(rSchema.name, rClass.name);
assert.isDefined(metaClass, `Class '${rClass.fullName}' not found in ecschema-metadata`);
// Name and fullName
expect(rClass.name).to.equal(metaClass.name, `name mismatch for ${rClass.fullName}`);
// Type
const expectedType = schemaItemTypeToClassType(metaClass.schemaItemType, metaClass);
expect(rClass.type).to.equal(expectedType, `type mismatch for ${rClass.fullName}: schemaView=${ClassType[rClass.type]}, expected=${ClassType[expectedType]}`);
// Modifier
const expectedModifier = ecModifierToClassModifier(metaClass.modifier);
expect(rClass.modifier).to.equal(expectedModifier, `modifier mismatch for ${rClass.fullName}`);
// Label (ecschema-metadata returns undefined when no label, SchemaView returns the name)
if (metaClass.label !== undefined) {
expect(rClass.label).to.equal(metaClass.label, `label mismatch for ${rClass.fullName}`);
}
// Base class
const metaBase = metaClass.getBaseClassSync();
if (metaBase !== undefined) {
assert.isDefined(rClass.baseClass, `${rClass.fullName} should have a base class`);
expect(rClass.baseClass.name).to.equal(metaBase.name, `base class name mismatch for ${rClass.fullName}`);
}
}
}
});
it("should have matching properties for every class", () => {
let totalChecked = 0;
for (const rSchema of schemaView.getSchemas()) {
for (const rClass of rSchema.getClasses()) {
const metaClass = iModel.schemaContext.getSchemaItemSync(rSchema.name, rClass.name);
if (metaClass === undefined)
continue;
// Compare own properties (exclude inherited to avoid ordering issues across layers)
const rOwnProps = rClass.getOwnProperties();
const metaOwnProps = [...metaClass.getPropertiesSync(true)]; // excludeInherited=true
expect(rOwnProps.length).to.equal(metaOwnProps.length, `own property count mismatch for ${rClass.fullName}: schemaView=${rOwnProps.length}, meta=${metaOwnProps.length}`);
for (const rProp of rOwnProps) {
const metaProp = metaOwnProps.find((p) => p.name.toLowerCase() === rProp.name.toLowerCase());
assert.isDefined(metaProp, `Property '${rProp.name}' on ${rClass.fullName} not found in ecschema-metadata`);
compareProperty(rProp, metaProp, rClass);
totalChecked++;
}
}
}
// Sanity: we actually compared something
assert.isAbove(totalChecked, 0, "No properties were compared - something is wrong with the test setup");
});
it("should have matching inherited properties for every class", () => {
let classesChecked = 0;
for (const rSchema of schemaView.getSchemas()) {
for (const rClass of rSchema.getClasses()) {
const metaClass = iModel.schemaContext.getSchemaItemSync(rSchema.name, rClass.name);
if (metaClass === undefined)
continue;
// Compare all properties (including inherited)
const rAllProps = rClass.getProperties();
const metaAllProps = [...metaClass.getPropertiesSync(false)]; // includeInherited
// Check that every ecschema-metadata property exists in the SchemaView
for (const metaProp of metaAllProps) {
const rProp = rAllProps.find((p) => p.name.toLowerCase() === metaProp.name.toLowerCase());
assert.isDefined(rProp, `Inherited property '${metaProp.name}' on ${rClass.fullName} missing from SchemaView`);
}
// Check that every SchemaView property exists in ecschema-metadata
for (const rProp of rAllProps) {
const metaProp = metaAllProps.find((p) => p.name.toLowerCase() === rProp.name.toLowerCase());
assert.isDefined(metaProp, `SchemaView property '${rProp.name}' on ${rClass.fullName} has no ecschema-metadata counterpart`);
}
expect(rAllProps.length).to.equal(metaAllProps.length, `total property count mismatch for ${rClass.fullName}`);
classesChecked++;
}
}
assert.isAbove(classesChecked, 0);
});
it("should have correct ecInstanceId values", () => {
// Validate that ecInstanceId on SchemaView objects matches ec_ table row IDs.
// This is the bridge that lets consumers fall back to ECDbMeta ECSQL queries.
// Schemas
const ecSchemaIds = new Map();
iModel.withSqliteStatement("SELECT Id, Name FROM ec_Schema", (stmt) => {
while (stmt.step() === DbResult.BE_SQLITE_ROW)
ecSchemaIds.set(stmt.getValueString(1).toLowerCase(), stmt.getValueInteger(0));
});
for (const rSchema of schemaView.getSchemas()) {
const ecId = ecSchemaIds.get(rSchema.name.toLowerCase());
assert.isDefined(ecId, `ec_Schema row not found for ${rSchema.name}`);
expect(rSchema.ecInstanceId).to.equal(ecId, `ecInstanceId mismatch for schema ${rSchema.name}`);
}
// Classes
const ecClassIds = new Map();
iModel.withSqliteStatement("SELECT c.Id, s.Name, c.Name FROM ec_Class c JOIN ec_Schema s ON c.SchemaId=s.Id", (stmt) => {
while (stmt.step() === DbResult.BE_SQLITE_ROW)
ecClassIds.set(`${stmt.getValueString(1).toLowerCase()}:${stmt.getValueString(2).toLowerCase()}`, stmt.getValueInteger(0));
});
for (const rSchema of schemaView.getSchemas()) {
for (const rClass of rSchema.getClasses()) {
const key = `${rSchema.name.toLowerCase()}:${rClass.name.toLowerCase()}`;
const ecId = ecClassIds.get(key);
assert.isDefined(ecId, `ec_Class row not found for ${rClass.fullName}`);
expect(rClass.ecInstanceId).to.equal(ecId, `ecInstanceId mismatch for class ${rClass.fullName}`);
}
}
// Own properties
const ecPropIds = new Map();
iModel.withSqliteStatement("SELECT p.Id, s.Name, c.Name, p.Name FROM ec_Property p JOIN ec_Class c ON p.ClassId=c.Id JOIN ec_Schema s ON c.SchemaId=s.Id", (stmt) => {
while (stmt.step() === DbResult.BE_SQLITE_ROW)
ecPropIds.set(`${stmt.getValueString(1).toLowerCase()}:${stmt.getValueString(2).toLowerCase()}.${stmt.getValueString(3).toLowerCase()}`, stmt.getValueInteger(0));
});
let propsChecked = 0;
for (const rSchema of schemaView.getSchemas()) {
for (const rClass of rSchema.getClasses()) {
for (const rProp of rClass.getOwnProperties()) {
const key = `${rSchema.name.toLowerCase()}:${rClass.name.toLowerCase()}.${rProp.name.toLowerCase()}`;
const ecId = ecPropIds.get(key);
if (ecId === undefined)
continue; // dropped properties (broken refs)
expect(rProp.ecInstanceId).to.equal(ecId, `ecInstanceId mismatch for property ${rClass.fullName}.${rProp.name}`);
propsChecked++;
}
}
}
assert.isAbove(propsChecked, 0, "No property ecInstanceIds were checked");
// Enumerations
const ecEnumIds = new Map();
iModel.withSqliteStatement("SELECT e.Id, s.Name, e.Name FROM ec_Enumeration e JOIN ec_Schema s ON e.SchemaId=s.Id", (stmt) => {
while (stmt.step() === DbResult.BE_SQLITE_ROW)
ecEnumIds.set(`${stmt.getValueString(1).toLowerCase()}:${stmt.getValueString(2).toLowerCase()}`, stmt.getValueInteger(0));
});
for (const rSchema of schemaView.getSchemas()) {
for (const rEnum of rSchema.getEnumerations()) {
const key = `${rSchema.name.toLowerCase()}:${rEnum.name.toLowerCase()}`;
const ecId = ecEnumIds.get(key);
assert.isDefined(ecId, `ec_Enumeration row not found for ${rEnum.fullName}`);
expect(rEnum.ecInstanceId).to.equal(ecId, `ecInstanceId mismatch for enum ${rEnum.fullName}`);
}
}
// KindOfQuantity
const ecKoqIds = new Map();
iModel.withSqliteStatement("SELECT k.Id, s.Name, k.Name FROM ec_KindOfQuantity k JOIN ec_Schema s ON k.SchemaId=s.Id", (stmt) => {
while (stmt.step() === DbResult.BE_SQLITE_ROW)
ecKoqIds.set(`${stmt.getValueString(1).toLowerCase()}:${stmt.getValueString(2).toLowerCase()}`, stmt.getValueInteger(0));
});
for (const rSchema of schemaView.getSchemas()) {
for (const rKoq of rSchema.getKindOfQuantities()) {
const key = `${rSchema.name.toLowerCase()}:${rKoq.name.toLowerCase()}`;
const ecId = ecKoqIds.get(key);
assert.isDefined(ecId, `ec_KindOfQuantity row not found for ${rKoq.fullName}`);
expect(rKoq.ecInstanceId).to.equal(ecId, `ecInstanceId mismatch for KoQ ${rKoq.fullName}`);
}
}
// PropertyCategory
const ecCatIds = new Map();
iModel.withSqliteStatement("SELECT pc.Id, s.Name, pc.Name FROM ec_PropertyCategory pc JOIN ec_Schema s ON pc.SchemaId=s.Id", (stmt) => {
while (stmt.step() === DbResult.BE_SQLITE_ROW)
ecCatIds.set(`${stmt.getValueString(1).toLowerCase()}:${stmt.getValueString(2).toLowerCase()}`, stmt.getValueInteger(0));
});
for (const rSchema of schemaView.getSchemas()) {
for (const rCat of rSchema.getPropertyCategories()) {
const key = `${rSchema.name.toLowerCase()}:${rCat.name.toLowerCase()}`;
const ecId = ecCatIds.get(key);
assert.isDefined(ecId, `ec_PropertyCategory row not found for ${rCat.fullName}`);
expect(rCat.ecInstanceId).to.equal(ecId, `ecInstanceId mismatch for category ${rCat.fullName}`);
}
}
});
it("should have matching enumerations", () => {
for (const rSchema of schemaView.getSchemas()) {
for (const rEnum of rSchema.getEnumerations()) {
const metaEnum = iModel.schemaContext.getSchemaItemSync(rSchema.name, rEnum.name);
assert.isDefined(metaEnum, `Enumeration '${rEnum.fullName}' not found in ecschema-metadata`);
expect(rEnum.isStrict).to.equal(metaEnum.isStrict, `isStrict mismatch for ${rEnum.fullName}`);
// Compare enumerators
const rEnumerators = [...rEnum.getEnumerators()];
const metaEnumerators = metaEnum.enumerators;
expect(rEnumerators.length).to.equal(metaEnumerators.length, `enumerator count mismatch for ${rEnum.fullName}`);
for (let i = 0; i < rEnumerators.length; i++) {
expect(rEnumerators[i].name).to.equal(metaEnumerators[i].name, `enumerator name mismatch at index ${i} in ${rEnum.fullName}`);
expect(rEnumerators[i].value).to.equal(metaEnumerators[i].value, `enumerator value mismatch for ${rEnumerators[i].name} in ${rEnum.fullName}`);
}
}
}
});
it("should have matching KindOfQuantity items", () => {
for (const rSchema of schemaView.getSchemas()) {
for (const rKoq of rSchema.getKindOfQuantities()) {
const metaKoq = iModel.schemaContext.getSchemaItemSync(rSchema.name, rKoq.name);
assert.isDefined(metaKoq, `KoQ '${rKoq.fullName}' not found in ecschema-metadata`);
expect(rKoq.relativeError).to.equal(metaKoq.relativeError, `relativeError mismatch for ${rKoq.fullName}`);
}
}
});
it("should have matching PropertyCategory items", () => {
for (const rSchema of schemaView.getSchemas()) {
for (const rCat of rSchema.getPropertyCategories()) {
const metaCat = iModel.schemaContext.getSchemaItemSync(rSchema.name, rCat.name);
assert.isDefined(metaCat, `PropertyCategory '${rCat.fullName}' not found in ecschema-metadata`);
expect(rCat.priority).to.equal(metaCat.priority, `priority mismatch for ${rCat.fullName}`);
}
}
});
it("should have matching relationship constraints", () => {
for (const rSchema of schemaView.getSchemas()) {
for (const rClass of rSchema.getClasses()) {
if (!rClass.isRelationship())
continue;
const metaRel = iModel.schemaContext.getSchemaItemSync(rSchema.name, rClass.name);
assert.isDefined(metaRel, `Relationship '${rClass.fullName}' not in ecschema-metadata`);
// Source constraint
if (rClass.source !== undefined) {
const metaSrc = metaRel.source;
if (metaSrc.abstractConstraint !== undefined && rClass.source.abstractConstraint !== undefined) {
expect(rClass.source.abstractConstraint.name).to.equal(metaSrc.abstractConstraint.fullName.split(".").pop(), `source abstractConstraint mismatch for ${rClass.fullName}`);
}
}
// Target constraint
if (rClass.target !== undefined) {
const metaTgt = metaRel.target;
if (metaTgt.abstractConstraint !== undefined && rClass.target.abstractConstraint !== undefined) {
expect(rClass.target.abstractConstraint.name).to.equal(metaTgt.abstractConstraint.fullName.split(".").pop(), `target abstractConstraint mismatch for ${rClass.fullName}`);
}
}
}
}
});
it("should have matching IS-A relationships", () => {
// Pick a few well-known hierarchies and verify IS-A checks match
const testPairs = [
["BisCore:GeometricElement3d", "BisCore:Element", true],
["BisCore:Element", "BisCore:GeometricElement3d", false],
["BisCore:PhysicalElement", "BisCore:GeometricElement", true],
["BisCore:ModelContainsElements", "BisCore:ElementRefersToElements", false],
];
for (const [className, baseName, expected] of testPairs) {
const rClass = schemaView.findClass(className);
if (rClass === undefined)
continue; // Class may not exist in minimal test iModels
const result = rClass.is(baseName);
expect(result).to.equal(expected, `IS-A check: ${className} -> ${baseName}`);
}
});
it("should have matching isHidden flags against ec_CustomAttribute", () => {
// Build a set of property IDs that have CoreCustomAttributes:HiddenProperty CA
// (without Show=True override), mirroring the C++ writer's CollectHiddenPropertyIds.
const hiddenPropertyIds = new Set();
iModel.withSqliteStatement(`SELECT ca.ContainerId, ca.Instance
FROM ec_CustomAttribute ca
JOIN ec_Class cac ON ca.ClassId=cac.Id
JOIN ec_Schema cas ON cac.SchemaId=cas.Id
WHERE ca.ContainerType & 992 <> 0
AND cas.Name='CoreCustomAttributes' AND cac.Name='HiddenProperty'`, (stmt) => {
while (stmt.step() === DbResult.BE_SQLITE_ROW) {
const instance = stmt.getValueString(1) ?? "";
if (instance) {
const lower = instance.toLowerCase();
if (lower.includes(">true</"))
continue; // Show=True means NOT hidden
}
hiddenPropertyIds.add(stmt.getValueInteger(0));
}
});
// Build a map of property ID -> (schemaName, className, propertyName) from ec_ tables
// so we can correlate ec_ property IDs to SchemaView properties.
const propIdMap = new Map();
iModel.withSqliteStatement(`SELECT p.Id, s.Name, c.Name, p.Name
FROM ec_Property p
JOIN ec_Class c ON p.ClassId=c.Id
JOIN ec_Schema s ON c.SchemaId=s.Id`, (stmt) => {
while (stmt.step() === DbResult.BE_SQLITE_ROW) {
propIdMap.set(stmt.getValueInteger(0), {
schema: stmt.getValueString(1),
cls: stmt.getValueString(2),
prop: stmt.getValueString(3),
});
}
});
// Cross-validate: for every hidden property in ec_ tables that belongs to a
// non-excluded schema, the SchemaView must also mark it as hidden.
let hiddenChecked = 0;
for (const propId of hiddenPropertyIds) {
const info = propIdMap.get(propId);
if (info === undefined)
continue;
if (excludedSchemaViewSchemas.has(info.schema))
continue;
const rClass = schemaView.findClass(`${info.schema}:${info.cls}`);
if (rClass === undefined)
continue;
// Check own properties only (the property lives on this class)
const rProp = rClass.getOwnProperties().find((p) => p.name.toLowerCase() === info.prop.toLowerCase());
if (rProp === undefined)
continue; // property may have been dropped (broken struct ref)
expect(rProp.isHidden).to.equal(true, `${info.schema}:${info.cls}.${info.prop} should be hidden (has HiddenProperty CA)`);
hiddenChecked++;
}
// Also spot-check that explicitly visible properties are NOT hidden.
// Walk a subset of SchemaView classes and verify that properties not in the hidden set
// are marked as not hidden.
let visibleChecked = 0;
for (const rSchema of schemaView.getSchemas()) {
for (const rClass of rSchema.getClasses()) {
for (const rProp of rClass.getOwnProperties()) {
// Find the ec_ property ID for this prop
const matchingEntry = [...propIdMap.entries()].find(([_, info]) => info.schema.toLowerCase() === rSchema.name.toLowerCase()
&& info.cls.toLowerCase() === rClass.name.toLowerCase()
&& info.prop.toLowerCase() === rProp.name.toLowerCase());
if (matchingEntry === undefined)
continue;
const [propId] = matchingEntry;
if (!hiddenPropertyIds.has(propId)) {
expect(rProp.isHidden).to.equal(false, `${rClass.fullName}.${rProp.name} should NOT be hidden (no HiddenProperty CA)`);
visibleChecked++;
}
}
if (visibleChecked >= 200)
break; // cap to keep test fast
}
if (visibleChecked >= 200)
break;
}
// The test.bim likely has at least some non-hidden properties
assert.isAbove(visibleChecked, 0, "No visible properties checked - test setup issue");
assert.isAbove(hiddenChecked, 0, "No hidden properties checked - test setup issue");
});
});
}
});
// --- Helper functions ---
/** Map ecschema-metadata SchemaItemType to SchemaView ClassType. */
function schemaItemTypeToClassType(itemType, ecClass) {
switch (itemType) {
case SchemaItemType.EntityClass:
// ecschema-metadata distinguishes Mixin via instanceof, not SchemaItemType
return (ecClass instanceof Mixin) ? ClassType.Mixin : ClassType.Entity;
case SchemaItemType.Mixin:
return ClassType.Mixin;
case SchemaItemType.RelationshipClass:
return ClassType.Relationship;
case SchemaItemType.StructClass:
return ClassType.Struct;
case SchemaItemType.CustomAttributeClass:
return ClassType.CustomAttribute;
default:
assert.fail(`Unexpected SchemaItemType '${itemType}' for class '${ecClass.fullName}'`);
}
}
/** Map ecschema-metadata ECClassModifier to SchemaView ClassModifier. */
function ecModifierToClassModifier(modifier) {
switch (modifier) {
case ECClassModifier.None: return ClassModifier.None;
case ECClassModifier.Abstract: return ClassModifier.Abstract;
case ECClassModifier.Sealed: return ClassModifier.Sealed;
default: return ClassModifier.None;
}
}
/** Compare a single property across both layers. */
function compareProperty(rProp, metaProp, rClass) {
const ctx = `${rClass.fullName}.${rProp.name}`;
// Name
expect(rProp.name.toLowerCase()).to.equal(metaProp.name.toLowerCase(), `name mismatch for ${ctx}`);
// Kind discriminators (methods, not getters - they are real type predicates)
expect(rProp.isPrimitive()).to.equal(metaProp.isPrimitive(), `isPrimitive mismatch for ${ctx}`);
expect(rProp.isNavigation()).to.equal(metaProp.isNavigation(), `isNavigation mismatch for ${ctx}`);
expect(rProp.isArray()).to.equal(metaProp.isArray(), `isArray mismatch for ${ctx}`);
// For primitives and enums: check primitiveType and extendedTypeName
if (rProp.isPrimitive()) {
// Cast to PrimitiveProperty for field access - ecschema-metadata's type hierarchy
// doesn't model enum-as-primitive correctly (see itwinjs-core#8448), but the fields exist at runtime.
const metaPrimProp = metaProp;
if (metaPrimProp.primitiveType !== undefined) {
// SchemaViewPrimitiveType values match PrimitiveType values
expect(rProp.primitiveType).to.equal(metaPrimProp.primitiveType, `primitiveType mismatch for ${ctx}: schemaView=0x${rProp.primitiveType.toString(16)}, meta=0x${metaPrimProp.primitiveType.toString(16)}`);
}
if (metaPrimProp.extendedTypeName !== undefined) {
expect(rProp.extendedTypeName?.toLowerCase()).to.equal(metaPrimProp.extendedTypeName.toLowerCase(), `extendedTypeName mismatch for ${ctx}`);
}
// KindOfQuantity reference (only on primitive properties)
const metaKoq = metaProp.getKindOfQuantitySync();
if (metaKoq !== undefined && rProp.kindOfQuantity !== undefined) {
expect(rProp.kindOfQuantity.name).to.equal(metaKoq.name, `KoQ mismatch for ${ctx}`);
}
}
// isReadOnly
expect(rProp.isReadOnly).to.equal(metaProp.isReadOnly, `isReadOnly mismatch for ${ctx}`);
// Struct class reference - structClass is non-nullable on struct subclasses
if (rProp.isStruct()) {
const metaStruct = metaProp;
expect(rProp.structClass.name).to.equal(metaStruct.structClass.name, `structClass mismatch for ${ctx}`);
}
// Navigation property: direction and relationship class
if (rProp.isNavigation()) {
const metaNav = metaProp;
expect(rProp.direction).to.equal(metaNav.direction, `direction mismatch for ${ctx}`);
const metaRelClass = metaNav.getRelationshipClassSync();
if (metaRelClass !== undefined) {
expect(rProp.relationshipClass.name).to.equal(metaRelClass.name, `nav relationshipClass mismatch for ${ctx}`);
}
}
// Category reference (available on all property kinds)
const metaCat = metaProp.getCategorySync();
if (metaCat !== undefined && rProp.category !== undefined) {
expect(rProp.category.name).to.equal(metaCat.name, `category mismatch for ${ctx}`);
}
}
//# sourceMappingURL=SchemaViewValidation.test.js.map