import { assert } from "chai" ; import { ReactWrapper } from "enzyme" ; import * as React from "react" ; import * as sinon from "sinon" ; import { HTMLInputProps , Keys , MenuItem } from "@blueprintjs/core" ; import { areFilmsEqual , createFilm , filterFilm , IFilm , renderFilm , TOP_100_FILMS , } from "../../docs-app/src/common/films" ; import { IListItemsProps } from "../src" ; export function selectComponentSuite < P extends IListItemsProps < IFilm > , S > ( render , findInput = wrapper => wrapper . find ( "input" ) as ReactWrapper < HTMLInputProps > , findItems = wrapper => wrapper . find ( "a" ) ) { const testProps = { itemPredicate : filterFilm , itemRenderer : sinon . spy ( renderFilm ) , items : TOP_100_FILMS . slice ( 0 , 20 ) , itemsEqual : areFilmsEqual , onActiveItemChange : sinon . spy ( ) , onItemSelect : sinon . spy ( ) , onQueryChange : sinon . spy ( ) , query : "19" , } ; beforeEach ( ( ) => { testProps . itemRenderer . resetHistory ( ) ; testProps . onActiveItemChange . resetHistory ( ) ; testProps . onItemSelect . resetHistory ( ) ; testProps . onQueryChange . resetHistory ( ) ; } ) ; describe ( "common behavior" , ( ) => { it ( "itemRenderer is called for each child" , ( ) => { const wrapper = render ( testProps ) ; assert . equal ( wrapper . find ( MenuItem ) . length , 15 , "re-render" ) ; wrapper . setProps ( { query : "1999" } ) ; wrapper . update ( ) ; assert . equal ( wrapper . find ( MenuItem ) . length , 2 , "re-render" ) ; } ) ; it ( "renders noResults when given empty list" , ( ) => { const wrapper = render ( { ... testProps , items : [ ] , noResults : < address /> }); assert . lengthOf ( wrapper . find ( "address" ) , 1 , "should find noResults" ) ; } ) ; it ( "renders noResults when filtering returns empty list" , ( ) => { const wrapper = render ( { ... testProps , noResults : < address />, query : "non-existent film name" , } ) ; assert . lengthOf ( wrapper . find ( "address" ) , 1 , "should find noResults" ) ; } ) ; it ( "clicking item invokes onItemSelect and changes active item" , ( ) => { const wrapper = render ( testProps ) ; findItems ( wrapper ) . at ( 4 ) . simulate ( "click" ) ; assert . strictEqual ( testProps . onItemSelect . args [ 0 ] [ 0 ] . rank , 6 , "onItemSelect" ) ; assert . strictEqual ( testProps . onActiveItemChange . args [ 0 ] [ 0 ] . rank , 6 , "onActiveItemChange" ) ; } ) ; it ( "clicking item resets state when resetOnSelect=true" , ( ) => { const wrapper = render ( { ... testProps , query : "19" , resetOnQuery : false , resetOnSelect : true , } ) ; findItems ( wrapper ) . at ( 3 ) . simulate ( "click" ) ; const ranks = testProps . onActiveItemChange . args . map ( args => ( args [ 0 ] as IFilm ) . rank ) ; assert . deepEqual ( ranks , [ 5 , 1 ] ) ; assert . strictEqual ( testProps . onQueryChange . lastCall . args [ 0 ] , "" ) ; } ) ; it ( "querying does not reset active item when resetOnQuery=false" , ( ) => { const wrapper = render ( { ... testProps , query : "19" , resetOnQuery : false } ) ; wrapper . setProps ( { query : "199" } ) ; assert . strictEqual ( testProps . onActiveItemChange . lastCall , null ) ; } ) ; it ( "querying resets active item when resetOnQuery=true" , ( ) => { const wrapper = render ( { ... testProps , query : "19" , resetOnQuery : true } ) ; wrapper . setProps ( { query : "199" } ) ; assert . strictEqual ( testProps . onActiveItemChange . lastCall . args [ 0 ] . rank , 1 ) ; } ) ; it ( "querying resets active item if it does not match" , ( ) => { const wrapper = render ( { ... testProps , query : "19" , resetOnQuery : false } ) ; wrapper . setProps ( { query : "Forrest" } ) ; assert . strictEqual ( testProps . onActiveItemChange . lastCall . args [ 0 ] . title , "Forrest Gump" ) ; } ) ; } ) ; describe ( "keyboard" , ( ) => { it ( "arrow down invokes onActiveItemChange with next filtered item" , ( ) => { const wrapper = render ( testProps ) ; findInput ( wrapper ) . simulate ( "keydown" , { keyCode : Keys . ARROW_DOWN } ) . simulate ( "keydown" , { keyCode : Keys . ARROW_DOWN } ) ; assert . equal ( ( testProps . onActiveItemChange . lastCall . args [ 0 ] as IFilm ) . rank , 3 ) ; } ) ; it ( "arrow up invokes onActiveItemChange with previous filtered item" , ( ) => { const wrapper = render ( testProps ) ; findInput ( wrapper ) . simulate ( "keydown" , { keyCode : Keys . ARROW_UP } ) ; assert . equal ( ( testProps . onActiveItemChange . lastCall . args [ 0 ] as IFilm ) . rank , 20 ) ; } ) ; it ( "arrow up/down does not invokes onActiveItemChange, when all items are disabled" , ( ) => { const wrapper = render ( { ... testProps , itemDisabled : ( ) => true } ) ; findInput ( wrapper ) . simulate ( "keydown" , { keyCode : Keys . ARROW_DOWN } ) ; assert . isNull ( testProps . onActiveItemChange . lastCall ) ; findInput ( wrapper ) . simulate ( "keyup" , { keyCode : Keys . ARROW_UP } ) ; assert . isNull ( testProps . onActiveItemChange . lastCall ) ; } ) ; it ( "enter invokes onItemSelect with active item" , ( ) => { const wrapper = render ( testProps ) ; findInput ( wrapper ) . simulate ( "keydown" , { keyCode : Keys . ENTER } ) ; findInput ( wrapper ) . simulate ( "keyup" , { keyCode : Keys . ENTER } ) ; const activeItem = testProps . onActiveItemChange . lastCall . args [ 0 ] ; assert . equal ( testProps . onItemSelect . lastCall . args [ 0 ] , activeItem ) ; } ) ; } ) ; describe ( "create" , ( ) => { const testCreateProps = { ... testProps , createNewItemFromQuery : sinon . spy ( ) , createNewItemRenderer : ( ) => < textarea />, noResults : < address />, } ; beforeEach ( ( ) => { testCreateProps . createNewItemFromQuery . resetHistory ( ) ; } ) ; it ( "doesn't render create item if input is empty" , ( ) => { const wrapper = render ( { ... testCreateProps , query : "" , } ) ; assert . lengthOf ( findCreateItem ( wrapper ) , 0 , "should not find createItem" ) ; } ) ; it ( "doesn't render create item if query is non-empty and matches one of the items" , ( ) => { const EXISTING_FILM_TITLE = TOP_100_FILMS [ 0 ] . title ; const wrapper = render ( { ... testCreateProps , createNewItemFromQuery : createFilm , query : EXISTING_FILM_TITLE , } ) ; assert . lengthOf ( wrapper . find ( "address" ) , 0 , "should not find noResults" ) ; assert . lengthOf ( findCreateItem ( wrapper ) , 0 , "should not find createItem" ) ; } ) ; it ( "renders create item if query is not empty and doesn't match any items exactly" , ( ) => { const wrapper = render ( { ... testCreateProps , query : TOP_100_FILMS [ 0 ] . title + " a few extra chars" , } ) ; assert . lengthOf ( wrapper . find ( "address" ) , 0 , "should not find noResults" ) ; assert . lengthOf ( findCreateItem ( wrapper ) , 1 , "should find createItem" ) ; } ) ; it ( "renders create item if filtering returns empty list" , ( ) => { const wrapper = render ( { ... testCreateProps , query : "non-existent film name" , } ) ; assert . lengthOf ( wrapper . find ( "address" ) , 0 , "should not find noResults" ) ; assert . lengthOf ( findCreateItem ( wrapper ) , 1 , "should find createItem" ) ; } ) ; it ( "enter invokes createNewItemFromQuery" , ( ) => { const wrapper = render ( { ... testCreateProps , query : "non-existent film name" , } ) ; findInput ( wrapper ) . simulate ( "keyup" , { keyCode : Keys . ENTER } ) ; assert . equal ( testCreateProps . createNewItemFromQuery . args [ 0 ] [ 0 ] , "non-existent film name" ) ; } ) ; it ( "when create item is rendered, arrow down invokes onActiveItemChange with activeItem=null and isCreateNewItem=true" , ( ) => { const wrapper = render ( { ... testCreateProps , query : TOP_100_FILMS [ 0 ] . title , } ) ; findInput ( wrapper ) . simulate ( "keydown" , { keyCode : Keys . ARROW_DOWN } ) ; assert . equal ( testProps . onActiveItemChange . lastCall . args [ 0 ] , null ) ; assert . equal ( testProps . onActiveItemChange . lastCall . args [ 1 ] , true ) ; findInput ( wrapper ) . simulate ( "keydown" , { keyCode : Keys . ARROW_DOWN } ) ; assert . equal ( ( testProps . onActiveItemChange . lastCall . args [ 0 ] as IFilm ) . rank , TOP_100_FILMS [ 0 ] . rank ) ; assert . equal ( testProps . onActiveItemChange . lastCall . args [ 1 ] , false ) ; } ) ; it ( "when create item is rendered, arrow up invokes onActiveItemChange with an `ICreateNewItem`" , ( ) => { const wrapper = render ( { ... testCreateProps , query : TOP_100_FILMS [ 0 ] . title , } ) ; findInput ( wrapper ) . simulate ( "keydown" , { keyCode : Keys . ARROW_UP } ) ; assert . equal ( testProps . onActiveItemChange . lastCall . args [ 0 ] , null ) ; assert . equal ( testProps . onActiveItemChange . lastCall . args [ 1 ] , true ) ; findInput ( wrapper ) . simulate ( "keydown" , { keyCode : Keys . ARROW_UP } ) ; assert . equal ( ( testProps . onActiveItemChange . lastCall . args [ 0 ] as IFilm ) . rank , TOP_100_FILMS [ 0 ] . rank ) ; assert . equal ( testProps . onActiveItemChange . lastCall . args [ 1 ] , false ) ; } ) ; it ( "when create item is rendered, updating the query to exactly match one of the items hides the create item" , ( ) => { const wrapper = render ( { ... testCreateProps , createNewItemFromQuery : createFilm , query : "non-empty, non-matching initial value" , } ) ; assert . lengthOf ( findCreateItem ( wrapper ) , 1 , "should find createItem" ) ; const EXISTING_FILM_TITLE = TOP_100_FILMS [ 0 ] . title ; findInput ( wrapper ) . simulate ( "change" , { target : { value : EXISTING_FILM_TITLE } } ) ; assert . lengthOf ( findCreateItem ( wrapper ) , 0 , "should not find createItem" ) ; } ) ; } ) ; function findCreateItem ( wrapper ) { return wrapper . find ( "textarea" ) ; } }