import { PropertyRecord , PropertyValueFormat } from "@itwin/appui-abstract" ; import { CategorizedPropertyTypes , FlatGridItemType , IMutableCategorizedPropertyItem , IMutableFlatGridItem , IMutableGridCategoryItem } from "../../../../../components-react/propertygrid/internal/flat-items/MutableFlatGridItem" ; import { CategoryRecordsDict , MutableGridCategory } from "../../../../../components-react/propertygrid/internal/flat-items/MutableGridCategory" ; import { AssertionError , expect } from "chai" ; import sinon from "sinon" ; import { MutableCategorizedPrimitiveProperty } from "../../../../../components-react/propertygrid/internal/flat-items/MutableCategorizedPrimitiveProperty" ; import { MutableCategorizedArrayProperty } from "../../../../../components-react/propertygrid/internal/flat-items/MutableCategorizedArrayProperty" ; import { MutableCategorizedStructProperty } from "../../../../../components-react/propertygrid/internal/flat-items/MutableCategorizedStructProperty" ; import shortid from "shortid" ; import { MutableGridItemFactory } from "../../../../../components-react/propertygrid/internal/flat-items/MutableGridItemFactory" ; import { CategorizedPropertyItem , FlatGridItem , GridCategoryItem } from "../../../../../components-react/propertygrid/internal/flat-items/FlatGridItem" ; import { PropertyCategory , PropertyData } from "../../../../../components-react/propertygrid/PropertyDataProvider" ; import * as faker from "faker" ; import { IPropertyGridModel } from "../../../../../components-react/propertygrid/internal/PropertyGridModel" ; internal export interface GridModelLastItemData { [ selectionKey ] : { isLastInRootCategory : boolean ; lastInNumberOfCategories : number ; } ; } internal export interface PropertyGridModelTestData { testName : string ; propertyData : PropertyData ; expectedLastItemData : GridModelLastItemData ; } internal export interface GridItemToMock { type : CategorizedPropertyTypes ; isVisible : boolean ; } internal export interface FlattenedPropertyBase { selectionKey : string ; } internal export interface FlattenedRecord extends FlattenedPropertyBase { item : PropertyRecord ; originalRecord : PropertyRecord ; } internal export interface FlattenedCategory extends FlattenedPropertyBase { item : PropertyCategory ; } internal export type FlattenedProperty = ( FlattenedRecord | FlattenedCategory ) ; internal export class FlatGridTestUtils { public static getSelectionKey ( property , parentSelectionKey ? ) ; public static getSelectionKey ( property , parentSelectionKey ) ; public static getSelectionKey ( property , parentSelectionKey ? ) { if ( property instanceof PropertyRecord ) return `${ parentSelectionKey }_${ property . property . name }` ; return parentSelectionKey ? `${ parentSelectionKey }_${ property . name }` : property . name ; } public static flattenPropertyRecords ( propertyRecords , parentSelectionKey , considerExpand = false , isParentArray = false ) { const results = [ ] ; propertyRecords . forEach ( ( record , index ) => { const itemToSave = { item : record , selectionKey : this . getSelectionKey ( record , parentSelectionKey ) , originalRecord : record } ; if ( isParentArray ) { const name = `${ record . property . name }_${ index }` ; const displayLabel = `[${ index + 1 }]` ; itemToSave . item = this . overridePropertyDescription ( record , name , displayLabel ) ; itemToSave . selectionKey = this . getSelectionKey ( itemToSave . item , parentSelectionKey ) ; } results . push ( itemToSave ) ; if ( ! considerExpand || record . autoExpand ) results . push ( ... this . flattenPropertyRecords ( record . getChildrenRecords ( ) , itemToSave . selectionKey , considerExpand , record . value . valueFormat === PropertyValueFormat . Array ) ) ; } ) ; return results ; } public static flattenPropertyCategories ( propertyCategories , records , considerExpand = false , parentSelectionKey ? ) { const results = [ ] ; propertyCategories . forEach ( ( category ) => { const selectionKey = this . getSelectionKey ( category , parentSelectionKey ) ; results . push ( { item : category , selectionKey } ) ; if ( ! considerExpand || category . expand ) { const categoryRecords = records [ category . name ] ?? [ ] ; results . push ( ... this . flattenPropertyRecords ( categoryRecords , selectionKey , considerExpand ) ) ; results . push ( ... this . flattenPropertyCategories ( category . childCategories ?? [ ] , records , considerExpand , selectionKey ) ) ; } } ) ; return results ; } public static overridePropertyDescription ( propertyRecord , name ? , displayLabel ? ) { name = name ?? propertyRecord . property . name ; displayLabel = displayLabel ?? propertyRecord . property . displayLabel ; const propertyDescription = { ... propertyRecord . property , name , displayLabel } ; const { value , property , ... others } = propertyRecord ; propertyRecord = new PropertyRecord ( value , propertyDescription ) ; Object . assign ( propertyRecord , others ) ; return propertyRecord ; } public static filterCategories ( gridItems ) { return gridItems . filter ( ( item ) => item . type === FlatGridItemType . Category ) . map ( ( item ) => item as IMutableGridCategoryItem ) ; } public static filterProperties ( gridItems ) { return gridItems . filter ( ( item ) => item . type !== FlatGridItemType . Category ) . map ( ( item ) => item as IMutableCategorizedPropertyItem ) ; } public static constructCategoryRecordsDict ( categories , recordsDict ? ) { recordsDict = recordsDict ?? { } ; for ( const category of categories ) { recordsDict [ category . name ] = [ ] ; this . constructCategoryRecordsDict ( category . childCategories ?? [ ] , recordsDict ) ; } return recordsDict ; } public static valueTypeToFlatGridType ( valueType ) { switch ( valueType ) { case PropertyValueFormat . Primitive : return FlatGridItemType . Primitive ; case PropertyValueFormat . Array : return FlatGridItemType . Array ; case PropertyValueFormat . Struct : return FlatGridItemType . Struct ; default : const unhandledType = valueType ; throw Error ( `Property Value Format not handled: ${ unhandledType }` ) ; } } public static callPropertyAndGridItemAssert ( gridItem , record , categoryAssert , categorizedPropertyAssert ) { if ( gridItem . type === FlatGridItemType . Category ) { categoryAssert ( gridItem , record as PropertyCategory ) ; return ; } categorizedPropertyAssert ( gridItem , record as PropertyRecord ) ; } public static callPropertyAndGridItemAsserts ( gridItems , records , categoryAssert , categorizedPropertyAssert ) { expect ( gridItems . length ) . to . be . equal ( records . length ) ; gridItems . forEach ( ( item , index ) => FlatGridTestUtils . callPropertyAndGridItemAssert ( item , records [ index ] , categoryAssert , categorizedPropertyAssert ) ) ; } private static replaceMockGridItemProperties ( mockItem , type , selectionKey , isExpanded ? ) { Object . assign ( mockItem , { key : shortid . generate ( ) } ) ; sinon . replaceGetter ( mockItem , "type" , ( ) => type ) ; if ( isExpanded !== undefined ) sinon . replaceGetter ( mockItem , "isExpanded" , ( ) => isExpanded ) ; sinon . replaceGetter ( mockItem , "selectionKey" , ( ) => selectionKey ) ; const getChildrenStub = sinon . stub ( ) . returns ( [ ] ) ; sinon . replace ( mockItem , "getChildren" , getChildrenStub ) ; } public static createMockGridCategory ( name , isExpanded ? ) { const gridCategory = sinon . createStubInstance ( MutableGridCategory ) ; sinon . replaceGetter ( gridCategory , "name" , ( ) => name ) ; this . replaceMockGridItemProperties ( gridCategory , FlatGridItemType . Category , name , isExpanded ) ; return gridCategory ; } public static createMockCategorizedPrimitive ( selectionKey ) { const primitive = sinon . createStubInstance ( MutableCategorizedPrimitiveProperty ) ; this . replaceMockGridItemProperties ( primitive , FlatGridItemType . Primitive , selectionKey , false ) ; return primitive ; } public static createMockCategorizedArray ( selectionKey , isExpanded ? ) { const array = sinon . createStubInstance ( MutableCategorizedArrayProperty ) ; this . replaceMockGridItemProperties ( array , FlatGridItemType . Array , selectionKey , isExpanded ) ; return array ; } public static createMockCategorizedStruct ( selectionKey , isExpanded ? ) { const struct = sinon . createStubInstance ( MutableCategorizedStructProperty ) ; this . replaceMockGridItemProperties ( struct , FlatGridItemType . Struct , selectionKey , isExpanded ) ; return struct ; } public static createMockCategorizedProperty ( selectionKey , type , isExpanded ? ) { switch ( type ) { case FlatGridItemType . Primitive : return this . createMockCategorizedPrimitive ( selectionKey ) ; case FlatGridItemType . Array : return this . createMockCategorizedArray ( selectionKey , isExpanded ) ; case FlatGridItemType . Struct : return this . createMockCategorizedStruct ( selectionKey , isExpanded ) ; default : const unhandledType = type ; throw new Error ( `Unhandled FlatGridItemType: ${ unhandledType }` ) ; } } public static createCategorizedPropertyStub ( records , factoryStub ) { const expectedMockChildren = [ ] ; records . forEach ( ( child , index ) => { const gridType = this . valueTypeToFlatGridType ( child . value . valueFormat ) ; const mock = this . createMockCategorizedProperty ( faker . random . words ( ) , gridType ) ; expectedMockChildren . push ( mock ) ; factoryStub . createCategorizedProperty . onCall ( index ) . returns ( mock ) ; } ) ; factoryStub . createCategorizedProperty . onCall ( records . length ) . throws ( new AssertionError ( "Factory called more times than there are children" ) ) ; return expectedMockChildren ; } public static createGridCategoryStub ( category , factoryStub ) { const children = category . childCategories ?? [ ] ; const expectedMockChildren = [ ] ; children . forEach ( ( child , index ) => { const mock = FlatGridTestUtils . createMockGridCategory ( child . name ) ; expectedMockChildren . push ( mock ) ; factoryStub . createGridCategory . onCall ( index ) . returns ( mock ) ; } ) ; factoryStub . createGridCategory . onCall ( children . length ) . throws ( new AssertionError ( "Factory called more times than there are category children" ) ) ; return expectedMockChildren ; } public static getLast < T > ( arr ) { if ( arr . length === 0 ) return undefined ; return arr [ arr . length - 1 ] ; } private static mockGetVisibleDescendants ( property , descendantsToMock ) { const visibleDescendants = descendantsToMock . filter ( ( value ) => value . isVisible ) . map ( ( value ) => FlatGridTestUtils . createMockCategorizedProperty ( faker . random . words ( ) , value . type , false ) ) ; const visibleDescendantsAndSelf = [ property , ... visibleDescendants ] ; sinon . replace ( property , "getVisibleDescendantsAndSelf" , ( ) => visibleDescendantsAndSelf ) ; return visibleDescendantsAndSelf ; } private static mockGetDescendants ( property , descendantsToMock , visibleDescendantsAndSelf ) { const nonVisibleDescendants = descendantsToMock . filter ( ( value ) => ! value . isVisible ) . map ( ( value ) => FlatGridTestUtils . createMockCategorizedProperty ( faker . random . words ( ) , value . type , false ) ) ; const descendantsAndSelf = [ ... visibleDescendantsAndSelf , ... nonVisibleDescendants ] ; sinon . replace ( property , "getDescendantsAndSelf" , ( ) => descendantsAndSelf ) ; return descendantsAndSelf ; } public static setupExpectedDescendants ( mockChildren , descendantsToMock ) { const expectedVisibleDescendants = [ ] ; const expectedDescendants = [ ] ; let expectedLastVisibleDescendant ; mockChildren . forEach ( ( child ) => { const visibleDescendantsAndSelf = FlatGridTestUtils . mockGetVisibleDescendants ( child , descendantsToMock ) ; expectedVisibleDescendants . push ( ... visibleDescendantsAndSelf ) ; expectedLastVisibleDescendant = FlatGridTestUtils . getLast ( visibleDescendantsAndSelf ) ; sinon . replace ( child , "getLastVisibleDescendantOrSelf" , ( ) => expectedLastVisibleDescendant ! ) ; const descendantsAndSelf = FlatGridTestUtils . mockGetDescendants ( child , descendantsToMock , visibleDescendantsAndSelf ) ; expectedDescendants . push ( ... descendantsAndSelf ) ; } ) ; return { expectedVisibleDescendants , expectedDescendants , expectedLastVisibleDescendant } ; } public static assertPropertyEquals ( gridItem , expectedRecord , overrideName ? , overrideLabel ? ) { expectedRecord = FlatGridTestUtils . overridePropertyDescription ( expectedRecord , overrideName , overrideLabel ) ; const expectedType = FlatGridTestUtils . valueTypeToFlatGridType ( expectedRecord . value . valueFormat ) ; expect ( gridItem . type ) . to . be . equal ( expectedType ) ; expect ( gridItem . label ) . to . be . equal ( expectedRecord . property . displayLabel ) ; expect ( gridItem . derivedRecord ) . to . deep . equal ( expectedRecord ) ; expect ( gridItem . selectionKey ) . to . be . equal ( FlatGridTestUtils . getSelectionKey ( expectedRecord , gridItem . parentSelectionKey ) ) ; } public static assertCategoryEquals ( gridCategory , propertyCategory ) { expect ( gridCategory . type ) . to . be . equal ( FlatGridItemType . Category ) ; expect ( gridCategory . name ) . to . be . equal ( propertyCategory . name ) ; expect ( gridCategory . label ) . to . be . equal ( propertyCategory . label ) ; expect ( gridCategory . derivedCategory . name ) . to . be . equal ( propertyCategory . name ) ; expect ( gridCategory . derivedCategory . label ) . to . be . equal ( propertyCategory . label ) ; expect ( gridCategory . selectionKey ) . to . be . equal ( FlatGridTestUtils . getSelectionKey ( propertyCategory , gridCategory . parentCategorySelectionKey ) ) ; } public static getFlattenedPropertyData ( propertyData , considerExpand = false ) { return FlatGridTestUtils . flattenPropertyCategories ( propertyData . categories , propertyData . records , considerExpand ) ; } public static assertGridModel ( gridModel , expectedFlatGrid , lastItemData ? ) { expectedFlatGrid . forEach ( ( expectedProperty ) => { const gridItem = gridModel . getItem ( expectedProperty . selectionKey ) ; this . assertGridItem ( gridItem , expectedProperty ) ; if ( lastItemData ) { const expectedLastItemData = lastItemData [ gridItem . selectionKey ] ?? { isLastInRootCategory : false , lastInNumberOfCategories : 0 } ; expect ( gridItem . lastInNumberOfCategories ) . to . be . equal ( expectedLastItemData . lastInNumberOfCategories , `lastInNumberOfCategories does not match: ${ gridItem . selectionKey }` ) ; expect ( gridItem . isLastInRootCategory ) . to . be . equal ( expectedLastItemData . isLastInRootCategory , `isLastInRootCategory does not match: ${ gridItem . selectionKey }` ) ; } } ) ; } public static assertGridItem ( gridItem , expectedProperty ) { expect ( gridItem . selectionKey ) . to . be . equal ( expectedProperty . selectionKey , "Selection keys do not match" ) ; if ( expectedProperty . item instanceof PropertyRecord ) { const expectedType = FlatGridTestUtils . valueTypeToFlatGridType ( expectedProperty . item . value . valueFormat ) ; expect ( gridItem . type ) . to . be . equal ( expectedType ) ; const property = gridItem as CategorizedPropertyItem ; expect ( property . derivedRecord ) . to . deep . equal ( expectedProperty . item , `Derived record and expected record do not match` ) ; } else { expect ( gridItem . type ) . to . be . equal ( FlatGridItemType . Category ) ; const category = gridItem as GridCategoryItem ; const expectedCategory = { name : expectedProperty . item . name , label : expectedProperty . item . label , expand : expectedProperty . item . expand } ; expect ( category . derivedCategory ) . to . deep . equal ( expectedCategory , "Derived category and expected category do not match" ) ; } } }