@@ -59,9 +59,6 @@ define(function (require, exports, module) {
5959 // (attaches checkboxHandler, inputHandler, etc.)
6060 if ( win && win . __setEditModeForTest ) {
6161 win . __setEditModeForTest ( false ) ;
62- }
63- _setMdEditMode ( true ) ;
64- if ( win && win . __setEditModeForTest ) {
6562 win . __setEditModeForTest ( true ) ;
6663 }
6764 await awaitsFor ( ( ) => {
@@ -73,7 +70,6 @@ define(function (require, exports, module) {
7370 }
7471
7572 async function _enterReaderMode ( ) {
76- _setMdEditMode ( false ) ;
7773 const win = _getMdIFrameWin ( ) ;
7874 if ( win && win . __setEditModeForTest ) {
7975 win . __setEditModeForTest ( false ) ;
@@ -991,5 +987,274 @@ define(function (require, exports, module) {
991987 } , 10000 ) ;
992988
993989 } ) ;
990+
991+ describe ( "UL/OL Toggle (List Type Switching)" , function ( ) {
992+
993+ async function _openMdFile ( fileName ) {
994+ await awaitsForDone ( SpecRunnerUtils . openProjectFiles ( [ fileName ] ) ,
995+ "open " + fileName ) ;
996+ await _waitForMdPreviewReady ( EditorManager . getActiveEditor ( ) ) ;
997+ }
998+
999+ function _placeCursorInElement ( el , offset ) {
1000+ const mdDoc = _getMdIFrameDoc ( ) ;
1001+ const win = _getMdIFrameWin ( ) ;
1002+ const range = mdDoc . createRange ( ) ;
1003+ const textNode = el . firstChild && el . firstChild . nodeType === Node . TEXT_NODE
1004+ ? el . firstChild : el ;
1005+ if ( textNode . nodeType === Node . TEXT_NODE ) {
1006+ range . setStart ( textNode , Math . min ( offset || 0 , textNode . textContent . length ) ) ;
1007+ } else {
1008+ range . setStart ( textNode , 0 ) ;
1009+ }
1010+ range . collapse ( true ) ;
1011+ const sel = win . getSelection ( ) ;
1012+ sel . removeAllRanges ( ) ;
1013+ sel . addRange ( range ) ;
1014+ }
1015+
1016+ function _findLiByText ( text ) {
1017+ const mdDoc = _getMdIFrameDoc ( ) ;
1018+ const items = mdDoc . querySelectorAll ( "#viewer-content li" ) ;
1019+ for ( const li of items ) {
1020+ if ( li . textContent . trim ( ) . includes ( text ) ) {
1021+ return li ;
1022+ }
1023+ }
1024+ return null ;
1025+ }
1026+
1027+ it ( "should clicking UL button when in OL switch list to unordered" , async function ( ) {
1028+ await _openMdFile ( "list-test.md" ) ;
1029+ await _enterReaderMode ( ) ;
1030+ await _enterEditMode ( ) ;
1031+
1032+ // Place cursor in an ordered list item
1033+ const olLi = _findLiByText ( "First ordered" ) ;
1034+ expect ( olLi ) . not . toBeNull ( ) ;
1035+ expect ( olLi . closest ( "ol" ) ) . not . toBeNull ( ) ;
1036+ _placeCursorInElement ( olLi , 0 ) ;
1037+
1038+ // Trigger selectionchange so toolbar updates
1039+ const mdDoc = _getMdIFrameDoc ( ) ;
1040+ mdDoc . dispatchEvent ( new Event ( "selectionchange" ) ) ;
1041+
1042+ // Click UL button
1043+ const ulBtn = mdDoc . getElementById ( "emb-ul" ) ;
1044+ expect ( ulBtn ) . not . toBeNull ( ) ;
1045+ ulBtn . dispatchEvent ( new MouseEvent ( "mousedown" , { bubbles : true } ) ) ;
1046+
1047+ // The list should now be a UL
1048+ await awaitsFor ( ( ) => {
1049+ return olLi . closest ( "ul" ) !== null && olLi . closest ( "ol" ) === null ;
1050+ } , "ordered list to switch to unordered" ) ;
1051+
1052+ await awaitsForDone ( CommandManager . execute ( Commands . FILE_CLOSE , { _forceClose : true } ) ,
1053+ "force close" ) ;
1054+ } , 10000 ) ;
1055+
1056+ it ( "should clicking OL button when in UL switch list to ordered" , async function ( ) {
1057+ await _openMdFile ( "list-test.md" ) ;
1058+ await _enterEditMode ( ) ;
1059+
1060+ // Place cursor in an unordered list item
1061+ const ulLi = _findLiByText ( "First item" ) ;
1062+ expect ( ulLi ) . not . toBeNull ( ) ;
1063+ expect ( ulLi . closest ( "ul" ) ) . not . toBeNull ( ) ;
1064+ _placeCursorInElement ( ulLi , 0 ) ;
1065+
1066+ const mdDoc = _getMdIFrameDoc ( ) ;
1067+ mdDoc . dispatchEvent ( new Event ( "selectionchange" ) ) ;
1068+
1069+ // Click OL button
1070+ const olBtn = mdDoc . getElementById ( "emb-ol" ) ;
1071+ expect ( olBtn ) . not . toBeNull ( ) ;
1072+ olBtn . dispatchEvent ( new MouseEvent ( "mousedown" , { bubbles : true } ) ) ;
1073+
1074+ // The list should now be an OL
1075+ await awaitsFor ( ( ) => {
1076+ return ulLi . closest ( "ol" ) !== null && ulLi . closest ( "ul" ) === null ;
1077+ } , "unordered list to switch to ordered" ) ;
1078+
1079+ await awaitsForDone ( CommandManager . execute ( Commands . FILE_CLOSE , { _forceClose : true } ) ,
1080+ "force close" ) ;
1081+ } , 10000 ) ;
1082+
1083+ it ( "should UL/OL toggle preserve list content and nesting" , async function ( ) {
1084+ await _openMdFile ( "list-test.md" ) ;
1085+ await _enterEditMode ( ) ;
1086+
1087+ // Find ordered list and remember its content
1088+ const olLi = _findLiByText ( "First ordered" ) ;
1089+ expect ( olLi ) . not . toBeNull ( ) ;
1090+ const ol = olLi . closest ( "ol" ) ;
1091+ const itemTexts = Array . from ( ol . querySelectorAll ( ":scope > li" ) )
1092+ . map ( li => li . textContent . trim ( ) ) ;
1093+ expect ( itemTexts . length ) . toBeGreaterThan ( 0 ) ;
1094+
1095+ _placeCursorInElement ( olLi , 0 ) ;
1096+ const mdDoc = _getMdIFrameDoc ( ) ;
1097+ mdDoc . dispatchEvent ( new Event ( "selectionchange" ) ) ;
1098+
1099+ // Switch to UL
1100+ mdDoc . getElementById ( "emb-ul" ) . dispatchEvent (
1101+ new MouseEvent ( "mousedown" , { bubbles : true } ) ) ;
1102+
1103+ await awaitsFor ( ( ) => olLi . closest ( "ul" ) !== null ,
1104+ "list to switch to UL" ) ;
1105+
1106+ // Verify content preserved
1107+ const newList = olLi . closest ( "ul" ) ;
1108+ const newTexts = Array . from ( newList . querySelectorAll ( ":scope > li" ) )
1109+ . map ( li => li . textContent . trim ( ) ) ;
1110+ expect ( newTexts ) . toEqual ( itemTexts ) ;
1111+
1112+ await awaitsForDone ( CommandManager . execute ( Commands . FILE_CLOSE , { _forceClose : true } ) ,
1113+ "force close" ) ;
1114+ } , 10000 ) ;
1115+
1116+
1117+ it ( "should toolbar UL button show active state when cursor in UL" , async function ( ) {
1118+ await _openMdFile ( "list-test.md" ) ;
1119+ await _enterEditMode ( ) ;
1120+
1121+ const ulLi = _findLiByText ( "First item" ) ;
1122+ _placeCursorInElement ( ulLi , 0 ) ;
1123+
1124+ const mdDoc = _getMdIFrameDoc ( ) ;
1125+ mdDoc . dispatchEvent ( new Event ( "selectionchange" ) ) ;
1126+
1127+ // Wait for toolbar state update
1128+ await awaitsFor ( ( ) => {
1129+ const ulBtn = mdDoc . getElementById ( "emb-ul" ) ;
1130+ return ulBtn && ulBtn . getAttribute ( "aria-pressed" ) === "true" ;
1131+ } , "UL button to show active state" ) ;
1132+
1133+ const olBtn = mdDoc . getElementById ( "emb-ol" ) ;
1134+ expect ( olBtn . getAttribute ( "aria-pressed" ) ) . toBe ( "false" ) ;
1135+
1136+ await awaitsForDone ( CommandManager . execute ( Commands . FILE_CLOSE , { _forceClose : true } ) ,
1137+ "force close" ) ;
1138+ } , 10000 ) ;
1139+
1140+ it ( "should toolbar OL button show active state when cursor in OL" , async function ( ) {
1141+ await _openMdFile ( "list-test.md" ) ;
1142+ await _enterEditMode ( ) ;
1143+
1144+ const olLi = _findLiByText ( "First ordered" ) ;
1145+ _placeCursorInElement ( olLi , 0 ) ;
1146+
1147+ const mdDoc = _getMdIFrameDoc ( ) ;
1148+ mdDoc . dispatchEvent ( new Event ( "selectionchange" ) ) ;
1149+
1150+ await awaitsFor ( ( ) => {
1151+ const olBtn = mdDoc . getElementById ( "emb-ol" ) ;
1152+ return olBtn && olBtn . getAttribute ( "aria-pressed" ) === "true" ;
1153+ } , "OL button to show active state" ) ;
1154+
1155+ const ulBtn = mdDoc . getElementById ( "emb-ul" ) ;
1156+ expect ( ulBtn . getAttribute ( "aria-pressed" ) ) . toBe ( "false" ) ;
1157+
1158+ await awaitsForDone ( CommandManager . execute ( Commands . FILE_CLOSE , { _forceClose : true } ) ,
1159+ "force close" ) ;
1160+ } , 10000 ) ;
1161+
1162+ it ( "should block-level buttons be hidden when cursor is in list" , async function ( ) {
1163+ await _openMdFile ( "list-test.md" ) ;
1164+ await _enterEditMode ( ) ;
1165+
1166+ const ulLi = _findLiByText ( "First item" ) ;
1167+ _placeCursorInElement ( ulLi , 0 ) ;
1168+
1169+ const mdDoc = _getMdIFrameDoc ( ) ;
1170+ mdDoc . dispatchEvent ( new Event ( "selectionchange" ) ) ;
1171+
1172+ // Wait for toolbar update
1173+ await awaitsFor ( ( ) => {
1174+ const quoteBtn = mdDoc . getElementById ( "emb-quote" ) ;
1175+ return quoteBtn && quoteBtn . style . display === "none" ;
1176+ } , "block buttons to be hidden in list" ) ;
1177+
1178+ // Block-level buttons should be hidden
1179+ const blockIds = [ "emb-quote" , "emb-hr" , "emb-table" , "emb-codeblock" ] ;
1180+ for ( const id of blockIds ) {
1181+ const btn = mdDoc . getElementById ( id ) ;
1182+ if ( btn ) {
1183+ expect ( btn . style . display ) . toBe ( "none" ) ;
1184+ }
1185+ }
1186+
1187+ // Block type selector should be hidden
1188+ const blockTypeSelect = mdDoc . getElementById ( "emb-block-type" ) ;
1189+ if ( blockTypeSelect ) {
1190+ expect ( blockTypeSelect . style . display ) . toBe ( "none" ) ;
1191+ }
1192+
1193+ // List buttons should remain visible
1194+ const ulBtn = mdDoc . getElementById ( "emb-ul" ) ;
1195+ const olBtn = mdDoc . getElementById ( "emb-ol" ) ;
1196+ expect ( ulBtn . style . display ) . not . toBe ( "none" ) ;
1197+ expect ( olBtn . style . display ) . not . toBe ( "none" ) ;
1198+
1199+ await awaitsForDone ( CommandManager . execute ( Commands . FILE_CLOSE , { _forceClose : true } ) ,
1200+ "force close" ) ;
1201+ } , 10000 ) ;
1202+
1203+ it ( "should moving cursor out of list restore all toolbar buttons" , async function ( ) {
1204+ await _openMdFile ( "list-test.md" ) ;
1205+ await _enterEditMode ( ) ;
1206+
1207+ const mdDoc = _getMdIFrameDoc ( ) ;
1208+
1209+ // First place cursor in list — block buttons hidden
1210+ const ulLi = _findLiByText ( "First item" ) ;
1211+ _placeCursorInElement ( ulLi , 0 ) ;
1212+ mdDoc . dispatchEvent ( new Event ( "selectionchange" ) ) ;
1213+
1214+ await awaitsFor ( ( ) => {
1215+ const quoteBtn = mdDoc . getElementById ( "emb-quote" ) ;
1216+ return quoteBtn && quoteBtn . style . display === "none" ;
1217+ } , "block buttons to be hidden in list" ) ;
1218+
1219+ // Now move cursor to a paragraph outside the list
1220+ const paragraphs = mdDoc . querySelectorAll ( "#viewer-content > p" ) ;
1221+ let targetP = null ;
1222+ for ( const p of paragraphs ) {
1223+ if ( p . textContent . includes ( "End of list test" ) ) {
1224+ targetP = p ;
1225+ break ;
1226+ }
1227+ }
1228+ expect ( targetP ) . not . toBeNull ( ) ;
1229+ const range = mdDoc . createRange ( ) ;
1230+ range . setStart ( targetP . firstChild , 0 ) ;
1231+ range . collapse ( true ) ;
1232+ _getMdIFrameWin ( ) . getSelection ( ) . removeAllRanges ( ) ;
1233+ _getMdIFrameWin ( ) . getSelection ( ) . addRange ( range ) ;
1234+ mdDoc . dispatchEvent ( new Event ( "selectionchange" ) ) ;
1235+
1236+ // Block buttons should be restored
1237+ await awaitsFor ( ( ) => {
1238+ const quoteBtn = mdDoc . getElementById ( "emb-quote" ) ;
1239+ return quoteBtn && quoteBtn . style . display !== "none" ;
1240+ } , "block buttons to be restored outside list" ) ;
1241+
1242+ const blockIds = [ "emb-quote" , "emb-hr" , "emb-table" , "emb-codeblock" ] ;
1243+ for ( const id of blockIds ) {
1244+ const btn = mdDoc . getElementById ( id ) ;
1245+ if ( btn ) {
1246+ expect ( btn . style . display ) . not . toBe ( "none" ) ;
1247+ }
1248+ }
1249+
1250+ const blockTypeSelect = mdDoc . getElementById ( "emb-block-type" ) ;
1251+ if ( blockTypeSelect ) {
1252+ expect ( blockTypeSelect . style . display ) . not . toBe ( "none" ) ;
1253+ }
1254+
1255+ await awaitsForDone ( CommandManager . execute ( Commands . FILE_CLOSE , { _forceClose : true } ) ,
1256+ "force close" ) ;
1257+ } , 10000 ) ;
1258+ } ) ;
9941259 } ) ;
9951260} ) ;
0 commit comments