import { expect } from "chai" ; import { BeDuration , Id64 , Id64Arg , Id64String } from "@itwin/core-bentley" ; import { ClipVector , Transform } from "@itwin/core-geometry" ; import { AmbientOcclusion , AnalysisStyle , ClipStyle , ColorDef , FeatureAppearance , ModelClipGroup , ModelClipGroups , MonochromeMode , PlanProjectionSettings , SubCategoryOverride , ThematicDisplay , ViewFlags , } from "@itwin/core-common" ; import { ChangeFlag , FeatureSymbology , PerModelCategoryVisibility , ScreenViewport , SnapshotConnection , SpatialViewState , StandardViewId , Viewport , ViewState , } from "@itwin/core-frontend" ; import { ViewportChangedHandler , ViewportState } from "../ViewportChangedHandler" ; import { TestUtility } from "../TestUtility" ; describe ( "Viewport changed events" , async ( ) => { let testBim ; let testImodel ; let vp ; const viewDiv = document . createElement ( "div" ) ; viewDiv . style . width = viewDiv . style . height = "1000px" ; document . body . appendChild ( viewDiv ) ; before ( async ( ) => { await TestUtility . startFrontend ( undefined , true ) ; testBim = await SnapshotConnection . openFile ( "test.bim" ) ; testImodel = await SnapshotConnection . openFile ( "testImodel.bim" ) ; } ) ; after ( async ( ) => { if ( undefined !== testBim ) await testBim . close ( ) ; if ( undefined !== testImodel ) await testImodel . close ( ) ; await TestUtility . shutdownFrontend ( ) ; } ) ; afterEach ( ( ) => { if ( vp ) vp . dispose ( ) ; } ) ; function id64 ( localId ) { return Id64 . fromLocalAndBriefcaseIds ( localId , 1 ) ; } it ( "should be dispatched when always/never-drawn change" , async ( ) => { const view = await testBim . views . load ( "0x34" ) as SpatialViewState ; view . setStandardRotation ( StandardViewId . RightIso ) ; vp = ScreenViewport . create ( viewDiv , view ) ; ViewportChangedHandler . test ( vp , ( mon ) => { mon . expect ( ChangeFlag . None , undefined , ( ) => vp . clearNeverDrawn ( ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . clearAlwaysDrawn ( ) ) ; const idSet = new Set < string > ( ) ; idSet . add ( "0x123" ) ; mon . expect ( ChangeFlag . AlwaysDrawn , undefined , ( ) => vp . setAlwaysDrawn ( idSet , false ) ) ; mon . expect ( ChangeFlag . AlwaysDrawn , undefined , ( ) => vp . setAlwaysDrawn ( idSet , true ) ) ; mon . expect ( ChangeFlag . NeverDrawn , undefined , ( ) => vp . setNeverDrawn ( idSet ) ) ; mon . expect ( ChangeFlag . NeverDrawn , undefined , ( ) => vp . clearNeverDrawn ( ) ) ; mon . expect ( ChangeFlag . AlwaysDrawn , undefined , ( ) => vp . clearAlwaysDrawn ( ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . clearNeverDrawn ( ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . clearAlwaysDrawn ( ) ) ; mon . expect ( ChangeFlag . AlwaysDrawn , undefined , ( ) => vp . setAlwaysDrawn ( idSet , true ) ) ; mon . expect ( ChangeFlag . AlwaysDrawn , undefined , ( ) => vp . setAlwaysDrawn ( idSet , true ) ) ; idSet . clear ( ) ; mon . expect ( ChangeFlag . AlwaysDrawn , undefined , ( ) => vp . setAlwaysDrawn ( idSet , true ) ) ; mon . expect ( ChangeFlag . AlwaysDrawn , undefined , ( ) => vp . clearAlwaysDrawn ( ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . clearAlwaysDrawn ( ) ) ; idSet . add ( "0x123" ) ; mon . expect ( ChangeFlag . AlwaysDrawn | ChangeFlag . NeverDrawn , undefined , ( ) => { for ( let i = 0 ; i < 5 ; i ++ ) { vp . setAlwaysDrawn ( idSet ) ; vp . clearAlwaysDrawn ( ) ; vp . setNeverDrawn ( idSet ) ; vp . clearNeverDrawn ( ) ; } } ) ; vp . saveViewUndo ( ) ; vp . doUndo ( ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => undefined ) ; vp . doRedo ( ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => undefined ) ; } ) ; } ) ; it ( "should be dispatched when display style is modified using Viewport APIs" , async ( ) => { const view = await testBim . views . load ( "0x34" ) as SpatialViewState ; view . setStandardRotation ( StandardViewId . RightIso ) ; vp = ScreenViewport . create ( viewDiv , view ) ; ViewportChangedHandler . test ( vp , ( mon ) => { let newFlags = vp . viewFlags . copy ( { } ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . viewFlags = newFlags ) ; newFlags = newFlags . with ( "lighting" , ! newFlags . lighting ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . RenderPlan , ( ) => vp . viewFlags = newFlags ) ; newFlags = newFlags . with ( "constructions" , ! newFlags . constructions ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . RenderPlan , ( ) => vp . viewFlags = newFlags ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . RenderPlan , ( ) => { vp . displayStyle . backgroundColor = ColorDef . red ; vp . displayStyle . viewFlags = new ViewFlags ( ) ; } ) ; vp . saveViewUndo ( ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . RenderPlan , ( ) => { const newStyle = vp . displayStyle . clone ( ) ; newStyle . backgroundColor = ColorDef . red ; vp . displayStyle = newStyle ; } ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . RenderPlan , ( ) => vp . clipStyle = ClipStyle . fromJSON ( { cutStyle : { appearance : { weight : 12 } } } ) ) ; vp . saveViewUndo ( ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . RenderPlan , ( ) => { const newStyle = vp . displayStyle . clone ( ) ; newStyle . viewFlags = newStyle . viewFlags . with ( "constructions" , ! newStyle . viewFlags . constructions ) ; vp . displayStyle = newStyle ; } ) ; vp . saveViewUndo ( ) ; const ovr = SubCategoryOverride . fromJSON ( { color : ColorDef . green . tbgr } ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . RenderPlan , ( ) => vp . displayStyle . overrideSubCategory ( "0x123" , ovr ) ) ; vp . saveViewUndo ( ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . RenderPlan , ( ) => { const style = vp . displayStyle . clone ( ) ; style . overrideSubCategory ( "0x123" , ovr ) ; vp . displayStyle = style ; } ) ; vp . saveViewUndo ( ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . ViewedModels | ChangeFlag . FeatureOverrideProvider , ViewportState . RenderPlan , ( ) => { vp . overrideSubCategory ( "0x123" , ovr ) ; vp . changeViewedModels ( new Set < string > ( ) ) ; } ) ; vp . saveViewUndo ( ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . RenderPlan , ( ) => vp . overrideSubCategory ( "0x123" , SubCategoryOverride . fromJSON ( { color : ColorDef . red . tbgr } ) ) ) ; } ) ; } ) ; it ( "should be dispatched when display style is modified directly" , async ( ) => { const view = await testBim . views . load ( "0x34" ) as SpatialViewState ; vp = ScreenViewport . create ( viewDiv , view ) ; ViewportChangedHandler . test ( vp , ( mon ) => { const expectNoChange = ( func ) => mon . expect ( ChangeFlag . None , undefined , func ) ; const expectChange = ( func ) => mon . expect ( ChangeFlag . DisplayStyle , ViewportState . RenderPlan , func ) ; const expectOverrideChange = ( func ) => mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . RenderPlan , func ) ; expectNoChange ( ( ) => view . displayStyle = view . displayStyle ) ; expectOverrideChange ( ( ) => view . displayStyle = view . displayStyle . clone ( ) ) ; const style = view . getDisplayStyle3d ( ) ; const settings = style . settings ; let vf = settings . viewFlags . copy ( { } ) ; expectNoChange ( ( ) => settings . viewFlags = vf ) ; vf = vf . with ( "transparency" , ! vf . transparency ) ; expectChange ( ( ) => settings . viewFlags = vf ) ; expectOverrideChange ( ( ) => settings . overrideSubCategory ( "0x123" , SubCategoryOverride . fromJSON ( { color : ColorDef . blue . tbgr } ) ) ) ; expectOverrideChange ( ( ) => settings . overrideModelAppearance ( "0xabc" , FeatureAppearance . fromJSON ( { weight : 10 } ) ) ) ; expectOverrideChange ( ( ) => settings . dropModelAppearanceOverride ( "0xabc" ) ) ; expectChange ( ( ) => settings . backgroundColor = ColorDef . red ) ; expectNoChange ( ( ) => settings . backgroundColor = ColorDef . red ) ; expectChange ( ( ) => settings . monochromeColor = ColorDef . green ) ; expectNoChange ( ( ) => settings . monochromeColor = ColorDef . green ) ; expectChange ( ( ) => settings . monochromeMode = MonochromeMode . Flat ) ; expectNoChange ( ( ) => settings . monochromeMode = MonochromeMode . Flat ) ; expectOverrideChange ( ( ) => settings . clipStyle = ClipStyle . fromJSON ( { cutStyle : { appearance : { weight : 5 } } } ) ) ; expectNoChange ( ( ) => settings . analysisFraction = settings . analysisFraction ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . AnalysisFraction , ( ) => settings . analysisFraction = 0.123456 ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . AnalysisFraction | ViewportState . RenderPlan , ( ) => settings . analysisStyle = AnalysisStyle . fromJSON ( { displacement : { channelName : "source" } } ) ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . TimePoint , ( ) => settings . timePoint = 43 ) ; expectNoChange ( ( ) => settings . timePoint = 43 ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . TimePoint , ( ) => settings . scheduleScriptProps = [ { modelId : "0x123" , elementTimelines : [ ] } ] ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . TimePoint , ( ) => settings . scheduleScriptProps = undefined ) ; expectNoChange ( ( ) => settings . scheduleScriptProps = undefined ) ; expectChange ( ( ) => settings . hiddenLineSettings = settings . hiddenLineSettings . override ( { transThreshold : 1.0 - settings . hiddenLineSettings . transThreshold } ) ) ; expectNoChange ( ( ) => settings . hiddenLineSettings = settings . hiddenLineSettings . override ( { } ) ) ; expectNoChange ( ( ) => settings . lights = settings . lights . clone ( ) ) ; expectChange ( ( ) => settings . lights = settings . lights . clone ( { numCels : settings . lights . numCels + 1 } ) ) ; expectNoChange ( ( ) => settings . lights = settings . lights . clone ( { numCels : settings . lights . numCels } ) ) ; expectNoChange ( ( ) => settings . solarShadows = settings . solarShadows . clone ( { color : settings . solarShadows . color . toColorDef ( ) . toJSON ( ) } ) ) ; expectChange ( ( ) => settings . solarShadows = settings . solarShadows . clone ( { color : 123 } ) ) ; const thematicProps = settings . thematic . toJSON ( ) ; expectNoChange ( ( ) => settings . thematic = ThematicDisplay . fromJSON ( thematicProps ) ) ; thematicProps . range = { low : 123 , high : 456 } ; expectChange ( ( ) => settings . thematic = ThematicDisplay . fromJSON ( thematicProps ) ) ; expectChange ( ( ) => settings . ambientOcclusionSettings = AmbientOcclusion . Settings . fromJSON ( { bias : 42 , maxDistance : 24 } ) ) ; expectChange ( ( ) => settings . environment = settings . environment . withDisplay ( { ground : ! settings . environment . displayGround } ) ) ; expectChange ( ( ) => settings . setPlanProjectionSettings ( "0xabcdef" , undefined ) ) ; expectChange ( ( ) => settings . setPlanProjectionSettings ( "0xfedcba" , PlanProjectionSettings . fromJSON ( { elevation : 42 } ) ) ) ; } ) ; } ) ; it ( "should be dispatched when overrides are applied to the display style" , async ( ) => { vp = ScreenViewport . create ( viewDiv , await testBim . views . load ( "0x34" ) ) ; ViewportChangedHandler . test ( vp , ( mon ) => { const settings = vp . view . displayStyle . settings ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => settings . applyOverrides ( { viewflags : { backgroundMap : ! settings . viewFlags . backgroundMap } } ) ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . RenderPlan , ( ) => settings . applyOverrides ( { backgroundColor : 0xabcdef } ) ) ; mon . expect ( ChangeFlag . DisplayStyle | ChangeFlag . FeatureOverrideProvider , ViewportState . TimePoint , ( ) => settings . applyOverrides ( { scheduleScript : [ { modelId : "0x321" , elementTimelines : [ ] } ] } ) ) ; } ) ; } ) ; it ( "should be dispatched when background map is modified using Viewport APIs" , async ( ) => { const view = await testBim . views . load ( "0x34" ) as SpatialViewState ; view . setStandardRotation ( StandardViewId . RightIso ) ; vp = ScreenViewport . create ( viewDiv , view ) ; ViewportChangedHandler . test ( vp , ( mon ) => { let vf = vp . viewFlags . with ( "backgroundMap" , ! vp . viewFlags . backgroundMap ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => vp . viewFlags = vf ) ; vf = vf . with ( "backgroundMap" , ! vf . backgroundMap ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => vp . viewFlags = vf ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => vp . backgroundMapSettings = vp . backgroundMapSettings . clone ( { groundBias : 123 } ) ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => vp . changeBackgroundMapProps ( { groundBias : 456 } ) ) ; } ) ; } ) ; it ( "should be dispatched when background map is modified directly" , async ( ) => { const view = await testBim . views . load ( "0x34" ) as SpatialViewState ; vp = ScreenViewport . create ( viewDiv , view ) ; ViewportChangedHandler . test ( vp , ( mon ) => { const settings = view . displayStyle . settings ; const vf = settings . viewFlags . with ( "backgroundMap" , ! settings . viewFlags . backgroundMap ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => settings . viewFlags = vf ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => settings . viewFlags = vf ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => settings . backgroundMap = settings . backgroundMap . clone ( { groundBias : 654 } ) ) ; mon . expect ( ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => settings . backgroundMap = settings . backgroundMap . clone ( { groundBias : 321 } ) ) ; } ) ; } ) ; it ( "should invalidate scene when shadows are on" , async ( ) => { const view = await testBim . views . load ( "0x34" ) as SpatialViewState ; view . setStandardRotation ( StandardViewId . RightIso ) ; vp = ScreenViewport . create ( viewDiv , view ) ; ViewportChangedHandler . test ( vp , ( mon ) => { mon . expect ( ChangeFlag . DisplayStyle , ViewportState . RenderPlan , ( ) => vp . viewFlags = vp . viewFlags . with ( "shadows" , true ) ) ; const idSet = new Set < string > ( ) ; idSet . add ( "0x321" ) ; mon . expect ( ChangeFlag . AlwaysDrawn , ViewportState . Scene , ( ) => vp . setAlwaysDrawn ( idSet ) ) ; mon . expect ( ChangeFlag . AlwaysDrawn , ViewportState . Scene , ( ) => vp . clearAlwaysDrawn ( ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . clearAlwaysDrawn ( ) ) ; idSet . add ( "0x123" ) ; mon . expect ( ChangeFlag . NeverDrawn , ViewportState . Scene , ( ) => vp . setNeverDrawn ( idSet ) ) ; mon . expect ( ChangeFlag . NeverDrawn , ViewportState . Scene , ( ) => vp . clearNeverDrawn ( ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . clearNeverDrawn ( ) ) ; mon . expect ( ChangeFlag . FeatureOverrideProvider , ViewportState . Scene , ( ) => vp . setFeatureOverrideProviderChanged ( ) ) ; mon . expect ( ChangeFlag . ViewedCategories , ViewportState . Scene , ( ) => vp . changeCategoryDisplay ( [ "0xa" , "0xb" ] , true ) ) ; } ) ; } ) ; function changeView ( viewport , view ) { viewport . changeView ( view , { animateFrustumChange : false } ) ; } it ( "should be dispatched when displayed 2d models change" , async ( ) => { vp = ScreenViewport . create ( viewDiv , await testImodel . views . load ( id64 ( 0x20 ) ) ) ; await ViewportChangedHandler . testAsync ( vp , async ( mon ) => { mon . expect ( ChangeFlag . None , undefined , ( ) => expect ( vp . changeModelDisplay ( id64 ( 0x19 ) , false ) ) . to . be . false ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => expect ( vp . changeModelDisplay ( id64 ( 0x27 ) , true ) ) . to . be . false ) ; const viewedModels = new Set < string > ( ) ; viewedModels . add ( id64 ( 0x27 ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => expect ( vp . changeViewedModels ( viewedModels ) ) . to . be . false ) ; const view20 = await testImodel . views . load ( id64 ( 0x20 ) ) ; mon . expect ( ChangeFlag . ViewState , ViewportState . Controller , ( ) => changeView ( vp , view20 ) ) ; const view35 = await testImodel . views . load ( id64 ( 0x35 ) ) ; mon . expect ( ChangeFlag . ViewedModels | ChangeFlag . ViewedCategories | ChangeFlag . DisplayStyle | ChangeFlag . ViewState , ViewportState . Controller , ( ) => changeView ( vp , view35 ) ) ; mon . expect ( ChangeFlag . ViewedModels | ChangeFlag . ViewedCategories | ChangeFlag . DisplayStyle | ChangeFlag . ViewState , ViewportState . Controller , ( ) => changeView ( vp , view20 . clone ( ) ) ) ; } ) ; } ) ; it ( "should be dispatched when model selector is modified using Viewport APIs" , async ( ) => { vp = ScreenViewport . create ( viewDiv , await testBim . views . load ( "0x34" ) ) ; await ViewportChangedHandler . testAsync ( vp , async ( mon ) => { mon . expect ( ChangeFlag . None , undefined , ( ) => vp . changeModelDisplay ( "0x1c" , true ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . changeModelDisplay ( "0x9876543" , false ) ) ; let selectedModels = ( vp . view as SpatialViewState ) . modelSelector . models ; mon . expect ( ChangeFlag . ViewedModels , ViewportState . Scene , ( ) => vp . changeViewedModels ( selectedModels ) ) ; selectedModels = new Set < string > ( ) ; selectedModels . add ( "0x1c" ) ; mon . expect ( ChangeFlag . ViewedModels , ViewportState . Scene , ( ) => vp . changeViewedModels ( selectedModels ) ) ; mon . expect ( ChangeFlag . ViewedModels , ViewportState . Scene , ( ) => { vp . changeModelDisplay ( "0x1c" , false ) ; vp . changeModelDisplay ( "0x1f" , true ) ; } ) ; selectedModels . clear ( ) ; selectedModels . add ( "0x1f" ) ; mon . expect ( ChangeFlag . ViewedModels , ViewportState . Scene , ( ) => vp . changeViewedModels ( selectedModels ) ) ; } ) ; } ) ; it ( "should be dispatched when model selector is modified directly" , async ( ) => { const view = await testBim . views . load ( "0x34" ) as SpatialViewState ; vp = ScreenViewport . create ( viewDiv , view ) ; ViewportChangedHandler . test ( vp , ( mon ) => { const expectChange = ( func ) => mon . expect ( ChangeFlag . ViewedModels , ViewportState . Scene , func ) ; const expectNoChange = ( func ) => mon . expect ( ChangeFlag . None , undefined , func ) ; expectNoChange ( ( ) => view . modelSelector = view . modelSelector ) ; expectChange ( ( ) => view . modelSelector = view . modelSelector . clone ( ) ) ; const models = view . modelSelector . models ; expectChange ( ( ) => models . add ( "0xabc" ) ) ; expectNoChange ( ( ) => models . add ( "0xabc" ) ) ; expectChange ( ( ) => models . delete ( "0xabc" ) ) ; expectNoChange ( ( ) => models . delete ( "0xabc" ) ) ; expectChange ( ( ) => models . clear ( ) ) ; expectNoChange ( ( ) => models . clear ( ) ) ; const modelIds = new Set < string > ( [ "0x1" , "0x2" ] ) ; expectChange ( ( ) => view . modelSelector . models = modelIds ) ; expectChange ( ( ) => view . modelSelector . models = modelIds ) ; } ) ; } ) ; it ( "should be dispatched when category selector is modified using Viewport APIs" , async ( ) => { vp = ScreenViewport . create ( viewDiv , await testImodel . views . load ( id64 ( 0x15 ) ) ) ; await ViewportChangedHandler . testAsync ( vp , async ( mon ) => { mon . expect ( ChangeFlag . ViewedCategories , undefined , ( ) => vp . changeCategoryDisplay ( id64 ( 0x01 ) , true ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . changeCategoryDisplay ( id64 ( 0x1a ) , false ) ) ; vp . saveViewUndo ( ) ; mon . expect ( ChangeFlag . ViewedCategories , undefined , ( ) => { vp . changeCategoryDisplay ( id64 ( 0x01 ) , false ) ; vp . changeCategoryDisplay ( id64 ( 0x01 ) , true ) ; } ) ; vp . saveViewUndo ( ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . doUndo ( ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . doRedo ( ) ) ; const view13 = await testImodel . views . load ( id64 ( 0x13 ) ) ; mon . expect ( ChangeFlag . ViewState | ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => changeView ( vp , view13 ) ) ; const view17 = await testImodel . views . load ( id64 ( 0x17 ) ) ; mon . expect ( ChangeFlag . ViewState | ChangeFlag . DisplayStyle | ChangeFlag . ViewedCategories , ViewportState . Controller , ( ) => changeView ( vp , view17 ) ) ; mon . expect ( ChangeFlag . ViewedCategories , undefined , ( ) => { vp . changeCategoryDisplay ( vp . view . categorySelector . categories , false ) ; vp . changeCategoryDisplay ( view13 . categorySelector . categories , true ) ; } ) ; mon . expect ( ChangeFlag . ViewState | ChangeFlag . ViewedCategories | ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => changeView ( vp , view13 ) ) ; } ) ; } ) ; it ( "should be dispatched when category selector is modified directly" , async ( ) => { vp = ScreenViewport . create ( viewDiv , await testImodel . views . load ( id64 ( 0x15 ) ) ) ; ViewportChangedHandler . test ( vp , ( mon ) => { const expectChange = ( func ) => mon . expect ( ChangeFlag . ViewedCategories , undefined , func ) ; const expectNoChange = ( func ) => mon . expect ( ChangeFlag . None , undefined , func ) ; expectChange ( ( ) => vp . view . categorySelector = vp . view . categorySelector . clone ( ) ) ; expectNoChange ( ( ) => vp . view . categorySelector = vp . view . categorySelector ) ; const categories = vp . view . categorySelector . categories ; expectChange ( ( ) => categories . add ( "0x123" ) ) ; expectNoChange ( ( ) => categories . add ( "0x123" ) ) ; expectChange ( ( ) => categories . delete ( "0x123" ) ) ; expectNoChange ( ( ) => categories . delete ( "0x123" ) ) ; expectChange ( ( ) => categories . clear ( ) ) ; expectNoChange ( ( ) => categories . clear ( ) ) ; const catIds = new Set < string > ( [ "0xa" , "0xb" ] ) ; expectChange ( ( ) => vp . view . categorySelector . categories = catIds ) ; expectChange ( ( ) => vp . view . categorySelector . categories = catIds ) ; } ) ; } ) ; it ( "should be dispatched when per-model category visibility changes" , async ( ) => { vp = ScreenViewport . create ( viewDiv , await testBim . views . load ( "0x34" ) ) ; const vis = vp . perModelCategoryVisibility ; ViewportChangedHandler . test ( vp , ( mon ) => { expect ( vis . getOverride ( "0x1c" , "0x1234" ) ) . to . equal ( PerModelCategoryVisibility . Override . None ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vis . setOverride ( "0x1c" , "0x1234" , PerModelCategoryVisibility . Override . None ) ) ; expect ( vis . getOverride ( "0x1c" , "0x1234" ) ) . to . equal ( PerModelCategoryVisibility . Override . None ) ; mon . expect ( ChangeFlag . ViewedCategoriesPerModel , undefined , ( ) => vis . setOverride ( "0x1c" , "0x1234" , PerModelCategoryVisibility . Override . Show ) ) ; expect ( vis . getOverride ( "0x1c" , "0x1234" ) ) . to . equal ( PerModelCategoryVisibility . Override . Show ) ; mon . expect ( ChangeFlag . ViewedCategoriesPerModel , undefined , ( ) => vis . setOverride ( "0x1c" , "0x1234" , PerModelCategoryVisibility . Override . Hide ) ) ; expect ( vis . getOverride ( "0x1c" , "0x1234" ) ) . to . equal ( PerModelCategoryVisibility . Override . Hide ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vis . clearOverrides ( "0x9876" ) ) ; mon . expect ( ChangeFlag . ViewedCategoriesPerModel , undefined , ( ) => vis . clearOverrides ( ) ) ; expect ( vis . getOverride ( "0x1c" , "0x1234" ) ) . to . equal ( PerModelCategoryVisibility . Override . None ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vis . clearOverrides ( ) ) ; const idSet = new Set < string > ( ) ; idSet . add ( "0x1234567" ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vis . setOverride ( new Set < string > ( ) , new Set < string > ( ) , PerModelCategoryVisibility . Override . Show ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vis . setOverride ( idSet , new Set < string > ( ) , PerModelCategoryVisibility . Override . Show ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vis . setOverride ( new Set < string > ( ) , idSet , PerModelCategoryVisibility . Override . Show ) ) ; const idList = [ "0x1234567" ] ; mon . expect ( ChangeFlag . None , undefined , ( ) => vis . setOverride ( [ ] , [ ] , PerModelCategoryVisibility . Override . Show ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vis . setOverride ( idList , [ ] , PerModelCategoryVisibility . Override . Show ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vis . setOverride ( [ ] , idList , PerModelCategoryVisibility . Override . Show ) ) ; const modelIdList = [ "0x1" , "0x2" , "0x3" ] ; const catIdList = [ "0xa" , "0xb" ] ; mon . expect ( ChangeFlag . ViewedCategoriesPerModel , undefined , ( ) => vis . setOverride ( modelIdList , catIdList , PerModelCategoryVisibility . Override . Show ) ) ; for ( const modelId of modelIdList ) for ( const catId of catIdList ) expect ( vis . getOverride ( modelId , catId ) ) . to . equal ( PerModelCategoryVisibility . Override . Show ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => vis . setOverride ( modelIdList , catIdList , PerModelCategoryVisibility . Override . Show ) ) ; modelIdList . shift ( ) ; catIdList . shift ( ) ; mon . expect ( ChangeFlag . ViewedCategoriesPerModel , undefined , ( ) => vis . setOverride ( modelIdList , catIdList , PerModelCategoryVisibility . Override . Hide ) ) ; expect ( vis . getOverride ( "0x1" , "0xa" ) ) . to . equal ( PerModelCategoryVisibility . Override . Show ) ; expect ( vis . getOverride ( "0x1" , "0xb" ) ) . to . equal ( PerModelCategoryVisibility . Override . Show ) ; expect ( vis . getOverride ( "0x2" , "0xa" ) ) . to . equal ( PerModelCategoryVisibility . Override . Show ) ; expect ( vis . getOverride ( "0x2" , "0xb" ) ) . to . equal ( PerModelCategoryVisibility . Override . Hide ) ; expect ( vis . getOverride ( "0x3" , "0xa" ) ) . to . equal ( PerModelCategoryVisibility . Override . Show ) ; expect ( vis . getOverride ( "0x3" , "0xb" ) ) . to . equal ( PerModelCategoryVisibility . Override . Hide ) ; mon . expect ( ChangeFlag . ViewedCategoriesPerModel , undefined , ( ) => vis . clearOverrides ( [ "0x1" ] ) ) ; expect ( vis . getOverride ( "0x1" , "0xa" ) ) . to . equal ( PerModelCategoryVisibility . Override . None ) ; expect ( vis . getOverride ( "0x1" , "0xb" ) ) . to . equal ( PerModelCategoryVisibility . Override . None ) ; } ) ; } ) ; it ( "should be dispatched when feature override provider changes" , async ( ) => { vp = ScreenViewport . create ( viewDiv , await testBim . views . load ( "0x34" ) ) ; let overridesAdded = false ; const provider = { addFeatureOverrides : ( _overrides , _viewport ) : void => { expect ( overridesAdded ) . to . be . false ; overridesAdded = true ; } } ; ViewportChangedHandler . test ( vp , ( mon ) => { mon . expect ( ChangeFlag . FeatureOverrideProvider , undefined , ( ) => vp . addFeatureOverrideProvider ( provider ) ) ; expect ( overridesAdded ) . to . be . true ; overridesAdded = false ; mon . expect ( ChangeFlag . FeatureOverrideProvider , undefined , ( ) => vp . setFeatureOverrideProviderChanged ( ) ) ; expect ( overridesAdded ) . to . be . true ; overridesAdded = false ; mon . expect ( ChangeFlag . None , undefined , ( ) => vp . addFeatureOverrideProvider ( provider ) ) ; expect ( overridesAdded ) . to . be . false ; mon . expect ( ChangeFlag . FeatureOverrideProvider , undefined , ( ) => { const prov = vp . findFeatureOverrideProvider ( ( _ ) => true ) ; expect ( prov ) . not . to . be . undefined ; if ( prov ) vp . dropFeatureOverrideProvider ( prov ) ; } ) ; expect ( overridesAdded ) . to . be . false ; } ) ; } ) ; it ( "should be dispatched when changing ViewState" , async ( ) => { const view2d20 = await testImodel . views . load ( id64 ( 0x20 ) ) ; const view2d2e = await testImodel . views . load ( id64 ( 0x2e ) ) ; const view3d15 = await testImodel . views . load ( id64 ( 0x15 ) ) ; const view3d17 = await testImodel . views . load ( id64 ( 0x17 ) ) ; vp = ScreenViewport . create ( viewDiv , view2d20 . clone ( ) ) ; ViewportChangedHandler . test ( vp , ( mon ) => { mon . expect ( ChangeFlag . ViewState , ViewportState . Controller , ( ) => changeView ( vp , view2d20 . clone ( ) ) ) ; mon . expect ( ChangeFlag . ViewState | ChangeFlag . ViewedCategories | ChangeFlag . ViewedModels | ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => changeView ( vp , view2d2e . clone ( ) ) ) ; mon . expect ( ChangeFlag . ViewState | ChangeFlag . ViewedCategories | ChangeFlag . ViewedModels | ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => changeView ( vp , view3d15 . clone ( ) ) ) ; mon . expect ( ChangeFlag . ViewState , ViewportState . Controller , ( ) => changeView ( vp , view3d15 . clone ( ) ) ) ; mon . expect ( ChangeFlag . ViewState | ChangeFlag . ViewedCategories , ViewportState . Controller , ( ) => changeView ( vp , view3d17 . clone ( ) ) ) ; mon . expect ( ChangeFlag . ViewState | ChangeFlag . ViewedCategories | ChangeFlag . ViewedModels | ChangeFlag . DisplayStyle , ViewportState . Controller , ( ) => changeView ( vp , view2d20 . clone ( ) ) ) ; mon . expect ( ChangeFlag . None , undefined , ( ) => changeView ( vp , vp . view ) ) ; } ) ; let numEvents = 0 ; const removeListener = vp . onChangeView . addListener ( ( ) => ++ numEvents ) ; changeView ( vp , vp . view ) ; expect ( numEvents ) . to . equal ( 0 ) ; changeView ( vp , view2d20 . clone ( ) ) ; expect ( numEvents ) . to . equal ( 1 ) ; changeView ( vp , view2d20 ) ; expect ( numEvents ) . to . equal ( 2 ) ; removeListener ( ) ; } ) ; it ( "should be dispatched when modifying ViewState directly" , async ( ) => { const view = await testBim . views . load ( "0x34" ) as SpatialViewState ; vp = ScreenViewport . create ( viewDiv , view ) ; ViewportChangedHandler . test ( vp , ( mon ) => { const expectChange = ( state , func ) => mon . expect ( ChangeFlag . None , state , func ) ; expectChange ( ViewportState . RenderPlan , ( ) => view . details . clipVector = ClipVector . createEmpty ( ) ) ; expectChange ( ViewportState . Scene , ( ) => view . details . modelClipGroups = new ModelClipGroups ( [ ModelClipGroup . fromJSON ( { models : [ "0x123" ] } ) ] ) ) ; expectChange ( ViewportState . Scene , ( ) => view . modelDisplayTransformProvider = { getModelDisplayTransform : ( _id , _tf ) => Transform . createIdentity ( ) } ) ; } ) ; } ) ; it ( "should be dispatched to two views sharing the same display style" , async ( ) => { const v1 = await testBim . views . load ( "0x34" ) as SpatialViewState ; const v2 = v1 . clone ( ) ; v2 . displayStyle = v1 . displayStyle ; const div2 = document . createElement ( "div" ) ; div2 . style . width = div2 . style . height = "50px" ; document . body . appendChild ( div2 ) ; vp = ScreenViewport . create ( viewDiv , v1 ) ; const vp2 = ScreenViewport . create ( div2 , v2 ) ; vp . renderFrame ( ) ; vp2 . renderFrame ( ) ; expect ( vp . renderPlanValid ) . to . be . true ; expect ( vp2 . renderPlanValid ) . to . be . true ; vp . viewFlags = vp . viewFlags . with ( "transparency" , ! vp . viewFlags . transparency ) ; expect ( vp . renderPlanValid ) . to . be . false ; expect ( vp2 . renderPlanValid ) . to . be . false ; vp . renderFrame ( ) ; vp2 . renderFrame ( ) ; expect ( vp . renderPlanValid ) . to . be . true ; expect ( vp2 . renderPlanValid ) . to . be . true ; vp2 . dispose ( ) ; document . body . removeChild ( div2 ) ; } ) ; it ( "should load subcategories for all displayed categories" , async ( ) => { const subcats = testImodel . subcategories ; subcats . onIModelConnectionClose ( ) ; vp = ScreenViewport . create ( viewDiv , await testImodel . views . load ( id64 ( 0x17 ) ) ) ; expect ( vp . view . viewsCategory ( id64 ( 0x07 ) ) ) . to . be . true ; expect ( subcats . getSubCategories ( id64 ( 0x07 ) ) ) . not . to . be . undefined ; expect ( vp . view . viewsCategory ( id64 ( 0x01 ) ) ) . to . be . false ; expect ( vp . view . viewsCategory ( id64 ( 0x03 ) ) ) . to . be . false ; expect ( vp . view . viewsCategory ( id64 ( 0x05 ) ) ) . to . be . false ; expect ( vp . view . viewsCategory ( id64 ( 0x1a ) ) ) . to . be . false ; expect ( vp . view . viewsCategory ( id64 ( 0x1c ) ) ) . to . be . false ; const waitForSubCats = async ( catIds ) : Promise < void > => { for ( const catId of Id64 . iterable ( catIds ) ) expect ( subcats . getSubCategories ( catId ) ) . to . be . undefined ; for ( let i = 1 ; i < 16 ; i ++ ) { await BeDuration . wait ( 250 ) ; let numLoaded = 0 ; for ( const catId of Id64 . iterable ( catIds ) ) { if ( subcats . getSubCategories ( catId ) !== undefined ) ++ numLoaded ; } if ( 0 !== numLoaded ) { expect ( numLoaded ) . to . equal ( Id64 . sizeOf ( catIds ) ) ; break ; } } for ( const catId of Id64 . iterable ( catIds ) ) expect ( subcats . getSubCategories ( catId ) ) . not . to . be . undefined ; } ; vp . changeCategoryDisplay ( id64 ( 0x01 ) , true ) ; await waitForSubCats ( id64 ( 0x01 ) ) ; vp . changeCategoryDisplay ( [ id64 ( 0x03 ) , id64 ( 0x05 ) ] , true ) ; await waitForSubCats ( [ id64 ( 0x03 ) , id64 ( 0x05 ) ] ) ; vp . changeCategoryDisplay ( id64 ( 0x1a ) , true ) ; vp . changeCategoryDisplay ( id64 ( 0x1c ) , true ) ; await waitForSubCats ( [ id64 ( 0x1c ) , id64 ( 0x1a ) ] ) ; } ) ; } ) ;