const { describe , it } = intern . getInterface ( 'bdd' ) ; const { assert } = intern . getPlugin ( 'chai' ) ; import * as sinon from 'sinon' ; import { tsx } from '@dojo/framework/core/vdom' ; import { renderer , assertion , wrap } from '@dojo/framework/testing/renderer' ; import { createResourceTemplate } from '@dojo/framework/core/middleware/resources' ; import { Keys } from '../../../common/util' ; import { stubEvent , noop } from '../../../common/tests/support/test-helpers' ; import Tree , { TreeNode , TreeNodeOption } from '../../index' ; import * as css from '../../../theme/default/tree.m.css' ; import { simpleTree } from './TreeData.mock' ; import LoadingIndicator from '../../../loading-indicator' ; const WrappedRoot = wrap ( 'ol' ) ; const WrappedListItem1 = wrap ( 'li' ) ; const WrappedListItem2 = wrap ( 'li' ) ; const WrappedNode1 = wrap ( TreeNode ) ; const WrappedNode2 = wrap ( TreeNode ) ; const WrappedNode3 = wrap ( TreeNode ) ; function generateNodeOptions ( value , hasChildren = false ) { return { id : value , parent : '' , value : value , hasChildren } ; } const defaultNodeProps = { activeNode : undefined , checkable : false , selectable : false , checked : false , disabled : false , expanded : false , selected : false , value : undefined , parentSelection : false , onActive : noop , onValue : noop , onCheck : noop , onExpand : noop } ; const baseAssertion = assertion ( ( ) => ( < WrappedRoot role = { 'tree' } classes = { [ css . root , css . nodeParent , undefined ] } onkeydown = { noop } tabIndex = { 0 } / > ) ) ; const simpleTreeAssertion = baseAssertion . replaceChildren ( WrappedRoot , ( ) => [ < WrappedListItem1 role = { 'treeitem' } classes = { [ css . node , css . leaf , false , false ] } > < WrappedNode1 { ... defaultNodeProps } node = { simpleTree [ 0 ] } theme = { undefined } classes = { undefined } variant = { undefined } > { ( ) => 'node-1' } < /WrappedNode1> < /WrappedListItem1>, < WrappedListItem2 role = { 'treeitem' } classes = { [ css . node , false , false , false ] } > < WrappedNode2 { ... defaultNodeProps } node = { simpleTree [ 2 ] } theme = { undefined } classes = { undefined } variant = { undefined } > { ( ) => 'node-2' } < /WrappedNode2> < /WrappedListItem2> ] ) ; describe ( 'Tree' , ( ) => { it ( 'renders with no data' , ( ) => { const r = renderer ( ( ) => < Tree resource = { { data : [ ] , id : 'test' , idKey : 'value' } } />); r . expect ( baseAssertion ) ; } ) ; it ( 'renders data' , ( ) => { const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } />)); r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion ) ; } ) ; it ( 'renders disabled nodes' , ( ) => { const disabledNodes = [ simpleTree [ 0 ] . id ] ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } disabledIds = { disabledNodes } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion . setProperty ( WrappedNode1 , 'disabled' , true ) . setProperty ( WrappedNode2 , 'disabled' , false ) ) ; } ) ; it ( 'can navigate active node with keyboard' , ( ) => { const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } />)); r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion ) ; r . property ( WrappedNode1 , 'onActive' ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion . setProperty ( WrappedNode1 , 'activeNode' , simpleTree [ 0 ] . id ) . setProperty ( WrappedNode2 , 'activeNode' , simpleTree [ 0 ] . id ) ) ; r . property ( WrappedRoot , 'onkeydown' , { ... stubEvent , which : Keys . Down } ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion . setProperty ( WrappedNode1 , 'activeNode' , simpleTree [ 2 ] . id ) . setProperty ( WrappedNode2 , 'activeNode' , simpleTree [ 2 ] . id ) ) ; r . property ( WrappedRoot , 'onkeydown' , { ... stubEvent , which : Keys . Up } ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion . setProperty ( WrappedNode1 , 'activeNode' , simpleTree [ 0 ] . id ) . setProperty ( WrappedNode2 , 'activeNode' , simpleTree [ 0 ] . id ) ) ; r . property ( WrappedRoot , 'onkeydown' , { ... stubEvent , which : Keys . Up } ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion . setProperty ( WrappedNode1 , 'activeNode' , simpleTree [ 2 ] . id ) . setProperty ( WrappedNode2 , 'activeNode' , simpleTree [ 2 ] . id ) ) ; } ) ; describe ( 'ExpandedNodes' , ( ) => { const expandedAssertion = simpleTreeAssertion . setProperty ( WrappedNode1 , 'expanded' , true ) . setProperty ( WrappedNode2 , 'expanded' , false ) . insertAfter ( WrappedNode1 , ( ) => ( < ol role = { 'group' } classes = { [ null , css . nodeParent , null ] } onkeydown = { noop } tabIndex = { 0 } > < li role = { 'treeitem' } classes = { [ css . node , false , false , false ] } > < WrappedNode3 { ... defaultNodeProps } expanded = { false } node = { simpleTree [ 1 ] } theme = { undefined } classes = { undefined } variant = { undefined } > { ( ) => '' } < /WrappedNode3> < /li> < /ol>) as any); it ( 'renders with expanded nodes' , ( ) => { const expandedNodes = [ simpleTree [ 0 ] . id ] ; const nodeProps = { ... defaultNodeProps , expanded : false } ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } expandedIds = { expandedNodes } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion . setProperty ( WrappedNode1 , 'expanded' , true ) . setProperty ( WrappedNode2 , 'expanded' , false ) . insertAfter ( WrappedNode1 , ( ) => ( < ol classes = { [ null , css . nodeParent , null ] } onkeydown = { noop } tabIndex = { 0 } role = { 'group' } > < li role = { 'treeitem' } classes = { [ css . node , false , false , false ] } > < TreeNode { ... nodeProps } node = { simpleTree [ 1 ] } theme = { undefined } classes = { undefined } variant = { undefined } > { ( ) => '' } < /TreeNode> < /li> < /ol>) as any) . setProperty ( WrappedListItem1 , 'classes' , [ css . node , css . leaf , false , false ] ) ) ; } ) ; it ( 'renders with nodes initially expanded' , ( ) => { const expandedNodes = [ simpleTree [ 0 ] . id ] ; const nodeProps = { ... defaultNodeProps , expanded : false } ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } initialExpanded = { expandedNodes } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion . setProperty ( WrappedNode1 , 'expanded' , true ) . setProperty ( WrappedNode2 , 'expanded' , false ) . insertAfter ( WrappedNode1 , ( ) => ( < ol classes = { [ null , css . nodeParent , null ] } onkeydown = { noop } tabIndex = { 0 } role = { 'group' } > < li role = { 'treeitem' } classes = { [ css . node , false , false , false ] } > < TreeNode { ... nodeProps } node = { simpleTree [ 1 ] } theme = { undefined } classes = { undefined } variant = { undefined } > { ( ) => '' } < /TreeNode> < /li> < /ol>) as any) . setProperty ( WrappedListItem1 , 'classes' , [ css . node , css . leaf , false , false ] ) ) ; } ) ; it ( 'raises events on expand/collapse' , ( ) => { const onExpand = sinon . spy ( ) ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } onExpand = { onExpand } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion ) ; r . property ( WrappedNode1 , 'onExpand' , true ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( expandedAssertion ) ; assert ( onExpand . calledWith ( [ simpleTree [ 0 ] . id ] ) ) ; onExpand . resetHistory ( ) ; r . property ( WrappedNode1 , 'onExpand' , false ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion . setProperty ( WrappedNode1 , 'expanded' , false ) . setProperty ( WrappedNode2 , 'expanded' , false ) ) ; assert ( onExpand . calledWith ( [ ] ) ) ; } ) ; it ( 'raises events on expand/collapse on keyboard navigation' , ( ) => { const onExpand = sinon . stub ( ) ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } onExpand = { onExpand } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( simpleTreeAssertion ) ; const nodeId = simpleTree [ 0 ] . id ; const activeAssertion = simpleTreeAssertion . setProperty ( WrappedNode1 , 'activeNode' , nodeId ) . setProperty ( WrappedNode2 , 'activeNode' , nodeId ) ; r . property ( WrappedNode1 , 'onActive' ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( activeAssertion ) ; r . property ( WrappedRoot , 'onkeydown' , { ... stubEvent , which : Keys . Right } ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( expandedAssertion . setProperty ( WrappedNode1 , 'activeNode' , nodeId ) . setProperty ( WrappedNode2 , 'activeNode' , nodeId ) . setProperty ( WrappedNode3 , 'activeNode' , nodeId ) ) ; assert ( onExpand . calledWith ( [ nodeId ] ) ) ; onExpand . resetHistory ( ) ; r . property ( WrappedRoot , 'onkeydown' , { ... stubEvent , which : Keys . Left } ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( activeAssertion ) ; assert ( onExpand . calledWith ( [ ] ) ) ; } ) ; it ( 'raises events on expand/collapse on keyboard navigation with controlled nodes' , ( ) => { const onExpand = sinon . stub ( ) ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } onExpand = { onExpand } expandedIds = { [ 'parent-1' ] } / > ) ) ; const nodeId = simpleTree [ 0 ] . id ; const activeExpandedAssertion = expandedAssertion . setProperty ( WrappedNode1 , 'activeNode' , nodeId ) . setProperty ( WrappedNode2 , 'activeNode' , nodeId ) . setProperty ( WrappedNode3 , 'activeNode' , nodeId ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( expandedAssertion ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( '' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( '' ) ] ) ; r . property ( WrappedNode1 , 'onActive' ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( activeExpandedAssertion ) ; r . property ( WrappedRoot , 'onkeydown' , { ... stubEvent , which : Keys . Left } ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( activeExpandedAssertion ) ; assert ( onExpand . calledWith ( [ ] ) ) ; } ) ; } ) ; describe ( 'CheckedNodes' , ( ) => { const checkableAssertion = simpleTreeAssertion . setProperty ( WrappedNode1 , 'checkable' , true ) . setProperty ( WrappedNode2 , 'checkable' , true ) ; it ( 'renders with checkable nodes' , ( ) => { const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } checkable = { true } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( checkableAssertion ) ; } ) ; it ( 'renders with checked nodes' , ( ) => { const checkedNodes = [ simpleTree [ 0 ] . id ] ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } checkable = { true } checkedIds = { checkedNodes } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( checkableAssertion . setProperty ( WrappedNode1 , 'checked' , true ) . setProperty ( WrappedNode2 , 'checked' , false ) ) ; } ) ; it ( 'renders with nodes initially checked' , ( ) => { const checkedNodes = [ simpleTree [ 0 ] . id ] ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } checkable = { true } initialChecked = { checkedNodes } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( checkableAssertion . setProperty ( WrappedNode1 , 'checked' , true ) . setProperty ( WrappedNode2 , 'checked' , false ) ) ; } ) ; it ( 'raises events on check/uncheck' , ( ) => { const onCheck = sinon . stub ( ) ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } checkable = { true } onCheck = { onCheck } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( checkableAssertion ) ; r . property ( WrappedNode1 , 'onCheck' , true ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( checkableAssertion . setProperty ( WrappedNode1 , 'checked' , true ) . setProperty ( WrappedNode2 , 'checked' , false ) ) ; assert ( onCheck . calledWith ( [ simpleTree [ 0 ] . id ] ) ) ; onCheck . resetHistory ( ) ; r . property ( WrappedNode1 , 'onCheck' , false ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( checkableAssertion . setProperty ( WrappedNode1 , 'checked' , false ) . setProperty ( WrappedNode2 , 'checked' , false ) ) ; assert ( onCheck . calledWith ( [ ] ) ) ; } ) ; } ) ; describe ( 'SelectedNode' , ( ) => { const selectableAssertion = simpleTreeAssertion . setProperty ( WrappedNode1 , 'selectable' , true ) . setProperty ( WrappedNode2 , 'selectable' , true ) . setProperty ( WrappedListItem1 , 'classes' , [ css . node , css . leaf , css . selectable , false ] ) . setProperty ( WrappedListItem2 , 'classes' , [ css . node , false , css . selectable , false ] ) ; it ( 'renders with selectable nodes' , ( ) => { const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } selectable = { true } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( selectableAssertion ) ; } ) ; it ( 'renders with selected node' , ( ) => { const selectedNode = simpleTree [ 0 ] . id ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } selectable = { true } value = { selectedNode } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( selectableAssertion . setProperty ( WrappedListItem1 , 'classes' , [ css . node , css . leaf , css . selectable , css . selected ] ) . setProperty ( WrappedNode1 , 'value' , selectedNode ) . setProperty ( WrappedNode2 , 'value' , selectedNode ) . setProperty ( WrappedNode1 , 'selected' , true ) ) ; } ) ; it ( 'raises events on select' , ( ) => { const onValue = sinon . stub ( ) ; const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } selectable = { true } onValue = { onValue } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( selectableAssertion ) ; const selectedNode = simpleTree [ 0 ] . id ; r . property ( WrappedNode1 , 'onValue' ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( selectableAssertion . setProperty ( WrappedListItem1 , 'classes' , [ css . node , css . leaf , css . selectable , css . selected ] ) . setProperty ( WrappedNode1 , 'value' , selectedNode ) . setProperty ( WrappedNode2 , 'value' , selectedNode ) . setProperty ( WrappedNode1 , 'selected' , true ) ) ; } ) ; it ( 'can select the active node with keyboard' , ( ) => { const r = renderer ( ( ) => ( < Tree resource = { { data : simpleTree , id : 'test' , idKey : 'value' } } selectable = { true } / > ) ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( selectableAssertion ) ; const nodeId = simpleTree [ 2 ] . id ; r . property ( WrappedNode2 , 'onActive' ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( selectableAssertion . setProperty ( WrappedNode1 , 'activeNode' , nodeId ) . setProperty ( WrappedNode2 , 'activeNode' , nodeId ) ) ; r . property ( WrappedRoot , 'onkeydown' , { ... stubEvent , which : Keys . Enter } ) ; r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( selectableAssertion . setProperty ( WrappedListItem2 , 'classes' , [ css . node , false , css . selectable , css . selected ] ) . setProperty ( WrappedNode1 , 'value' , nodeId ) . setProperty ( WrappedNode2 , 'value' , nodeId ) . setProperty ( WrappedNode2 , 'selected' , true ) . setProperty ( WrappedNode1 , 'activeNode' , nodeId ) . setProperty ( WrappedNode2 , 'activeNode' , nodeId ) ) ; } ) ; } ) ; describe ( 'Loading tree state' , ( ) => { const loadingAssertion = assertion ( ( ) => ( < LoadingIndicator theme = { undefined } classes = { undefined } variant = { undefined } / > ) ) ; it ( 'should have loading indicator when there is not data' , ( ) => { let resolvePromise = ( value ) => { } ; const dataPromise = new Promise ( ( res ) => { resolvePromise = res ; } ) ; const loadingTemplate = createResourceTemplate < TreeNodeOption > ( { idKey : 'id' , read : async ( request , { put } ) => { const data = await dataPromise ; put ( { data , total : simpleTree . length } , request ) ; } } ) ; const r = renderer ( ( ) => < Tree resource = { { template : loadingTemplate } } />); r . child ( WrappedNode1 , [ generateNodeOptions ( 'node-1' ) ] ) ; r . child ( WrappedNode2 , [ generateNodeOptions ( 'node-2' ) ] ) ; r . expect ( loadingAssertion ) ; resolvePromise ( simpleTree ) ; } ) ; } ) ; } ) ;