@@ -11,6 +11,7 @@ test('`model` (Object) has desired keys', function (t) {
1111 const keys = Object . keys ( app . model ) ;
1212 t . deepEqual ( keys , [ 'todos' , 'hash' ] , "`todos` and `hash` keys are present." ) ;
1313 t . true ( Array . isArray ( app . model . todos ) , "model.todos is an Array" )
14+ t . equal ( app . model . search , '' , "model.search defaults to empty string" ) ;
1415 t . end ( ) ;
1516} ) ;
1617
@@ -678,3 +679,203 @@ test('9. Routing > should allow me to display active/completed/all items',
678679 t . end ( ) ;
679680} ) ;
680681
682+ test ( '10. Search > model should have search field' , function ( t ) {
683+ t . equal ( typeof app . model . search , 'string' , "`search` field is available on model." ) ;
684+ t . equal ( app . model . search , '' , "initial search value is empty string." ) ;
685+ t . end ( ) ;
686+ } ) ;
687+
688+ test ( '10.1 Search > update SEARCH action should set search field' , function ( t ) {
689+ const model = { todos : [ ] , hash : '#/' } ;
690+ t . equal ( model . search , undefined , "search is not persisted on plain stored models." ) ;
691+
692+ const updated_model = app . update ( 'SEARCH' , model , "test" ) ;
693+ t . equal ( updated_model . search , "test" , "search field is updated to 'test'." ) ;
694+
695+ const cleared_model = app . update ( 'SEARCH' , updated_model , "" ) ;
696+ t . equal ( cleared_model . search , "" , "search field is cleared." ) ;
697+
698+ t . end ( ) ;
699+ } ) ;
700+
701+ test ( '10.2 Search > render_main should filter todos by search term' , function ( t ) {
702+ const model = {
703+ todos : [
704+ { id : 0 , title : "Buy milk" , done : false } ,
705+ { id : 1 , title : "Buy eggs" , done : false } ,
706+ { id : 2 , title : "Go to gym" , done : false }
707+ ] ,
708+ hash : '#/' ,
709+ search : ""
710+ } ;
711+
712+ document . getElementById ( id ) . appendChild ( app . render_main ( model , mock_signal ) ) ;
713+ t . equal ( document . querySelectorAll ( '.view' ) . length , 3 , "no search: 3 items" ) ;
714+ elmish . empty ( document . getElementById ( id ) ) ;
715+
716+ model . search = "buy" ;
717+ document . getElementById ( id ) . appendChild ( app . render_main ( model , mock_signal ) ) ;
718+ t . equal ( document . querySelectorAll ( '.view' ) . length , 2 , "search 'buy': 2 items" ) ;
719+ elmish . empty ( document . getElementById ( id ) ) ;
720+
721+ model . search = "gym" ;
722+ document . getElementById ( id ) . appendChild ( app . render_main ( model , mock_signal ) ) ;
723+ t . equal ( document . querySelectorAll ( '.view' ) . length , 1 , "search 'gym': 1 item" ) ;
724+ elmish . empty ( document . getElementById ( id ) ) ;
725+
726+ model . search = "nonexistent" ;
727+ document . getElementById ( id ) . appendChild ( app . render_main ( model , mock_signal ) ) ;
728+ t . equal ( document . querySelectorAll ( '.view' ) . length , 0 , "search 'nonexistent': 0 items" ) ;
729+ elmish . empty ( document . getElementById ( id ) ) ;
730+
731+ t . end ( ) ;
732+ } ) ;
733+
734+ test ( '10.3 Search > search should be case-insensitive' , function ( t ) {
735+ const model = {
736+ todos : [
737+ { id : 0 , title : "Buy Milk" , done : false } ,
738+ { id : 1 , title : "buy eggs" , done : false }
739+ ] ,
740+ hash : '#/' ,
741+ search : ""
742+ } ;
743+
744+ model . search = "BUY" ;
745+ document . getElementById ( id ) . appendChild ( app . render_main ( model , mock_signal ) ) ;
746+ t . equal ( document . querySelectorAll ( '.view' ) . length , 2 , "search 'BUY' matches both items" ) ;
747+ elmish . empty ( document . getElementById ( id ) ) ;
748+
749+ model . search = "MILK" ;
750+ document . getElementById ( id ) . appendChild ( app . render_main ( model , mock_signal ) ) ;
751+ t . equal ( document . querySelectorAll ( '.view' ) . length , 1 , "search 'MILK' matches 'Buy Milk'" ) ;
752+ elmish . empty ( document . getElementById ( id ) ) ;
753+
754+ t . end ( ) ;
755+ } ) ;
756+
757+ test ( '10.4 Search > search should work with route filtering (All + search)' , function ( t ) {
758+ const model = {
759+ todos : [
760+ { id : 0 , title : "Buy milk" , done : false } ,
761+ { id : 1 , title : "Buy eggs" , done : true } ,
762+ { id : 2 , title : "Go to gym" , done : false }
763+ ] ,
764+ hash : '#/' ,
765+ search : "buy"
766+ } ;
767+
768+ document . getElementById ( id ) . appendChild ( app . render_main ( model , mock_signal ) ) ;
769+ t . equal ( document . querySelectorAll ( '.view' ) . length , 2 , "All + search 'buy': 2 items" ) ;
770+ elmish . empty ( document . getElementById ( id ) ) ;
771+
772+ t . end ( ) ;
773+ } ) ;
774+
775+ test ( '10.5 Search > search should work with route filtering (Active + search)' , function ( t ) {
776+ const model = {
777+ todos : [
778+ { id : 0 , title : "Buy milk" , done : false } ,
779+ { id : 1 , title : "Buy eggs" , done : true } ,
780+ { id : 2 , title : "Go to gym" , done : false }
781+ ] ,
782+ hash : '#/active' ,
783+ search : "buy"
784+ } ;
785+
786+ document . getElementById ( id ) . appendChild ( app . render_main ( model , mock_signal ) ) ;
787+ t . equal ( document . querySelectorAll ( '.view' ) . length , 1 , "Active + search 'buy': 1 item (only 'Buy milk' is active)" ) ;
788+ elmish . empty ( document . getElementById ( id ) ) ;
789+
790+ t . end ( ) ;
791+ } ) ;
792+
793+ test ( '10.6 Search > search should work with route filtering (Completed + search)' , function ( t ) {
794+ const model = {
795+ todos : [
796+ { id : 0 , title : "Buy milk" , done : false } ,
797+ { id : 1 , title : "Buy eggs" , done : true } ,
798+ { id : 2 , title : "Go to gym" , done : false }
799+ ] ,
800+ hash : '#/completed' ,
801+ search : "buy"
802+ } ;
803+
804+ document . getElementById ( id ) . appendChild ( app . render_main ( model , mock_signal ) ) ;
805+ t . equal ( document . querySelectorAll ( '.view' ) . length , 1 , "Completed + search 'buy': 1 item (only 'Buy eggs' is completed)" ) ;
806+ elmish . empty ( document . getElementById ( id ) ) ;
807+
808+ t . end ( ) ;
809+ } ) ;
810+
811+ test ( '10.7 Search > render_footer count should reflect filtered results' , function ( t ) {
812+ const model = {
813+ todos : [
814+ { id : 0 , title : "Buy milk" , done : false } ,
815+ { id : 1 , title : "Buy eggs" , done : true } ,
816+ { id : 2 , title : "Go to gym" , done : false }
817+ ] ,
818+ hash : '#/' ,
819+ search : ""
820+ } ;
821+
822+ document . getElementById ( id ) . appendChild ( app . render_footer ( model ) ) ;
823+ let count = parseInt ( document . getElementById ( 'count' ) . textContent , 10 ) ;
824+ t . equal ( count , 2 , "no search: 2 items left" ) ;
825+ elmish . empty ( document . getElementById ( id ) ) ;
826+
827+ model . search = "buy" ;
828+ document . getElementById ( id ) . appendChild ( app . render_footer ( model ) ) ;
829+ count = parseInt ( document . getElementById ( 'count' ) . textContent , 10 ) ;
830+ t . equal ( count , 1 , "search 'buy': 1 item left ('Buy milk' is active)" ) ;
831+ elmish . empty ( document . getElementById ( id ) ) ;
832+
833+ t . end ( ) ;
834+ } ) ;
835+
836+ test ( '10.8 Search > render_footer clear completed should reflect filtered results' , function ( t ) {
837+ const model = {
838+ todos : [
839+ { id : 0 , title : "Buy milk" , done : false } ,
840+ { id : 1 , title : "Buy eggs" , done : true } ,
841+ { id : 2 , title : "Go to gym" , done : true }
842+ ] ,
843+ hash : '#/' ,
844+ search : ""
845+ } ;
846+
847+ document . getElementById ( id ) . appendChild ( app . render_footer ( model ) ) ;
848+ let completed_count = parseInt ( document . getElementById ( 'completed-count' ) . textContent , 10 ) ;
849+ t . equal ( completed_count , 2 , "no search: 2 completed items" ) ;
850+ elmish . empty ( document . getElementById ( id ) ) ;
851+
852+ model . search = "gym" ;
853+ document . getElementById ( id ) . appendChild ( app . render_footer ( model ) ) ;
854+ completed_count = parseInt ( document . getElementById ( 'completed-count' ) . textContent , 10 ) ;
855+ t . equal ( completed_count , 1 , "search 'gym': 1 completed item in results" ) ;
856+ elmish . empty ( document . getElementById ( id ) ) ;
857+
858+ t . end ( ) ;
859+ } ) ;
860+
861+ test ( '10.9 Search > view should render search input' , function ( t ) {
862+ const model = {
863+ todos : [ ] ,
864+ hash : '#/' ,
865+ search : ""
866+ } ;
867+
868+ document . getElementById ( id ) . appendChild ( app . view ( model ) ) ;
869+ const search_input = document . getElementById ( 'search-todo' ) ;
870+ t . notEqual ( search_input , null , "search input exists" ) ;
871+ t . equal ( search_input . getAttribute ( 'placeholder' ) , 'Search todos...' , "search input has correct placeholder" ) ;
872+ elmish . empty ( document . getElementById ( id ) ) ;
873+
874+ model . search = "test" ;
875+ document . getElementById ( id ) . appendChild ( app . view ( model ) ) ;
876+ const search_input_with_value = document . getElementById ( 'search-todo' ) ;
877+ t . equal ( search_input_with_value . value , 'test' , "search input displays search value" ) ;
878+ elmish . empty ( document . getElementById ( id ) ) ;
879+
880+ t . end ( ) ;
881+ } ) ;
0 commit comments