@@ -907,4 +907,190 @@ describe('AnnotationRow component', () => {
907
907
expect ( screen . queryByText ( 'Show more' ) ) . not . toBeInTheDocument ( ) ;
908
908
} ) ;
909
909
} ) ;
910
+
911
+ describe ( 'allows keyboard navigation' , ( ) => {
912
+ describe ( 'within a plain annotation text' , ( ) => {
913
+ const annotation = {
914
+ id : 'http://example.com/manifest/canvas/1/annotation-page/1/annotation/1' ,
915
+ canvasId : 'http://example.com/manifest/canvas/1' ,
916
+ motivation : [ 'supplementing' ] ,
917
+ time : { start : 0 , end : 10 } ,
918
+ value : [
919
+ {
920
+ format : 'text/plain' ,
921
+ purpose : [ 'supplementing' ] ,
922
+ value : `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`
923
+ } ,
924
+ {
925
+ format : 'text/plain' ,
926
+ purpose : [ 'supplementing' ] ,
927
+ value : `Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.`
928
+ } ,
929
+ ]
930
+ } ;
931
+
932
+ beforeEach ( ( ) => {
933
+ // Mock Canvas, getComputedStyle, and clientWidth of annotationTextRef for a controlled test
934
+ jest . spyOn ( window , 'getComputedStyle' ) . mockImplementation ( ( ele ) => ( {
935
+ lineHeight : '24px' ,
936
+ fontSize : '16px' ,
937
+ font : '16px / 24px "Open Sans", sans-serif' ,
938
+ } ) ) ;
939
+ Object . defineProperty ( HTMLCanvasElement . prototype , 'getContext' , {
940
+ value : jest . fn ( ( ) => ( {
941
+ measureText : jest . fn ( ( texts ) => ( { width : texts . length * 10 } ) ) ,
942
+ } ) ) ,
943
+ } ) ;
944
+ Object . defineProperty ( HTMLElement . prototype , 'clientWidth' , {
945
+ configurable : true ,
946
+ get : jest . fn ( ( ) => 800 ) ,
947
+ } ) ;
948
+
949
+ render ( < AnnotationRow
950
+ { ...props }
951
+ showMoreSettings = { { enableShowMore : true , textLineLimit : 6 } }
952
+ annotation = { annotation }
953
+ /> ) ;
954
+ } ) ;
955
+
956
+ test ( 'renders successfully' , ( ) => {
957
+ expect ( screen . getByTestId ( 'annotation-row' ) ) . toBeInTheDocument ( ) ;
958
+ expect ( screen . queryAllByTestId ( 'annotation-tag-0' ) . length ) . toBe ( 0 ) ;
959
+
960
+ expect ( screen . getByTestId ( 'annotation-start-time' ) ) . toHaveTextContent ( '00:00:00.000' ) ;
961
+ expect ( screen . queryByTestId ( 'annotation-end-time' ) ) . toHaveTextContent ( '00:00:10.000' ) ;
962
+
963
+ expect ( screen . queryByTestId ( 'annotation-text-0' ) ) . toBeInTheDocument ( ) ;
964
+ expect ( screen . getByTestId ( 'annotation-text-0' ) . textContent . length ) . toBeGreaterThan ( 0 ) ;
965
+ } ) ;
966
+
967
+ test ( 'activates the annotation on Enter keypress when focused' , ( ) => {
968
+ screen . getByTestId ( 'annotation-row' ) . focus ( ) ;
969
+ fireEvent . keyDown ( screen . getByTestId ( 'annotation-row' ) , { key : 'Enter' , keyCode : 13 } ) ;
970
+
971
+ expect ( checkCanvasMock ) . toHaveBeenCalled ( ) ;
972
+ } ) ;
973
+
974
+ test ( 'activates the annotation on Space keypress when focused' , ( ) => {
975
+ screen . getByTestId ( 'annotation-row' ) . focus ( ) ;
976
+ fireEvent . keyDown ( screen . getByTestId ( 'annotation-row' ) , { key : ' ' , keyCode : 32 } ) ;
977
+
978
+ expect ( checkCanvasMock ) . toHaveBeenCalledTimes ( 1 ) ;
979
+ } ) ;
980
+
981
+ test ( 'does nothing on Tab keypress' , ( ) => {
982
+ screen . getByTestId ( 'annotation-row' ) . focus ( ) ;
983
+ fireEvent . keyDown ( screen . getByTestId ( 'annotation-row' ) , { key : 'Tab' , keyCode : 9 } ) ;
984
+
985
+ expect ( screen . getByTestId ( 'annotation-row' ) ) . toHaveFocus ( ) ;
986
+ } ) ;
987
+ } ) ;
988
+
989
+ describe ( 'within an annotation text with links' , ( ) => {
990
+ const annotation = {
991
+ id : 'http://example.com/manifest/canvas/1/annotation-page/1/annotation/1' ,
992
+ canvasId : 'http://example.com/manifest/canvas/1' ,
993
+ motivation : [ 'supplementing' ] ,
994
+ time : { start : 0 , end : 10 } ,
995
+ value : [
996
+ {
997
+ format : 'text/plain' ,
998
+ purpose : [ 'supplementing' ] ,
999
+ value : `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. <sup><a href="https://example1.com">t</a></sup>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`
1000
+ } ,
1001
+ {
1002
+ format : 'text/plain' ,
1003
+ purpose : [ 'supplementing' ] ,
1004
+ value : `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis<sup><a href="https://example2.com">t</a></sup> nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`
1005
+ } ,
1006
+ {
1007
+ format : 'text/plain' ,
1008
+ purpose : [ 'supplementing' ] ,
1009
+ value : `Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in <sup><a href="https://example3.com">t</a></sup>reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`
1010
+ } ,
1011
+ ]
1012
+ } ;
1013
+
1014
+ beforeEach ( ( ) => {
1015
+ // Mock Canvas, getComputedStyle, and clientWidth of annotationTextRef for a controlled test
1016
+ jest . spyOn ( window , 'getComputedStyle' ) . mockImplementation ( ( ele ) => ( {
1017
+ lineHeight : '24px' ,
1018
+ fontSize : '16px' ,
1019
+ font : '16px / 24px "Open Sans", sans-serif' ,
1020
+ } ) ) ;
1021
+ Object . defineProperty ( HTMLCanvasElement . prototype , 'getContext' , {
1022
+ value : jest . fn ( ( ) => ( {
1023
+ measureText : jest . fn ( ( texts ) => ( { width : texts . length * 10 } ) ) ,
1024
+ } ) ) ,
1025
+ } ) ;
1026
+ Object . defineProperty ( HTMLElement . prototype , 'clientWidth' , {
1027
+ configurable : true ,
1028
+ get : jest . fn ( ( ) => 800 ) ,
1029
+ } ) ;
1030
+
1031
+ render ( < AnnotationRow
1032
+ { ...props }
1033
+ showMoreSettings = { { enableShowMore : true , textLineLimit : 6 } }
1034
+ annotation = { annotation }
1035
+ /> ) ;
1036
+ } ) ;
1037
+
1038
+ test ( 'renders successfully with links and \'Show more\' button' , ( ) => {
1039
+ expect ( screen . getByTestId ( 'annotation-row' ) ) . toBeInTheDocument ( ) ;
1040
+ expect ( screen . queryAllByTestId ( 'annotation-tag-0' ) . length ) . toBe ( 0 ) ;
1041
+
1042
+ expect ( screen . getByTestId ( 'annotation-start-time' ) ) . toHaveTextContent ( '00:00:00.000' ) ;
1043
+ expect ( screen . queryByTestId ( 'annotation-end-time' ) ) . toHaveTextContent ( '00:00:10.000' ) ;
1044
+
1045
+ expect ( screen . queryByTestId ( 'annotation-text-0' ) ) . toBeInTheDocument ( ) ;
1046
+ expect ( screen . getByTestId ( 'annotation-text-0' ) . textContent . length ) . toBeGreaterThan ( 0 ) ;
1047
+
1048
+ // Contains links and a show more button
1049
+ expect ( screen . getByTestId ( 'annotation-text-0' ) . querySelectorAll ( 'a' ) . length ) . toBeGreaterThan ( 0 ) ;
1050
+ expect ( screen . queryAllByTestId ( 'annotation-show-more-0' ) . length ) . toBe ( 1 ) ;
1051
+ expect ( screen . queryByText ( 'Show more' ) ) . toBeInTheDocument ( ) ;
1052
+
1053
+ } ) ;
1054
+
1055
+ test ( 'activates the annotation on Enter keypress when focused' , ( ) => {
1056
+ screen . getByTestId ( 'annotation-row' ) . focus ( ) ;
1057
+ fireEvent . keyDown ( screen . getByTestId ( 'annotation-row' ) , { key : 'Enter' , keyCode : 13 } ) ;
1058
+
1059
+ expect ( checkCanvasMock ) . toHaveBeenCalled ( ) ;
1060
+ } ) ;
1061
+
1062
+ test ( 'activates the annotation on Space keypress when focused' , ( ) => {
1063
+ screen . getByTestId ( 'annotation-row' ) . focus ( ) ;
1064
+ fireEvent . keyDown ( screen . getByTestId ( 'annotation-row' ) , { key : ' ' , keyCode : 32 } ) ;
1065
+
1066
+ expect ( checkCanvasMock ) . toHaveBeenCalledTimes ( 1 ) ;
1067
+ } ) ;
1068
+
1069
+ test ( 'moves focus to first link on Tab keypress' , ( ) => {
1070
+ screen . getByTestId ( 'annotation-row' ) . focus ( ) ;
1071
+ fireEvent . keyDown ( screen . getByTestId ( 'annotation-row' ) , { key : 'Tab' , keyCode : 9 } ) ;
1072
+
1073
+ expect ( screen . getByTestId ( 'annotation-row' ) ) . not . toHaveFocus ( ) ;
1074
+ expect ( screen . getByTestId ( 'annotation-text-0' ) . querySelectorAll ( 'a' ) [ 0 ] ) . toHaveFocus ( ) ;
1075
+ } ) ;
1076
+
1077
+ test ( 'moves focus to previous link on Shift+Tab keypress' , ( ) => {
1078
+ screen . getByTestId ( 'annotation-row' ) . focus ( ) ;
1079
+ // Press Tab key twice to move focus to show more button
1080
+ fireEvent . keyDown ( screen . getByTestId ( 'annotation-row' ) , { key : 'Tab' , keyCode : 9 } ) ;
1081
+ fireEvent . keyDown ( screen . getByTestId ( 'annotation-row' ) , { key : 'Tab' , keyCode : 9 } ) ;
1082
+
1083
+ // Focus is moved to the 'Show more' button
1084
+ expect ( screen . getByTestId ( 'annotation-row' ) ) . not . toHaveFocus ( ) ;
1085
+ expect ( screen . getByText ( 'Show more' ) ) . toHaveFocus ( ) ;
1086
+
1087
+ // Press Shift+Tab key
1088
+ fireEvent . keyDown ( screen . getByTestId ( 'annotation-row' ) , { key : 'Tab' , keyCode : 9 , shiftKey : true } ) ;
1089
+
1090
+ // Focus is moved to the link text
1091
+ expect ( screen . getByTestId ( 'annotation-text-0' ) . querySelectorAll ( 'a' ) [ 0 ] ) . toHaveFocus ( ) ;
1092
+ expect ( screen . getByText ( 'Show more' ) ) . not . toHaveFocus ( ) ;
1093
+ } ) ;
1094
+ } ) ;
1095
+ } ) ;
910
1096
} ) ;
0 commit comments