TextFormatterTests.cs 120 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083
  1. using System.Text;
  2. using UnitTests;
  3. using Xunit.Abstractions;
  4. // Alias Console to MockConsole so we don't accidentally use Console
  5. namespace Terminal.Gui.TextTests;
  6. public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
  7. {
  8. private readonly ITestOutputHelper _output;
  9. public TextFormatterTests (ITestOutputHelper output)
  10. {
  11. _output = output;
  12. }
  13. [Theory]
  14. [InlineData ("")]
  15. [InlineData (null)]
  16. [InlineData ("test")]
  17. public void ClipAndJustify_Invalid_Returns_Original (string text)
  18. {
  19. string expected = string.IsNullOrEmpty (text) ? text : "";
  20. Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, Alignment.Start));
  21. Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, Alignment.Start));
  22. Assert.Throws<ArgumentOutOfRangeException> (
  23. () =>
  24. TextFormatter.ClipAndJustify (text, -1, Alignment.Start)
  25. );
  26. }
  27. [Theory]
  28. [InlineData ("test", "", 0)]
  29. [InlineData ("test", "te", 2)]
  30. [InlineData ("test", "test", int.MaxValue)]
  31. [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit
  32. [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
  33. [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
  34. [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
  35. [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
  36. [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
  37. [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
  38. [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
  39. [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
  40. [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
  41. [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit
  42. [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
  43. [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
  44. public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth)
  45. {
  46. var alignment = Alignment.Center;
  47. var textDirection = TextDirection.LeftRight_TopBottom;
  48. var tabWidth = 1;
  49. Assert.Equal (
  50. justifiedText,
  51. TextFormatter.ClipAndJustify (text, maxWidth, alignment, textDirection, tabWidth)
  52. );
  53. int expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
  54. Assert.Equal (
  55. justifiedText,
  56. TextFormatter.ClipAndJustify (text, maxWidth, alignment, textDirection, tabWidth)
  57. );
  58. Assert.True (justifiedText.GetRuneCount () <= maxWidth);
  59. Assert.True (justifiedText.GetColumns () <= maxWidth);
  60. Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
  61. Assert.Equal (
  62. expectedClippedWidth,
  63. justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))
  64. );
  65. Assert.True (expectedClippedWidth <= maxWidth);
  66. Assert.Equal (
  67. StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]),
  68. justifiedText
  69. );
  70. }
  71. [Theory]
  72. [InlineData ("test", "", 0)]
  73. [InlineData ("test", "te", 2)]
  74. [InlineData ("test", "test", int.MaxValue)] // This doesn't throw because it only create a word with length 1
  75. [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit
  76. [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
  77. [InlineData (
  78. "A sentence has words.",
  79. "A sentence has words.",
  80. 500
  81. )] // should fit
  82. [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
  83. [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
  84. // Now throw System.OutOfMemoryException. See https://stackoverflow.com/questions/20672920/maxcapacity-of-stringbuilder
  85. //[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
  86. [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
  87. [InlineData (
  88. "line1\nline2\nline3long!",
  89. "line1\nline2\nline3long!",
  90. int.MaxValue
  91. )] // This doesn't throw because it only create a line with length 1
  92. [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
  93. [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
  94. [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit
  95. [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
  96. [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
  97. public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth)
  98. {
  99. var alignment = Alignment.Fill;
  100. var textDirection = TextDirection.LeftRight_TopBottom;
  101. var tabWidth = 1;
  102. Assert.Equal (
  103. justifiedText,
  104. TextFormatter.ClipAndJustify (text, maxWidth, alignment, textDirection, tabWidth)
  105. );
  106. int expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
  107. Assert.Equal (
  108. justifiedText,
  109. TextFormatter.ClipAndJustify (text, maxWidth, alignment, textDirection, tabWidth)
  110. );
  111. Assert.True (justifiedText.GetRuneCount () <= maxWidth);
  112. Assert.True (justifiedText.GetColumns () <= maxWidth);
  113. Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
  114. Assert.Equal (
  115. expectedClippedWidth,
  116. justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))
  117. );
  118. Assert.True (expectedClippedWidth <= maxWidth);
  119. Assert.Equal (
  120. StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]),
  121. justifiedText
  122. );
  123. // see Justify_ tests below
  124. }
  125. [Theory]
  126. [InlineData ("test", "", 0)]
  127. [InlineData ("test", "te", 2)]
  128. [InlineData ("test", "test", int.MaxValue)]
  129. [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit
  130. [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
  131. [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
  132. [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
  133. [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
  134. [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
  135. [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
  136. [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
  137. [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
  138. [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
  139. [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit
  140. [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
  141. [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
  142. public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth)
  143. {
  144. var alignment = Alignment.Start;
  145. var textDirection = TextDirection.LeftRight_BottomTop;
  146. var tabWidth = 1;
  147. Assert.Equal (
  148. justifiedText,
  149. TextFormatter.ClipAndJustify (text, maxWidth, alignment, textDirection, tabWidth)
  150. );
  151. int expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
  152. Assert.Equal (
  153. justifiedText,
  154. TextFormatter.ClipAndJustify (text, maxWidth, alignment, textDirection, tabWidth)
  155. );
  156. Assert.True (justifiedText.GetRuneCount () <= maxWidth);
  157. Assert.True (justifiedText.GetColumns () <= maxWidth);
  158. Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
  159. Assert.Equal (
  160. expectedClippedWidth,
  161. justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))
  162. );
  163. Assert.True (expectedClippedWidth <= maxWidth);
  164. Assert.Equal (
  165. StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]),
  166. justifiedText
  167. );
  168. }
  169. [Theory]
  170. [InlineData ("test", "", 0)]
  171. [InlineData ("test", "te", 2)]
  172. [InlineData ("test", "test", int.MaxValue)]
  173. [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit
  174. [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
  175. [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
  176. [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
  177. [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
  178. [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
  179. [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
  180. [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
  181. [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
  182. [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
  183. [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit
  184. [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
  185. [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
  186. public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth)
  187. {
  188. var alignment = Alignment.End;
  189. var textDirection = TextDirection.LeftRight_BottomTop;
  190. var tabWidth = 1;
  191. Assert.Equal (
  192. justifiedText,
  193. TextFormatter.ClipAndJustify (text, maxWidth, alignment, textDirection, tabWidth)
  194. );
  195. int expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
  196. Assert.Equal (
  197. justifiedText,
  198. TextFormatter.ClipAndJustify (text, maxWidth, alignment, textDirection, tabWidth)
  199. );
  200. Assert.True (justifiedText.GetRuneCount () <= maxWidth);
  201. Assert.True (justifiedText.GetColumns () <= maxWidth);
  202. Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
  203. Assert.Equal (
  204. expectedClippedWidth,
  205. justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))
  206. );
  207. Assert.True (expectedClippedWidth <= maxWidth);
  208. Assert.Equal (
  209. StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]),
  210. justifiedText
  211. );
  212. }
  213. public static IEnumerable<object []> CMGlyphs =>
  214. new List<object []> { new object [] { $"{Glyphs.LeftBracket} Say Hello 你 {Glyphs.RightBracket}", 16, 15 } };
  215. [Theory]
  216. [InlineData ("_k Before", true, 0, (KeyCode)'K')] // lower case should return uppercase Hotkey
  217. [InlineData ("a_k Second", true, 1, (KeyCode)'K')]
  218. [InlineData ("Last _k", true, 5, (KeyCode)'K')]
  219. [InlineData ("After k_", false, -1, KeyCode.Null)]
  220. [InlineData ("Multiple _k and _R", true, 9, (KeyCode)'K')]
  221. [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к')] // Lower case Cryllic K (к)
  222. [InlineData ("_k Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results
  223. [InlineData ("a_k Second", true, 1, (KeyCode)'K', true)]
  224. [InlineData ("Last _k", true, 5, (KeyCode)'K', true)]
  225. [InlineData ("After k_", false, -1, KeyCode.Null, true)]
  226. [InlineData ("Multiple _k and _r", true, 9, (KeyCode)'K', true)]
  227. [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к', true)] // Cryllic K (К)
  228. public void FindHotKey_AlphaLowerCase_Succeeds (
  229. string text,
  230. bool expectedResult,
  231. int expectedHotPos,
  232. KeyCode expectedKey,
  233. bool supportFirstUpperCase = false
  234. )
  235. {
  236. var hotKeySpecifier = (Rune)'_';
  237. bool result = TextFormatter.FindHotKey (
  238. text,
  239. hotKeySpecifier,
  240. out int hotPos,
  241. out Key hotKey,
  242. supportFirstUpperCase
  243. );
  244. if (expectedResult)
  245. {
  246. Assert.True (result);
  247. }
  248. else
  249. {
  250. Assert.False (result);
  251. }
  252. Assert.Equal (expectedResult, result);
  253. Assert.Equal (expectedHotPos, hotPos);
  254. Assert.Equal (expectedKey, hotKey);
  255. }
  256. [Theory]
  257. [InlineData ("_K Before", true, 0, (KeyCode)'K')]
  258. [InlineData ("a_K Second", true, 1, (KeyCode)'K')]
  259. [InlineData ("Last _K", true, 5, (KeyCode)'K')]
  260. [InlineData ("After K_", false, -1, KeyCode.Null)]
  261. [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K')]
  262. [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К)
  263. [InlineData ("_K Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results
  264. [InlineData ("a_K Second", true, 1, (KeyCode)'K', true)]
  265. [InlineData ("Last _K", true, 5, (KeyCode)'K', true)]
  266. [InlineData ("After K_", false, -1, KeyCode.Null, true)]
  267. [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K', true)]
  268. [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К', true)] // Cryllic K (К)
  269. public void FindHotKey_AlphaUpperCase_Succeeds (
  270. string text,
  271. bool expectedResult,
  272. int expectedHotPos,
  273. KeyCode expectedKey,
  274. bool supportFirstUpperCase = false
  275. )
  276. {
  277. var hotKeySpecifier = (Rune)'_';
  278. bool result = TextFormatter.FindHotKey (
  279. text,
  280. hotKeySpecifier,
  281. out int hotPos,
  282. out Key hotKey,
  283. supportFirstUpperCase
  284. );
  285. if (expectedResult)
  286. {
  287. Assert.True (result);
  288. }
  289. else
  290. {
  291. Assert.False (result);
  292. }
  293. Assert.Equal (expectedResult, result);
  294. Assert.Equal (expectedHotPos, hotPos);
  295. Assert.Equal (expectedKey, hotKey);
  296. }
  297. [Theory]
  298. [InlineData (null)]
  299. [InlineData ("")]
  300. [InlineData ("no hotkey")]
  301. [InlineData ("No hotkey, Upper Case")]
  302. [InlineData ("Non-english: Сохранить")]
  303. public void FindHotKey_Invalid_ReturnsFalse (string text)
  304. {
  305. var hotKeySpecifier = (Rune)'_';
  306. var supportFirstUpperCase = false;
  307. var hotPos = 0;
  308. Key hotKey = KeyCode.Null;
  309. var result = false;
  310. result = TextFormatter.FindHotKey (
  311. text,
  312. hotKeySpecifier,
  313. out hotPos,
  314. out hotKey,
  315. supportFirstUpperCase
  316. );
  317. Assert.False (result);
  318. Assert.Equal (-1, hotPos);
  319. Assert.Equal (KeyCode.Null, hotKey);
  320. }
  321. [Theory]
  322. [InlineData ("\"k before")]
  323. [InlineData ("ak second")]
  324. [InlineData ("last k")]
  325. [InlineData ("multiple k and r")]
  326. [InlineData ("12345")]
  327. [InlineData ("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")] // punctuation
  328. [InlineData (" ~  s  gui.cs   master ↑10")] // ~IsLetterOrDigit + Unicode
  329. [InlineData ("non-english: кдать")] // Lower case Cryllic K (к)
  330. public void FindHotKey_Legacy_FirstUpperCase_NotFound_Returns_False (string text)
  331. {
  332. var supportFirstUpperCase = true;
  333. var hotKeySpecifier = (Rune)0;
  334. bool result = TextFormatter.FindHotKey (
  335. text,
  336. hotKeySpecifier,
  337. out int hotPos,
  338. out Key hotKey,
  339. supportFirstUpperCase
  340. );
  341. Assert.False (result);
  342. Assert.Equal (-1, hotPos);
  343. Assert.Equal (KeyCode.Null, hotKey);
  344. }
  345. [Theory]
  346. [InlineData ("K Before", true, 0, (KeyCode)'K')]
  347. [InlineData ("aK Second", true, 1, (KeyCode)'K')]
  348. [InlineData ("last K", true, 5, (KeyCode)'K')]
  349. [InlineData ("multiple K and R", true, 9, (KeyCode)'K')]
  350. [InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К)
  351. public void FindHotKey_Legacy_FirstUpperCase_Succeeds (
  352. string text,
  353. bool expectedResult,
  354. int expectedHotPos,
  355. KeyCode expectedKey
  356. )
  357. {
  358. var supportFirstUpperCase = true;
  359. var hotKeySpecifier = (Rune)0;
  360. bool result = TextFormatter.FindHotKey (
  361. text,
  362. hotKeySpecifier,
  363. out int hotPos,
  364. out Key hotKey,
  365. supportFirstUpperCase
  366. );
  367. if (expectedResult)
  368. {
  369. Assert.True (result);
  370. }
  371. else
  372. {
  373. Assert.False (result);
  374. }
  375. Assert.Equal (expectedResult, result);
  376. Assert.Equal (expectedHotPos, hotPos);
  377. Assert.Equal (expectedKey, hotKey);
  378. }
  379. [Theory]
  380. [InlineData ("_1 Before", true, 0, (KeyCode)'1')] // Digits
  381. [InlineData ("a_1 Second", true, 1, (KeyCode)'1')]
  382. [InlineData ("Last _1", true, 5, (KeyCode)'1')]
  383. [InlineData ("After 1_", false, -1, KeyCode.Null)]
  384. [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1')]
  385. [InlineData ("_1 Before", true, 0, (KeyCode)'1', true)] // Turn on FirstUpperCase and verify same results
  386. [InlineData ("a_1 Second", true, 1, (KeyCode)'1', true)]
  387. [InlineData ("Last _1", true, 5, (KeyCode)'1', true)]
  388. [InlineData ("After 1_", false, -1, KeyCode.Null, true)]
  389. [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1', true)]
  390. public void FindHotKey_Numeric_Succeeds (
  391. string text,
  392. bool expectedResult,
  393. int expectedHotPos,
  394. KeyCode expectedKey,
  395. bool supportFirstUpperCase = false
  396. )
  397. {
  398. var hotKeySpecifier = (Rune)'_';
  399. bool result = TextFormatter.FindHotKey (
  400. text,
  401. hotKeySpecifier,
  402. out int hotPos,
  403. out Key hotKey,
  404. supportFirstUpperCase
  405. );
  406. if (expectedResult)
  407. {
  408. Assert.True (result);
  409. }
  410. else
  411. {
  412. Assert.False (result);
  413. }
  414. Assert.Equal (expectedResult, result);
  415. Assert.Equal (expectedHotPos, hotPos);
  416. Assert.Equal (expectedKey, hotKey);
  417. }
  418. [Theory]
  419. [InlineData ("_\"k before", true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char
  420. [InlineData ("\"_k before", true, KeyCode.K)]
  421. [InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')]
  422. [InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')]
  423. [InlineData (
  424. "`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?",
  425. true,
  426. (KeyCode)'='
  427. )] // BUGBUG: Not sure why this fails. Ignore the first and consider the second
  428. [InlineData ("_ ~  s  gui.cs   master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
  429. [InlineData (" ~  s  gui.cs  _ master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
  430. [InlineData ("non-english: _кдать", true, (KeyCode)'к')] // Lower case Cryllic K (к)
  431. public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected)
  432. {
  433. var hotKeySpecifier = (Rune)'_';
  434. bool result = TextFormatter.FindHotKey (text, hotKeySpecifier, out int _, out Key hotKey);
  435. Assert.Equal (found, result);
  436. Assert.Equal (expected, hotKey);
  437. }
  438. [Fact]
  439. public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True ()
  440. {
  441. Exception exception = Record.Exception (
  442. () =>
  443. TextFormatter.Format (
  444. "Some text",
  445. 4,
  446. Alignment.Start,
  447. false,
  448. true
  449. )
  450. );
  451. Assert.Null (exception);
  452. }
  453. [Theory]
  454. [InlineData (
  455. "Hello world, how are you today? Pretty neat!",
  456. 44,
  457. 80,
  458. "Hello world, how are you today? Pretty neat!"
  459. )]
  460. public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal (
  461. string text,
  462. int runeCount,
  463. int maxWidth,
  464. string justifiedText
  465. )
  466. {
  467. Assert.Equal (runeCount, text.GetRuneCount ());
  468. var fmtText = string.Empty;
  469. for (int i = text.GetRuneCount (); i < maxWidth; i++)
  470. {
  471. fmtText = TextFormatter.Format (text, i, Alignment.Fill, true) [0];
  472. Assert.Equal (i, fmtText.GetRuneCount ());
  473. char c = fmtText [^1];
  474. Assert.True (text.EndsWith (c));
  475. }
  476. Assert.Equal (justifiedText, fmtText);
  477. }
  478. [Theory]
  479. [InlineData (
  480. "Hello world, how are you today? Pretty neat!",
  481. 44,
  482. 80,
  483. "Hello world, how are you today? Pretty neat!"
  484. )]
  485. public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Vertical (
  486. string text,
  487. int runeCount,
  488. int maxWidth,
  489. string justifiedText
  490. )
  491. {
  492. Assert.Equal (runeCount, text.GetRuneCount ());
  493. var fmtText = string.Empty;
  494. for (int i = text.GetRuneCount (); i < maxWidth; i++)
  495. {
  496. fmtText = TextFormatter.Format (
  497. text,
  498. i,
  499. Alignment.Fill,
  500. false,
  501. true,
  502. 0,
  503. TextDirection.TopBottom_LeftRight
  504. ) [0];
  505. Assert.Equal (i, fmtText.GetRuneCount ());
  506. char c = fmtText [^1];
  507. Assert.True (text.EndsWith (c));
  508. }
  509. Assert.Equal (justifiedText, fmtText);
  510. }
  511. [Theory]
  512. [InlineData ("Truncate", 3, "Tru")]
  513. [InlineData ("デモエムポンズ", 3, "デ")]
  514. public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected)
  515. {
  516. List<string> list = TextFormatter.Format (text, width, false, false);
  517. Assert.Equal (expected, list [^1]);
  518. }
  519. [Theory]
  520. [MemberData (nameof (FormatEnvironmentNewLine))]
  521. public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces (
  522. string text,
  523. int width,
  524. IEnumerable<string> expected
  525. )
  526. {
  527. var preserveTrailingSpaces = false;
  528. List<string> formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
  529. Assert.Equal (expected, formated);
  530. preserveTrailingSpaces = true;
  531. formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
  532. Assert.Equal (expected, formated);
  533. }
  534. [Theory]
  535. [InlineData (
  536. " A sentence has words. \n This is the second Line - 2. ",
  537. 4,
  538. -50,
  539. Alignment.Start,
  540. true,
  541. false,
  542. new [] { " A", "sent", "ence", "has", "word", "s. ", " Thi", "s is", "the", "seco", "nd", "Line", "- 2." },
  543. " Asentencehaswords. This isthesecondLine- 2."
  544. )]
  545. [InlineData (
  546. " A sentence has words. \n This is the second Line - 2. ",
  547. 4,
  548. -50,
  549. Alignment.Start,
  550. true,
  551. true,
  552. new []
  553. {
  554. " A ",
  555. "sent",
  556. "ence",
  557. " ",
  558. "has ",
  559. "word",
  560. "s. ",
  561. " ",
  562. "This",
  563. " is ",
  564. "the ",
  565. "seco",
  566. "nd ",
  567. "Line",
  568. " - ",
  569. "2. "
  570. },
  571. " A sentence has words. This is the second Line - 2. "
  572. )]
  573. public void Format_WordWrap_PreserveTrailingSpaces (
  574. string text,
  575. int maxWidth,
  576. int widthOffset,
  577. Alignment alignment,
  578. bool wrap,
  579. bool preserveTrailingSpaces,
  580. IEnumerable<string> resultLines,
  581. string expectedWrappedText
  582. )
  583. {
  584. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  585. List<string> list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces);
  586. Assert.Equal (list.Count, resultLines.Count ());
  587. Assert.Equal (resultLines, list);
  588. var wrappedText = string.Empty;
  589. foreach (string txt in list)
  590. {
  591. wrappedText += txt;
  592. }
  593. Assert.Equal (expectedWrappedText, wrappedText);
  594. }
  595. [Theory]
  596. [InlineData (
  597. "Les Mise\u0301rables",
  598. 14,
  599. -1,
  600. false,
  601. new [] { "Les Misérables" },
  602. "Les Misérables"
  603. )]
  604. [InlineData (
  605. "Les Mise\u0328\u0301rables",
  606. 14,
  607. -2,
  608. false,
  609. new [] { "Les Misę́rables" },
  610. "Les Misę́rables"
  611. )]
  612. public void Format_Combining_Marks_Alignments (
  613. string text,
  614. int maxWidth,
  615. int widthOffset,
  616. bool wrap,
  617. IEnumerable<string> resultLines,
  618. string expectedText
  619. )
  620. {
  621. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  622. // Horizontal text direction
  623. foreach (Alignment alignment in Enum.GetValues (typeof (Alignment)))
  624. {
  625. TextFormatter tf = new () { Text = text, ConstrainToSize = new (maxWidth, 1), WordWrap = wrap, Alignment = alignment };
  626. List<string> list = TextFormatter.Format (
  627. text,
  628. maxWidth,
  629. alignment,
  630. wrap,
  631. tf.PreserveTrailingSpaces,
  632. tf.TabWidth,
  633. tf.Direction,
  634. tf.MultiLine,
  635. tf);
  636. Assert.Equal (list.Count, resultLines.Count ());
  637. Assert.Equal (resultLines, list);
  638. var formattedText = string.Empty;
  639. foreach (string txt in list)
  640. {
  641. formattedText += txt;
  642. }
  643. Assert.Equal (expectedText, formattedText);
  644. }
  645. // Vertical text direction
  646. foreach (Alignment alignment in Enum.GetValues (typeof (Alignment)))
  647. {
  648. TextFormatter tf = new ()
  649. { Text = text, ConstrainToSize = new (1, maxWidth), WordWrap = wrap, VerticalAlignment = alignment, Direction = TextDirection.TopBottom_LeftRight };
  650. List<string> list = TextFormatter.Format (
  651. text,
  652. maxWidth,
  653. alignment,
  654. wrap,
  655. tf.PreserveTrailingSpaces,
  656. tf.TabWidth,
  657. tf.Direction,
  658. tf.MultiLine,
  659. tf);
  660. Assert.Equal (list.Count, resultLines.Count ());
  661. Assert.Equal (resultLines, list);
  662. var formattedText = string.Empty;
  663. foreach (string txt in list)
  664. {
  665. formattedText += txt;
  666. }
  667. Assert.Equal (expectedText, formattedText);
  668. }
  669. }
  670. public static IEnumerable<object []> FormatEnvironmentNewLine =>
  671. new List<object []>
  672. {
  673. new object []
  674. {
  675. $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}",
  676. 60,
  677. new [] { "Line1", "Line2", "Line3" }
  678. }
  679. };
  680. [Theory]
  681. [InlineData ("Hello World", 11)]
  682. [InlineData ("こんにちは世界", 14)]
  683. public void GetColumns_Simple_And_Wide_Runes (string text, int width) { Assert.Equal (width, text.GetColumns ()); }
  684. [Theory]
  685. [InlineData (new [] { "0123456789" }, 1)]
  686. [InlineData (new [] { "Hello World" }, 1)]
  687. [InlineData (new [] { "Hello", "World" }, 2)]
  688. [InlineData (new [] { "こんにちは", "世界" }, 4)]
  689. public void GetColumnsRequiredForVerticalText_List_GetsWidth (IEnumerable<string> text, int expectedWidth)
  690. {
  691. Assert.Equal (expectedWidth, TextFormatter.GetColumnsRequiredForVerticalText (text.ToList ()));
  692. }
  693. [Theory]
  694. [InlineData (new [] { "Hello World" }, 1, 0, 1, 1)]
  695. [InlineData (new [] { "Hello", "World" }, 2, 1, 1, 1)]
  696. [InlineData (new [] { "こんにちは", "世界" }, 4, 1, 1, 2)]
  697. public void GetColumnsRequiredForVerticalText_List_Simple_And_Wide_Runes (
  698. IEnumerable<string> text,
  699. int expectedWidth,
  700. int index,
  701. int length,
  702. int expectedIndexWidth
  703. )
  704. {
  705. Assert.Equal (expectedWidth, TextFormatter.GetColumnsRequiredForVerticalText (text.ToList ()));
  706. Assert.Equal (expectedIndexWidth, TextFormatter.GetColumnsRequiredForVerticalText (text.ToList (), index, length));
  707. }
  708. [Fact]
  709. public void GetColumnsRequiredForVerticalText_List_With_Combining_Runes ()
  710. {
  711. List<string> text = new () { "Les Mis", "e\u0328\u0301", "rables" };
  712. Assert.Equal (1, TextFormatter.GetColumnsRequiredForVerticalText (text, 1, 1));
  713. }
  714. [Theory]
  715. [InlineData ("Hello World", 6, 6)]
  716. [InlineData ("こんにちは 世界", 6, 3)]
  717. [MemberData (nameof (CMGlyphs))]
  718. public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength)
  719. {
  720. List<Rune> runes = text.ToRuneList ();
  721. Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
  722. }
  723. [Theory]
  724. [InlineData ("test", 3, 3)]
  725. [InlineData ("test", 4, 4)]
  726. [InlineData ("test", 10, 4)]
  727. public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength)
  728. {
  729. List<Rune> runes = text.ToRuneList ();
  730. Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
  731. }
  732. [Theory]
  733. [InlineData ("Hello World", 6, 6)]
  734. [InlineData ("こんにちは 世界", 6, 3)]
  735. public void GetLengthThatFits_Simple_And_Wide_Runes (string text, int columns, int expectedLength)
  736. {
  737. Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
  738. }
  739. [Theory]
  740. [InlineData ("test", 3, 3)]
  741. [InlineData ("test", 4, 4)]
  742. [InlineData ("test", 10, 4)]
  743. [InlineData ("test", 1, 1)]
  744. [InlineData ("test", 0, 0)]
  745. [InlineData ("test", -1, 0)]
  746. [InlineData (null, -1, 0)]
  747. [InlineData ("", -1, 0)]
  748. public void GetLengthThatFits_String (string text, int columns, int expectedLength)
  749. {
  750. Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
  751. }
  752. [Fact]
  753. public void GetLengthThatFits_With_Combining_Runes ()
  754. {
  755. var text = "Les Mise\u0328\u0301rables";
  756. Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14));
  757. }
  758. [Fact]
  759. public void GetMaxColsForWidth_With_Combining_Runes ()
  760. {
  761. List<string> text = new () { "Les Mis", "e\u0328\u0301", "rables" };
  762. Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1));
  763. }
  764. //[Fact]
  765. //public void GetWidestLineLength_With_Combining_Runes ()
  766. //{
  767. // var text = "Les Mise\u0328\u0301rables";
  768. // Assert.Equal (1, TextFormatter.GetWidestLineLength (text, 1, 1));
  769. //}
  770. [Fact]
  771. public void Internal_Tests ()
  772. {
  773. var tf = new TextFormatter ();
  774. Assert.Equal (KeyCode.Null, tf.HotKey);
  775. tf.HotKey = KeyCode.CtrlMask | KeyCode.Q;
  776. Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, tf.HotKey);
  777. }
  778. [Theory]
  779. [InlineData ("")]
  780. [InlineData (null)]
  781. [InlineData ("test")]
  782. public void Justify_Invalid (string text)
  783. {
  784. Assert.Equal (text, TextFormatter.Justify (text, 0));
  785. Assert.Equal (text, TextFormatter.Justify (text, 0));
  786. Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.Justify (text, -1));
  787. }
  788. [Theory]
  789. // Even # of spaces
  790. // 0123456789
  791. [InlineData ("012 456 89", "012 456 89", 10, 0, "+", true)]
  792. [InlineData ("012 456 89", "012++456+89", 11, 1)]
  793. [InlineData ("012 456 89", "012 456 89", 12, 2, "++", true)]
  794. [InlineData ("012 456 89", "012+++456++89", 13, 3)]
  795. [InlineData ("012 456 89", "012 456 89", 14, 4, "+++", true)]
  796. [InlineData ("012 456 89", "012++++456+++89", 15, 5)]
  797. [InlineData ("012 456 89", "012 456 89", 16, 6, "++++", true)]
  798. [InlineData ("012 456 89", "012 456 89", 30, 20, "+++++++++++", true)]
  799. [InlineData ("012 456 89", "012+++++++++++++456++++++++++++89", 33, 23)]
  800. // Odd # of spaces
  801. // 01234567890123
  802. [InlineData ("012 456 89 end", "012 456 89 end", 14, 0, "+", true)]
  803. [InlineData ("012 456 89 end", "012++456+89+end", 15, 1)]
  804. [InlineData ("012 456 89 end", "012++456++89+end", 16, 2)]
  805. [InlineData ("012 456 89 end", "012 456 89 end", 17, 3, "++", true)]
  806. [InlineData ("012 456 89 end", "012+++456++89++end", 18, 4)]
  807. [InlineData ("012 456 89 end", "012+++456+++89++end", 19, 5)]
  808. [InlineData ("012 456 89 end", "012 456 89 end", 20, 6, "+++", true)]
  809. [InlineData ("012 456 89 end", "012++++++++456++++++++89+++++++end", 34, 20)]
  810. [InlineData ("012 456 89 end", "012+++++++++456+++++++++89++++++++end", 37, 23)]
  811. // Unicode
  812. // Even # of chars
  813. // 0123456789
  814. [InlineData ("пÑРвРÑ", "пÑРвРÑ", 10, 0, "+", true)]
  815. [InlineData ("пÑРвРÑ", "пÑÐ++вÐ+Ñ", 11, 1)]
  816. [InlineData ("пÑРвРÑ", "пÑРвРÑ", 12, 2, "++", true)]
  817. [InlineData ("пÑРвРÑ", "пÑÐ+++вÐ++Ñ", 13, 3)]
  818. [InlineData ("пÑРвРÑ", "пÑРвРÑ", 14, 4, "+++", true)]
  819. [InlineData ("пÑРвРÑ", "пÑÐ++++вÐ+++Ñ", 15, 5)]
  820. [InlineData ("пÑРвРÑ", "пÑРвРÑ", 16, 6, "++++", true)]
  821. [InlineData ("пÑРвРÑ", "пÑРвРÑ", 30, 20, "+++++++++++", true)]
  822. [InlineData ("пÑРвРÑ", "пÑÐ+++++++++++++вÐ++++++++++++Ñ", 33, 23)]
  823. // Unicode
  824. // Odd # of chars
  825. // 0123456789
  826. [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 10, 0, "+", true)]
  827. [InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ+вÐ+Ñ", 11, 1)]
  828. [InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ++вÐ+Ñ", 12, 2)]
  829. [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 13, 3, "++", true)]
  830. [InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ++вÐ++Ñ", 14, 4)]
  831. [InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ+++вÐ++Ñ", 15, 5)]
  832. [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 16, 6, "+++", true)]
  833. [InlineData ("Ð ÑРвРÑ", "Ð++++++++ÑÐ++++++++вÐ+++++++Ñ", 30, 20)]
  834. [InlineData ("Ð ÑРвРÑ", "Ð+++++++++ÑÐ+++++++++вÐ++++++++Ñ", 33, 23)]
  835. public void Justify_Sentence (
  836. string text,
  837. string justifiedText,
  838. int forceToWidth,
  839. int widthOffset,
  840. string replaceWith = null,
  841. bool replace = false
  842. )
  843. {
  844. var fillChar = '+';
  845. Assert.Equal (forceToWidth, text.GetRuneCount () + widthOffset);
  846. if (replace)
  847. {
  848. justifiedText = text.Replace (" ", replaceWith);
  849. }
  850. Assert.Equal (justifiedText, TextFormatter.Justify (text, forceToWidth, fillChar));
  851. Assert.True (Math.Abs (forceToWidth - justifiedText.GetRuneCount ()) < text.Count (s => s == ' '));
  852. Assert.True (Math.Abs (forceToWidth - justifiedText.GetColumns ()) < text.Count (s => s == ' '));
  853. }
  854. [Theory]
  855. [InlineData ("word")] // Even # of chars
  856. [InlineData ("word.")] // Odd # of chars
  857. [InlineData ("пÑивеÑ")] // Unicode (even #)
  858. [InlineData ("пÑивеÑ.")] // Unicode (odd # of chars)
  859. public void Justify_SingleWord (string text)
  860. {
  861. string justifiedText = text;
  862. var fillChar = '+';
  863. int width = text.GetRuneCount ();
  864. Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
  865. width = text.GetRuneCount () + 1;
  866. Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
  867. width = text.GetRuneCount () + 2;
  868. Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
  869. width = text.GetRuneCount () + 10;
  870. Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
  871. width = text.GetRuneCount () + 11;
  872. Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
  873. }
  874. [Theory]
  875. [InlineData ("Single Line 界", 14)]
  876. [InlineData ("First Line 界\nSecond Line 界\nThird Line 界\n", 14)]
  877. public void MaxWidthLine_With_And_Without_Newlines (string text, int expected) { Assert.Equal (expected, TextFormatter.GetWidestLineLength (text)); }
  878. [Theory]
  879. [InlineData (
  880. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  881. 0,
  882. 0,
  883. false,
  884. new [] { "" }
  885. )]
  886. [InlineData (
  887. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  888. 0,
  889. 1,
  890. false,
  891. new [] { "" }
  892. )]
  893. [InlineData (
  894. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  895. 1,
  896. 0,
  897. false,
  898. new [] { "" }
  899. )]
  900. [InlineData (
  901. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  902. 0,
  903. 0,
  904. true,
  905. new [] { "" }
  906. )]
  907. [InlineData (
  908. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  909. 0,
  910. 1,
  911. true,
  912. new [] { "" }
  913. )]
  914. [InlineData (
  915. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  916. 1,
  917. 0,
  918. true,
  919. new [] { "" }
  920. )]
  921. [InlineData (
  922. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  923. 6,
  924. 5,
  925. false,
  926. new [] { "First " }
  927. )]
  928. [InlineData ("1\n2\n3\n4\n5\n6", 6, 5, false, new [] { "1 2 3 " })]
  929. [InlineData (
  930. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  931. 6,
  932. 5,
  933. true,
  934. new [] { "First ", "Second", "Third ", "Forty ", "Fiftee" }
  935. )]
  936. [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, false, new [] { "第一" })]
  937. [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, true, new [] { "第一", "第二", "第三", "四十", "第十" })]
  938. public void MultiLine_WordWrap_False_Horizontal_Direction (
  939. string text,
  940. int maxWidth,
  941. int maxHeight,
  942. bool multiLine,
  943. IEnumerable<string> resultLines
  944. )
  945. {
  946. var tf = new TextFormatter
  947. {
  948. Text = text, ConstrainToSize = new (maxWidth, maxHeight), WordWrap = false, MultiLine = multiLine
  949. };
  950. Assert.False (tf.WordWrap);
  951. Assert.True (tf.MultiLine == multiLine);
  952. Assert.Equal (TextDirection.LeftRight_TopBottom, tf.Direction);
  953. List<string> splitLines = tf.GetLines ();
  954. Assert.Equal (splitLines.Count, resultLines.Count ());
  955. Assert.Equal (splitLines, resultLines);
  956. }
  957. [Theory]
  958. [InlineData (
  959. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  960. 0,
  961. 0,
  962. false,
  963. new [] { "" }
  964. )]
  965. [InlineData (
  966. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  967. 0,
  968. 1,
  969. false,
  970. new [] { "" }
  971. )]
  972. [InlineData (
  973. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  974. 1,
  975. 0,
  976. false,
  977. new [] { "" }
  978. )]
  979. [InlineData (
  980. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  981. 0,
  982. 0,
  983. true,
  984. new [] { "" }
  985. )]
  986. [InlineData (
  987. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  988. 0,
  989. 1,
  990. true,
  991. new [] { "" }
  992. )]
  993. [InlineData (
  994. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  995. 1,
  996. 0,
  997. true,
  998. new [] { "" }
  999. )]
  1000. [InlineData (
  1001. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  1002. 6,
  1003. 5,
  1004. false,
  1005. new [] { "First" }
  1006. )]
  1007. [InlineData ("1\n2\n3\n4\n5\n6", 6, 5, false, new [] { "1 2 3" })]
  1008. [InlineData (
  1009. "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line",
  1010. 6,
  1011. 5,
  1012. true,
  1013. new [] { "First", "Secon", "Third", "Forty", "Fifte", "Seven" }
  1014. )]
  1015. [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, false, new [] { "第一行 第" })]
  1016. [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, true, new [] { "第一行", "第二行" })]
  1017. public void MultiLine_WordWrap_False_Vertical_Direction (
  1018. string text,
  1019. int maxWidth,
  1020. int maxHeight,
  1021. bool multiLine,
  1022. IEnumerable<string> resultLines
  1023. )
  1024. {
  1025. var tf = new TextFormatter
  1026. {
  1027. Text = text,
  1028. ConstrainToSize = new (maxWidth, maxHeight),
  1029. WordWrap = false,
  1030. MultiLine = multiLine,
  1031. Direction = TextDirection.TopBottom_LeftRight
  1032. };
  1033. Assert.False (tf.WordWrap);
  1034. Assert.True (tf.MultiLine == multiLine);
  1035. Assert.Equal (TextDirection.TopBottom_LeftRight, tf.Direction);
  1036. List<string> splitLines = tf.GetLines ();
  1037. Assert.Equal (splitLines.Count, resultLines.Count ());
  1038. Assert.Equal (splitLines, resultLines);
  1039. }
  1040. [Fact]
  1041. public void NeedsFormat_Sets ()
  1042. {
  1043. var testText = "test";
  1044. var testBounds = new Rectangle (0, 0, 100, 1);
  1045. var tf = new TextFormatter ();
  1046. tf.Text = "test";
  1047. Assert.True (tf.NeedsFormat); // get_Lines causes a Format
  1048. Assert.NotEmpty (tf.GetLines ());
  1049. Assert.False (tf.NeedsFormat); // get_Lines causes a Format
  1050. Assert.Equal (testText, tf.Text);
  1051. tf.Draw (testBounds, new (), new ());
  1052. Assert.False (tf.NeedsFormat);
  1053. tf.ConstrainToSize = new (1, 1);
  1054. Assert.True (tf.NeedsFormat);
  1055. Assert.NotEmpty (tf.GetLines ());
  1056. Assert.False (tf.NeedsFormat); // get_Lines causes a Format
  1057. tf.Alignment = Alignment.Center;
  1058. Assert.True (tf.NeedsFormat);
  1059. Assert.NotEmpty (tf.GetLines ());
  1060. Assert.False (tf.NeedsFormat); // get_Lines causes a Format
  1061. }
  1062. // Test that changing TextFormatter does not impact View dimensions if Dim.Auto is not in play
  1063. [Fact]
  1064. public void Not_Used_TextFormatter_Does_Not_Change_View_Size ()
  1065. {
  1066. View view = new ()
  1067. {
  1068. Text = "_1234"
  1069. };
  1070. Assert.Equal (Size.Empty, view.Frame.Size);
  1071. view.TextFormatter.Text = "ABC";
  1072. Assert.Equal (Size.Empty, view.Frame.Size);
  1073. view.TextFormatter.Alignment = Alignment.Fill;
  1074. Assert.Equal (Size.Empty, view.Frame.Size);
  1075. view.TextFormatter.VerticalAlignment = Alignment.Center;
  1076. Assert.Equal (Size.Empty, view.Frame.Size);
  1077. view.TextFormatter.HotKeySpecifier = (Rune)'*';
  1078. Assert.Equal (Size.Empty, view.Frame.Size);
  1079. view.TextFormatter.Text = "*ABC";
  1080. Assert.Equal (Size.Empty, view.Frame.Size);
  1081. }
  1082. [Fact]
  1083. public void Not_Used_TextSettings_Do_Not_Change_View_Size ()
  1084. {
  1085. View view = new ()
  1086. {
  1087. Text = "_1234"
  1088. };
  1089. Assert.Equal (Size.Empty, view.Frame.Size);
  1090. view.TextAlignment = Alignment.Fill;
  1091. Assert.Equal (Size.Empty, view.Frame.Size);
  1092. view.VerticalTextAlignment = Alignment.Center;
  1093. Assert.Equal (Size.Empty, view.Frame.Size);
  1094. view.HotKeySpecifier = (Rune)'*';
  1095. Assert.Equal (Size.Empty, view.Frame.Size);
  1096. view.Text = "*ABC";
  1097. Assert.Equal (Size.Empty, view.Frame.Size);
  1098. }
  1099. [Theory]
  1100. [InlineData ("", -1, Alignment.Start, false, 0)]
  1101. [InlineData (null, 0, Alignment.Start, false, 1)]
  1102. [InlineData (null, 0, Alignment.Start, true, 1)]
  1103. [InlineData ("", 0, Alignment.Start, false, 1)]
  1104. [InlineData ("", 0, Alignment.Start, true, 1)]
  1105. public void Reformat_Invalid (string text, int maxWidth, Alignment alignment, bool wrap, int linesCount)
  1106. {
  1107. if (maxWidth < 0)
  1108. {
  1109. Assert.Throws<ArgumentOutOfRangeException> (
  1110. () =>
  1111. TextFormatter.Format (text, maxWidth, alignment, wrap)
  1112. );
  1113. }
  1114. else
  1115. {
  1116. List<string> list = TextFormatter.Format (text, maxWidth, alignment, wrap);
  1117. Assert.NotEmpty (list);
  1118. Assert.True (list.Count == linesCount);
  1119. Assert.Equal (string.Empty, list [0]);
  1120. }
  1121. }
  1122. [Theory]
  1123. [InlineData ("A sentence has words.\nLine 2.", 0, -29, Alignment.Start, false, 1, true)]
  1124. [InlineData ("A sentence has words.\nLine 2.", 1, -28, Alignment.Start, false, 1, false)]
  1125. [InlineData ("A sentence has words.\nLine 2.", 5, -24, Alignment.Start, false, 1, false)]
  1126. [InlineData ("A sentence has words.\nLine 2.", 28, -1, Alignment.Start, false, 1, false)]
  1127. // no clip
  1128. [InlineData ("A sentence has words.\nLine 2.", 29, 0, Alignment.Start, false, 1, false)]
  1129. [InlineData ("A sentence has words.\nLine 2.", 30, 1, Alignment.Start, false, 1, false)]
  1130. [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, Alignment.Start, false, 1, true)]
  1131. [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, Alignment.Start, false, 1, false)]
  1132. [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, Alignment.Start, false, 1, false)]
  1133. [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, Alignment.Start, false, 1, false, 1)]
  1134. [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, Alignment.Start, false, 1, false)]
  1135. [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, Alignment.Start, false, 1, false)]
  1136. public void Reformat_NoWordrap_NewLines_MultiLine_False (
  1137. string text,
  1138. int maxWidth,
  1139. int widthOffset,
  1140. Alignment alignment,
  1141. bool wrap,
  1142. int linesCount,
  1143. bool stringEmpty,
  1144. int clipWidthOffset = 0
  1145. )
  1146. {
  1147. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1148. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset;
  1149. List<string> list = TextFormatter.Format (text, maxWidth, alignment, wrap);
  1150. Assert.NotEmpty (list);
  1151. Assert.True (list.Count == linesCount);
  1152. if (stringEmpty)
  1153. {
  1154. Assert.Equal (string.Empty, list [0]);
  1155. }
  1156. else
  1157. {
  1158. Assert.NotEqual (string.Empty, list [0]);
  1159. }
  1160. if (text.Contains ("\r\n") && maxWidth > 0)
  1161. {
  1162. Assert.Equal (
  1163. StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth])
  1164. .Replace ("\r\n", " "),
  1165. list [0]
  1166. );
  1167. }
  1168. else if (text.Contains ('\n') && maxWidth > 0)
  1169. {
  1170. Assert.Equal (
  1171. StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth])
  1172. .Replace ("\n", " "),
  1173. list [0]
  1174. );
  1175. }
  1176. else
  1177. {
  1178. Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]);
  1179. }
  1180. }
  1181. [Theory]
  1182. [InlineData ("A sentence has words.\nLine 2.", 0, -29, Alignment.Start, false, 1, true, new [] { "" })]
  1183. [InlineData (
  1184. "A sentence has words.\nLine 2.",
  1185. 1,
  1186. -28,
  1187. Alignment.Start,
  1188. false,
  1189. 2,
  1190. false,
  1191. new [] { "A", "L" }
  1192. )]
  1193. [InlineData (
  1194. "A sentence has words.\nLine 2.",
  1195. 5,
  1196. -24,
  1197. Alignment.Start,
  1198. false,
  1199. 2,
  1200. false,
  1201. new [] { "A sen", "Line " }
  1202. )]
  1203. [InlineData (
  1204. "A sentence has words.\nLine 2.",
  1205. 28,
  1206. -1,
  1207. Alignment.Start,
  1208. false,
  1209. 2,
  1210. false,
  1211. new [] { "A sentence has words.", "Line 2." }
  1212. )]
  1213. //// no clip
  1214. [InlineData (
  1215. "A sentence has words.\nLine 2.",
  1216. 29,
  1217. 0,
  1218. Alignment.Start,
  1219. false,
  1220. 2,
  1221. false,
  1222. new [] { "A sentence has words.", "Line 2." }
  1223. )]
  1224. [InlineData (
  1225. "A sentence has words.\nLine 2.",
  1226. 30,
  1227. 1,
  1228. Alignment.Start,
  1229. false,
  1230. 2,
  1231. false,
  1232. new [] { "A sentence has words.", "Line 2." }
  1233. )]
  1234. [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, Alignment.Start, false, 1, true, new [] { "" })]
  1235. [InlineData (
  1236. "A sentence has words.\r\nLine 2.",
  1237. 1,
  1238. -29,
  1239. Alignment.Start,
  1240. false,
  1241. 2,
  1242. false,
  1243. new [] { "A", "L" }
  1244. )]
  1245. [InlineData (
  1246. "A sentence has words.\r\nLine 2.",
  1247. 5,
  1248. -25,
  1249. Alignment.Start,
  1250. false,
  1251. 2,
  1252. false,
  1253. new [] { "A sen", "Line " }
  1254. )]
  1255. [InlineData (
  1256. "A sentence has words.\r\nLine 2.",
  1257. 29,
  1258. -1,
  1259. Alignment.Start,
  1260. false,
  1261. 2,
  1262. false,
  1263. new [] { "A sentence has words.", "Line 2." }
  1264. )]
  1265. [InlineData (
  1266. "A sentence has words.\r\nLine 2.",
  1267. 30,
  1268. 0,
  1269. Alignment.Start,
  1270. false,
  1271. 2,
  1272. false,
  1273. new [] { "A sentence has words.", "Line 2." }
  1274. )]
  1275. [InlineData (
  1276. "A sentence has words.\r\nLine 2.",
  1277. 31,
  1278. 1,
  1279. Alignment.Start,
  1280. false,
  1281. 2,
  1282. false,
  1283. new [] { "A sentence has words.", "Line 2." }
  1284. )]
  1285. public void Reformat_NoWordrap_NewLines_MultiLine_True (
  1286. string text,
  1287. int maxWidth,
  1288. int widthOffset,
  1289. Alignment alignment,
  1290. bool wrap,
  1291. int linesCount,
  1292. bool stringEmpty,
  1293. IEnumerable<string> resultLines
  1294. )
  1295. {
  1296. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1297. List<string> list = TextFormatter.Format (
  1298. text,
  1299. maxWidth,
  1300. alignment,
  1301. wrap,
  1302. false,
  1303. 0,
  1304. TextDirection.LeftRight_TopBottom,
  1305. true
  1306. );
  1307. Assert.NotEmpty (list);
  1308. Assert.True (list.Count == linesCount);
  1309. if (stringEmpty)
  1310. {
  1311. Assert.Equal (string.Empty, list [0]);
  1312. }
  1313. else
  1314. {
  1315. Assert.NotEqual (string.Empty, list [0]);
  1316. }
  1317. Assert.Equal (list, resultLines);
  1318. }
  1319. [Theory]
  1320. [InlineData ("A sentence has words.\nLine 2.", 0, -29, Alignment.Start, false, 1, true, new [] { "" })]
  1321. [InlineData (
  1322. "A sentence has words.\nLine 2.",
  1323. 1,
  1324. -28,
  1325. Alignment.Start,
  1326. false,
  1327. 2,
  1328. false,
  1329. new [] { "A", "L" }
  1330. )]
  1331. [InlineData (
  1332. "A sentence has words.\nLine 2.",
  1333. 5,
  1334. -24,
  1335. Alignment.Start,
  1336. false,
  1337. 2,
  1338. false,
  1339. new [] { "A sen", "Line " }
  1340. )]
  1341. [InlineData (
  1342. "A sentence has words.\nLine 2.",
  1343. 28,
  1344. -1,
  1345. Alignment.Start,
  1346. false,
  1347. 2,
  1348. false,
  1349. new [] { "A sentence has words.", "Line 2." }
  1350. )]
  1351. //// no clip
  1352. [InlineData (
  1353. "A sentence has words.\nLine 2.",
  1354. 29,
  1355. 0,
  1356. Alignment.Start,
  1357. false,
  1358. 2,
  1359. false,
  1360. new [] { "A sentence has words.", "Line 2." }
  1361. )]
  1362. [InlineData (
  1363. "A sentence has words.\nLine 2.",
  1364. 30,
  1365. 1,
  1366. Alignment.Start,
  1367. false,
  1368. 2,
  1369. false,
  1370. new [] { "A sentence has words.", "Line 2." }
  1371. )]
  1372. [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, Alignment.Start, false, 1, true, new [] { "" })]
  1373. [InlineData (
  1374. "A sentence has words.\r\nLine 2.",
  1375. 1,
  1376. -29,
  1377. Alignment.Start,
  1378. false,
  1379. 2,
  1380. false,
  1381. new [] { "A", "L" }
  1382. )]
  1383. [InlineData (
  1384. "A sentence has words.\r\nLine 2.",
  1385. 5,
  1386. -25,
  1387. Alignment.Start,
  1388. false,
  1389. 2,
  1390. false,
  1391. new [] { "A sen", "Line " }
  1392. )]
  1393. [InlineData (
  1394. "A sentence has words.\r\nLine 2.",
  1395. 29,
  1396. -1,
  1397. Alignment.Start,
  1398. false,
  1399. 2,
  1400. false,
  1401. new [] { "A sentence has words.", "Line 2." }
  1402. )]
  1403. [InlineData (
  1404. "A sentence has words.\r\nLine 2.",
  1405. 30,
  1406. 0,
  1407. Alignment.Start,
  1408. false,
  1409. 2,
  1410. false,
  1411. new [] { "A sentence has words.", "Line 2." }
  1412. )]
  1413. [InlineData (
  1414. "A sentence has words.\r\nLine 2.",
  1415. 31,
  1416. 1,
  1417. Alignment.Start,
  1418. false,
  1419. 2,
  1420. false,
  1421. new [] { "A sentence has words.", "Line 2." }
  1422. )]
  1423. public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical (
  1424. string text,
  1425. int maxWidth,
  1426. int widthOffset,
  1427. Alignment alignment,
  1428. bool wrap,
  1429. int linesCount,
  1430. bool stringEmpty,
  1431. IEnumerable<string> resultLines
  1432. )
  1433. {
  1434. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1435. List<string> list = TextFormatter.Format (
  1436. text,
  1437. maxWidth,
  1438. alignment,
  1439. wrap,
  1440. false,
  1441. 0,
  1442. TextDirection.TopBottom_LeftRight,
  1443. true
  1444. );
  1445. Assert.NotEmpty (list);
  1446. Assert.True (list.Count == linesCount);
  1447. if (stringEmpty)
  1448. {
  1449. Assert.Equal (string.Empty, list [0]);
  1450. }
  1451. else
  1452. {
  1453. Assert.NotEqual (string.Empty, list [0]);
  1454. }
  1455. Assert.Equal (list, resultLines);
  1456. }
  1457. [Theory]
  1458. [InlineData ("", 0, 0, Alignment.Start, false, 1, true)]
  1459. [InlineData ("", 1, 1, Alignment.Start, false, 1, true)]
  1460. [InlineData ("A sentence has words.", 0, -21, Alignment.Start, false, 1, true)]
  1461. [InlineData ("A sentence has words.", 1, -20, Alignment.Start, false, 1, false)]
  1462. [InlineData ("A sentence has words.", 5, -16, Alignment.Start, false, 1, false)]
  1463. [InlineData ("A sentence has words.", 20, -1, Alignment.Start, false, 1, false)]
  1464. // no clip
  1465. [InlineData ("A sentence has words.", 21, 0, Alignment.Start, false, 1, false)]
  1466. [InlineData ("A sentence has words.", 22, 1, Alignment.Start, false, 1, false)]
  1467. public void Reformat_NoWordrap_SingleLine (
  1468. string text,
  1469. int maxWidth,
  1470. int widthOffset,
  1471. Alignment alignment,
  1472. bool wrap,
  1473. int linesCount,
  1474. bool stringEmpty
  1475. )
  1476. {
  1477. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1478. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  1479. List<string> list = TextFormatter.Format (text, maxWidth, alignment, wrap);
  1480. Assert.NotEmpty (list);
  1481. Assert.True (list.Count == linesCount);
  1482. if (stringEmpty)
  1483. {
  1484. Assert.Equal (string.Empty, list [0]);
  1485. }
  1486. else
  1487. {
  1488. Assert.NotEqual (string.Empty, list [0]);
  1489. }
  1490. Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]);
  1491. }
  1492. [Theory]
  1493. // Unicode
  1494. [InlineData (
  1495. "\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464",
  1496. 8,
  1497. -1,
  1498. Alignment.Start,
  1499. true,
  1500. false,
  1501. new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" }
  1502. )]
  1503. // no clip
  1504. [InlineData (
  1505. "\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464",
  1506. 9,
  1507. 0,
  1508. Alignment.Start,
  1509. true,
  1510. false,
  1511. new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" }
  1512. )]
  1513. [InlineData (
  1514. "\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464",
  1515. 10,
  1516. 1,
  1517. Alignment.Start,
  1518. true,
  1519. false,
  1520. new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" }
  1521. )]
  1522. public void Reformat_Unicode_Wrap_Spaces_NewLines (
  1523. string text,
  1524. int maxWidth,
  1525. int widthOffset,
  1526. Alignment alignment,
  1527. bool wrap,
  1528. bool preserveTrailingSpaces,
  1529. IEnumerable<string> resultLines
  1530. )
  1531. {
  1532. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1533. List<string> list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces);
  1534. Assert.Equal (list.Count, resultLines.Count ());
  1535. Assert.Equal (resultLines, list);
  1536. }
  1537. [Theory]
  1538. // Unicode
  1539. // Even # of chars
  1540. // 0123456789
  1541. [InlineData ("\u2660пÑРвРÑ", 10, -1, Alignment.Start, true, false, new [] { "\u2660пÑРвÐ", "Ñ" })]
  1542. // no clip
  1543. [InlineData ("\u2660пÑРвРÑ", 11, 0, Alignment.Start, true, false, new [] { "\u2660пÑРвРÑ" })]
  1544. [InlineData ("\u2660пÑРвРÑ", 12, 1, Alignment.Start, true, false, new [] { "\u2660пÑРвРÑ" })]
  1545. // Unicode
  1546. // Odd # of chars
  1547. // 0123456789
  1548. [InlineData ("\u2660 ÑРвРÑ", 9, -1, Alignment.Start, true, false, new [] { "\u2660 ÑРвÐ", "Ñ" })]
  1549. // no clip
  1550. [InlineData ("\u2660 ÑРвРÑ", 10, 0, Alignment.Start, true, false, new [] { "\u2660 ÑРвРÑ" })]
  1551. [InlineData ("\u2660 ÑРвРÑ", 11, 1, Alignment.Start, true, false, new [] { "\u2660 ÑРвРÑ" })]
  1552. public void Reformat_Unicode_Wrap_Spaces_No_NewLines (
  1553. string text,
  1554. int maxWidth,
  1555. int widthOffset,
  1556. Alignment alignment,
  1557. bool wrap,
  1558. bool preserveTrailingSpaces,
  1559. IEnumerable<string> resultLines
  1560. )
  1561. {
  1562. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1563. List<string> list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces);
  1564. Assert.Equal (list.Count, resultLines.Count ());
  1565. Assert.Equal (resultLines, list);
  1566. }
  1567. [Theory]
  1568. // Even # of spaces
  1569. // 0123456789
  1570. [InlineData ("012 456 89", 0, -10, Alignment.Start, true, true, true, new [] { "" })]
  1571. [InlineData (
  1572. "012 456 89",
  1573. 1,
  1574. -9,
  1575. Alignment.Start,
  1576. true,
  1577. true,
  1578. false,
  1579. new [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" },
  1580. "01245689"
  1581. )]
  1582. [InlineData ("012 456 89", 5, -5, Alignment.Start, true, true, false, new [] { "012 ", "456 ", "89" })]
  1583. [InlineData ("012 456 89", 9, -1, Alignment.Start, true, true, false, new [] { "012 456 ", "89" })]
  1584. // no clip
  1585. [InlineData ("012 456 89", 10, 0, Alignment.Start, true, true, false, new [] { "012 456 89" })]
  1586. [InlineData ("012 456 89", 11, 1, Alignment.Start, true, true, false, new [] { "012 456 89" })]
  1587. // Odd # of spaces
  1588. // 01234567890123
  1589. [InlineData ("012 456 89 end", 13, -1, Alignment.Start, true, true, false, new [] { "012 456 89 ", "end" })]
  1590. // no clip
  1591. [InlineData ("012 456 89 end", 14, 0, Alignment.Start, true, true, false, new [] { "012 456 89 end" })]
  1592. [InlineData ("012 456 89 end", 15, 1, Alignment.Start, true, true, false, new [] { "012 456 89 end" })]
  1593. public void Reformat_Wrap_Spaces_No_NewLines (
  1594. string text,
  1595. int maxWidth,
  1596. int widthOffset,
  1597. Alignment alignment,
  1598. bool wrap,
  1599. bool preserveTrailingSpaces,
  1600. bool stringEmpty,
  1601. IEnumerable<string> resultLines,
  1602. string noSpaceText = ""
  1603. )
  1604. {
  1605. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1606. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  1607. List<string> list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces);
  1608. Assert.NotEmpty (list);
  1609. Assert.True (list.Count == resultLines.Count ());
  1610. if (stringEmpty)
  1611. {
  1612. Assert.Equal (string.Empty, list [0]);
  1613. }
  1614. else
  1615. {
  1616. Assert.NotEqual (string.Empty, list [0]);
  1617. }
  1618. Assert.Equal (resultLines, list);
  1619. if (maxWidth > 0)
  1620. {
  1621. // remove whitespace chars
  1622. if (maxWidth < 5)
  1623. {
  1624. expectedClippedWidth = text.GetRuneCount () - text.Sum (r => r == ' ' ? 1 : 0);
  1625. }
  1626. else
  1627. {
  1628. expectedClippedWidth = Math.Min (
  1629. text.GetRuneCount (),
  1630. maxWidth - text.Sum (r => r == ' ' ? 1 : 0)
  1631. );
  1632. }
  1633. list = TextFormatter.Format (text, maxWidth, Alignment.Start, wrap);
  1634. if (maxWidth == 1)
  1635. {
  1636. Assert.Equal (expectedClippedWidth, list.Count);
  1637. Assert.Equal (noSpaceText, string.Concat (list.ToArray ()));
  1638. }
  1639. if (maxWidth > 1 && maxWidth < 10)
  1640. {
  1641. Assert.Equal (
  1642. StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]),
  1643. list [0]
  1644. );
  1645. }
  1646. }
  1647. }
  1648. [Theory]
  1649. [InlineData (null)]
  1650. [InlineData ("")]
  1651. [InlineData ("a")]
  1652. public void RemoveHotKeySpecifier_InValid_ReturnsOriginal (string text)
  1653. {
  1654. var hotKeySpecifier = (Rune)'_';
  1655. if (text == null)
  1656. {
  1657. Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier));
  1658. Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier));
  1659. Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier));
  1660. }
  1661. else
  1662. {
  1663. Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier));
  1664. Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier));
  1665. Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier));
  1666. }
  1667. }
  1668. [Theory]
  1669. [InlineData ("all lower case", 0)]
  1670. [InlineData ("K Before", 0)]
  1671. [InlineData ("aK Second", 1)]
  1672. [InlineData ("Last K", 5)]
  1673. [InlineData ("fter K", 7)]
  1674. [InlineData ("Multiple K and R", 9)]
  1675. [InlineData ("Non-english: Кдать", 13)]
  1676. public void RemoveHotKeySpecifier_Valid_Legacy_ReturnsOriginal (string text, int hotPos)
  1677. {
  1678. var hotKeySpecifier = (Rune)'_';
  1679. Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier));
  1680. }
  1681. [Theory]
  1682. [InlineData ("_K Before", 0, "K Before")]
  1683. [InlineData ("a_K Second", 1, "aK Second")]
  1684. [InlineData ("Last _K", 5, "Last K")]
  1685. [InlineData ("After K_", 7, "After K")]
  1686. [InlineData ("Multiple _K and _R", 9, "Multiple K and _R")]
  1687. [InlineData ("Non-english: _Кдать", 13, "Non-english: Кдать")]
  1688. public void RemoveHotKeySpecifier_Valid_ReturnsStripped (string text, int hotPos, string expectedText)
  1689. {
  1690. var hotKeySpecifier = (Rune)'_';
  1691. Assert.Equal (expectedText, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier));
  1692. }
  1693. [Theory]
  1694. [InlineData ("test", 0, 't', "test")]
  1695. [InlineData ("test", 1, 'e', "test")]
  1696. [InlineData ("Ok", 0, 'O', "Ok")]
  1697. [InlineData ("[◦ Ok ◦]", 3, 'O', "[◦ Ok ◦]")]
  1698. [InlineData ("^k", 0, '^', "^k")]
  1699. public void ReplaceHotKeyWithTag (string text, int hotPos, uint tag, string expected)
  1700. {
  1701. var tf = new TextFormatter ();
  1702. List<Rune> runes = text.ToRuneList ();
  1703. Rune rune;
  1704. if (Rune.TryGetRuneAt (text, hotPos, out rune))
  1705. {
  1706. Assert.Equal (rune, (Rune)tag);
  1707. }
  1708. string result = TextFormatter.ReplaceHotKeyWithTag (text, hotPos);
  1709. Assert.Equal (result, expected);
  1710. Assert.Equal ((Rune)tag, result.ToRunes () [hotPos]);
  1711. Assert.Equal (text.GetRuneCount (), runes.Count);
  1712. Assert.Equal (text, StringExtensions.ToString (runes));
  1713. }
  1714. public static IEnumerable<object []> SplitEnvironmentNewLine =>
  1715. new List<object []>
  1716. {
  1717. new object []
  1718. {
  1719. $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界",
  1720. new [] { "First Line 界", "Second Line 界", "Third Line 界" }
  1721. },
  1722. new object []
  1723. {
  1724. $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}",
  1725. new [] { "First Line 界", "Second Line 界", "Third Line 界", "" }
  1726. }
  1727. };
  1728. [Theory]
  1729. [MemberData (nameof (SplitEnvironmentNewLine))]
  1730. public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF (
  1731. string text,
  1732. IEnumerable<string> expected
  1733. )
  1734. {
  1735. List<string> splited = TextFormatter.SplitNewLine (text);
  1736. Assert.Equal (expected, splited);
  1737. }
  1738. [Theory]
  1739. [InlineData (
  1740. "First Line 界\nSecond Line 界\nThird Line 界\n",
  1741. new [] { "First Line 界", "Second Line 界", "Third Line 界", "" }
  1742. )]
  1743. public void SplitNewLine_Ending_With_NewLine_Only_LF (string text, IEnumerable<string> expected)
  1744. {
  1745. List<string> splited = TextFormatter.SplitNewLine (text);
  1746. Assert.Equal (expected, splited);
  1747. }
  1748. [Theory]
  1749. [InlineData (
  1750. "First Line 界\nSecond Line 界\nThird Line 界",
  1751. new [] { "First Line 界", "Second Line 界", "Third Line 界" }
  1752. )]
  1753. public void SplitNewLine_Ending_Without_NewLine_Only_LF (string text, IEnumerable<string> expected)
  1754. {
  1755. List<string> splited = TextFormatter.SplitNewLine (text);
  1756. Assert.Equal (expected, splited);
  1757. }
  1758. [Theory]
  1759. [InlineData ("New Test 你", 10, 10, 20320, 20320, 9, "你")]
  1760. [InlineData ("New Test \U0001d539", 10, 11, 120121, 55349, 9, "𝔹")]
  1761. public void String_Array_Is_Not_Always_Equal_ToRunes_Array (
  1762. string text,
  1763. int runesLength,
  1764. int stringLength,
  1765. int runeValue,
  1766. int stringValue,
  1767. int index,
  1768. string expected
  1769. )
  1770. {
  1771. Rune [] usToRunes = text.ToRunes ();
  1772. Assert.Equal (runesLength, usToRunes.Length);
  1773. Assert.Equal (stringLength, text.Length);
  1774. Assert.Equal (runeValue, usToRunes [index].Value);
  1775. Assert.Equal (stringValue, text [index]);
  1776. Assert.Equal (expected, usToRunes [index].ToString ());
  1777. if (char.IsHighSurrogate (text [index]))
  1778. {
  1779. // Rune array length isn't equal to string array
  1780. Assert.Equal (expected, new (new [] { text [index], text [index + 1] }));
  1781. }
  1782. else
  1783. {
  1784. // Rune array length is equal to string array
  1785. Assert.Equal (expected, text [index].ToString ());
  1786. }
  1787. }
  1788. [Theory]
  1789. [InlineData ("123456789", 3, "123")]
  1790. [InlineData ("Hello World", 8, "Hello Wo")]
  1791. public void TestClipOrPad_LongWord (string text, int fillPad, string expectedText)
  1792. {
  1793. // word is long but we want it to fill # space only
  1794. Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad));
  1795. }
  1796. [Theory]
  1797. [InlineData ("fff", 6, "fff ")]
  1798. [InlineData ("Hello World", 16, "Hello World ")]
  1799. public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedText)
  1800. {
  1801. // word is short but we want it to fill # so it should be padded
  1802. Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad));
  1803. }
  1804. [Theory]
  1805. [InlineData ("你", TextDirection.LeftRight_TopBottom, 2, 1)]
  1806. [InlineData ("你", TextDirection.TopBottom_LeftRight, 2, 1)]
  1807. [InlineData ("你你", TextDirection.LeftRight_TopBottom, 4, 1)]
  1808. [InlineData ("你你", TextDirection.TopBottom_LeftRight, 2, 2)]
  1809. public void Text_Set_SizeIsCorrect (string text, TextDirection textDirection, int expectedWidth, int expectedHeight)
  1810. {
  1811. var tf = new TextFormatter { Direction = textDirection, Text = text };
  1812. tf.ConstrainToWidth = 10;
  1813. tf.ConstrainToHeight = 10;
  1814. Assert.Equal (new (expectedWidth, expectedHeight), tf.FormatAndGetSize ());
  1815. }
  1816. [Fact]
  1817. public void WordWrap_BigWidth ()
  1818. {
  1819. List<string> wrappedLines;
  1820. var text = "Constantinople";
  1821. wrappedLines = TextFormatter.WordWrapText (text, 100);
  1822. Assert.True (wrappedLines.Count == 1);
  1823. Assert.Equal ("Constantinople", wrappedLines [0]);
  1824. }
  1825. [Fact]
  1826. public void WordWrap_Invalid ()
  1827. {
  1828. var text = string.Empty;
  1829. var width = 0;
  1830. Assert.Empty (TextFormatter.WordWrapText (null, width));
  1831. Assert.Empty (TextFormatter.WordWrapText (text, width));
  1832. Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.WordWrapText (text, -1));
  1833. }
  1834. [Theory]
  1835. [InlineData ("A sentence has words.", 3, -18, new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })]
  1836. [InlineData (
  1837. "A sentence has words.",
  1838. 2,
  1839. -19,
  1840. new [] { "A", "se", "nt", "en", "ce", "ha", "s", "wo", "rd", "s." }
  1841. )]
  1842. [InlineData (
  1843. "A sentence has words.",
  1844. 1,
  1845. -20,
  1846. new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." }
  1847. )]
  1848. public void WordWrap_Narrow_Default (
  1849. string text,
  1850. int maxWidth,
  1851. int widthOffset,
  1852. IEnumerable<string> resultLines
  1853. )
  1854. {
  1855. // Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false
  1856. List<string> wrappedLines;
  1857. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1858. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  1859. wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
  1860. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  1861. Assert.True (
  1862. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  1863. );
  1864. Assert.True (
  1865. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  1866. );
  1867. Assert.Equal (resultLines, wrappedLines);
  1868. }
  1869. [Theory]
  1870. [InlineData ("A sentence has words.", 21, 0, new [] { "A sentence has words." })]
  1871. [InlineData ("A sentence has words.", 20, -1, new [] { "A sentence has", "words." })]
  1872. [InlineData ("A sentence has words.", 15, -6, new [] { "A sentence has", "words." })]
  1873. [InlineData ("A sentence has words.", 14, -7, new [] { "A sentence has", "words." })]
  1874. [InlineData ("A sentence has words.", 13, -8, new [] { "A sentence", "has words." })]
  1875. // Unicode
  1876. [InlineData (
  1877. "A Unicode sentence (пÑивеÑ) has words.",
  1878. 42,
  1879. 0,
  1880. new [] { "A Unicode sentence (пÑивеÑ) has words." }
  1881. )]
  1882. [InlineData (
  1883. "A Unicode sentence (пÑивеÑ) has words.",
  1884. 41,
  1885. -1,
  1886. new [] { "A Unicode sentence (пÑивеÑ) has", "words." }
  1887. )]
  1888. [InlineData (
  1889. "A Unicode sentence (пÑивеÑ) has words.",
  1890. 36,
  1891. -6,
  1892. new [] { "A Unicode sentence (пÑивеÑ) has", "words." }
  1893. )]
  1894. [InlineData (
  1895. "A Unicode sentence (пÑивеÑ) has words.",
  1896. 35,
  1897. -7,
  1898. new [] { "A Unicode sentence (пÑивеÑ) has", "words." }
  1899. )]
  1900. [InlineData (
  1901. "A Unicode sentence (пÑивеÑ) has words.",
  1902. 34,
  1903. -8,
  1904. new [] { "A Unicode sentence (пÑивеÑ)", "has words." }
  1905. )]
  1906. [InlineData (
  1907. "A Unicode sentence (пÑивеÑ) has words.",
  1908. 25,
  1909. -17,
  1910. new [] { "A Unicode sentence", "(пÑивеÑ) has words." }
  1911. )]
  1912. public void WordWrap_NoNewLines_Default (
  1913. string text,
  1914. int maxWidth,
  1915. int widthOffset,
  1916. IEnumerable<string> resultLines
  1917. )
  1918. {
  1919. // Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false
  1920. List<string> wrappedLines;
  1921. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1922. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  1923. wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
  1924. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  1925. Assert.True (
  1926. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  1927. );
  1928. Assert.True (
  1929. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  1930. );
  1931. Assert.Equal (resultLines, wrappedLines);
  1932. }
  1933. [Theory]
  1934. [InlineData ("これが最初の行です。 こんにちは世界。 これが2行目です。", 29, 0, new [] { "これが最初の行です。", "こんにちは世界。", "これが2行目です。" })]
  1935. public void WordWrap_PreserveTrailingSpaces_False_Unicode_Wide_Runes (
  1936. string text,
  1937. int maxWidth,
  1938. int widthOffset,
  1939. IEnumerable<string> resultLines
  1940. )
  1941. {
  1942. List<string> wrappedLines;
  1943. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1944. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  1945. wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
  1946. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  1947. Assert.True (
  1948. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  1949. );
  1950. Assert.True (
  1951. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  1952. );
  1953. Assert.Equal (resultLines, wrappedLines);
  1954. }
  1955. [Theory]
  1956. [InlineData ("文に は言葉 があり ます。", 14, 0, new [] { "文に は言葉", "があり ます。" })]
  1957. [InlineData ("文に は言葉 があり ます。", 3, -11, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
  1958. [InlineData ("文に は言葉 があり ます。", 2, -12, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
  1959. [InlineData (
  1960. "文に は言葉 があり ます。",
  1961. 1,
  1962. -13,
  1963. new [] { " ", " ", " " }
  1964. )] // Just Spaces; should result in a single space for each line
  1965. public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes (
  1966. string text,
  1967. int maxWidth,
  1968. int widthOffset,
  1969. IEnumerable<string> resultLines
  1970. )
  1971. {
  1972. List<string> wrappedLines;
  1973. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  1974. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  1975. wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
  1976. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  1977. Assert.True (
  1978. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  1979. );
  1980. Assert.True (
  1981. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  1982. );
  1983. Assert.Equal (resultLines, wrappedLines);
  1984. }
  1985. [Theory]
  1986. [InlineData (null, 1, new string [] { })] // null input
  1987. [InlineData ("", 1, new string [] { })] // Empty input
  1988. [InlineData ("1 34", 1, new [] { "1", "3", "4" })] // Single Spaces
  1989. [InlineData ("1", 1, new [] { "1" })] // Short input
  1990. [InlineData ("12", 1, new [] { "1", "2" })]
  1991. [InlineData ("123", 1, new [] { "1", "2", "3" })]
  1992. [InlineData ("123456", 1, new [] { "1", "2", "3", "4", "5", "6" })] // No spaces
  1993. [InlineData (" ", 1, new [] { " " })] // Just Spaces; should result in a single space
  1994. [InlineData (" ", 1, new [] { " " })]
  1995. [InlineData (" ", 1, new [] { " ", " " })]
  1996. [InlineData (" ", 1, new [] { " ", " " })]
  1997. [InlineData ("12 456", 1, new [] { "1", "2", "4", "5", "6" })] // Single Spaces
  1998. [InlineData (" 2 456", 1, new [] { " ", "2", "4", "5", "6" })] // Leading spaces should be preserved.
  1999. [InlineData (" 2 456 8", 1, new [] { " ", "2", "4", "5", "6", "8" })]
  2000. [InlineData (
  2001. "A sentence has words. ",
  2002. 1,
  2003. new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." }
  2004. )] // Complex example
  2005. [InlineData ("12 567", 1, new [] { "1", "2", " ", "5", "6", "7" })] // Double Spaces
  2006. [InlineData (" 3 567", 1, new [] { " ", "3", "5", "6", "7" })] // Double Leading spaces should be preserved.
  2007. [InlineData (" 3 678 1", 1, new [] { " ", "3", " ", "6", "7", "8", " ", "1" })]
  2008. [InlineData ("1 456", 1, new [] { "1", " ", "4", "5", "6" })]
  2009. [InlineData (
  2010. "A sentence has words. ",
  2011. 1,
  2012. new []
  2013. {
  2014. "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", "w", "o", "r", "d", "s", ".", " "
  2015. }
  2016. )] // Double space Complex example
  2017. public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_1 (
  2018. string text,
  2019. int width,
  2020. IEnumerable<string> resultLines
  2021. )
  2022. {
  2023. List<string> wrappedLines = TextFormatter.WordWrapText (text, width);
  2024. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2025. Assert.Equal (resultLines, wrappedLines);
  2026. var breakLines = "";
  2027. foreach (string line in wrappedLines)
  2028. {
  2029. breakLines += $"{line}{Environment.NewLine}";
  2030. }
  2031. var expected = string.Empty;
  2032. foreach (string line in resultLines)
  2033. {
  2034. expected += $"{line}{Environment.NewLine}";
  2035. }
  2036. Assert.Equal (expected, breakLines);
  2037. }
  2038. [Theory]
  2039. [InlineData (null, 3, new string [] { })] // null input
  2040. [InlineData ("", 3, new string [] { })] // Empty input
  2041. [InlineData ("1", 3, new [] { "1" })] // Short input
  2042. [InlineData ("12", 3, new [] { "12" })]
  2043. [InlineData ("123", 3, new [] { "123" })]
  2044. [InlineData ("123456", 3, new [] { "123", "456" })] // No spaces
  2045. [InlineData ("1234567", 3, new [] { "123", "456", "7" })] // No spaces
  2046. [InlineData (" ", 3, new [] { " " })] // Just Spaces; should result in a single space
  2047. [InlineData (" ", 3, new [] { " " })]
  2048. [InlineData (" ", 3, new [] { " " })]
  2049. [InlineData (" ", 3, new [] { " " })]
  2050. [InlineData ("12 456", 3, new [] { "12", "456" })] // Single Spaces
  2051. [InlineData (" 2 456", 3, new [] { " 2", "456" })] // Leading spaces should be preserved.
  2052. [InlineData (" 2 456 8", 3, new [] { " 2", "456", "8" })]
  2053. [InlineData (
  2054. "A sentence has words. ",
  2055. 3,
  2056. new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." }
  2057. )] // Complex example
  2058. [InlineData ("12 567", 3, new [] { "12 ", "567" })] // Double Spaces
  2059. [InlineData (" 3 567", 3, new [] { " 3", "567" })] // Double Leading spaces should be preserved.
  2060. [InlineData (" 3 678 1", 3, new [] { " 3", " 67", "8 ", "1" })]
  2061. [InlineData ("1 456", 3, new [] { "1 ", "456" })]
  2062. [InlineData (
  2063. "A sentence has words. ",
  2064. 3,
  2065. new [] { "A ", "sen", "ten", "ce ", " ", "has", "wor", "ds.", " " }
  2066. )] // Double space Complex example
  2067. public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_3 (
  2068. string text,
  2069. int width,
  2070. IEnumerable<string> resultLines
  2071. )
  2072. {
  2073. List<string> wrappedLines = TextFormatter.WordWrapText (text, width);
  2074. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2075. Assert.Equal (resultLines, wrappedLines);
  2076. var breakLines = "";
  2077. foreach (string line in wrappedLines)
  2078. {
  2079. breakLines += $"{line}{Environment.NewLine}";
  2080. }
  2081. var expected = string.Empty;
  2082. foreach (string line in resultLines)
  2083. {
  2084. expected += $"{line}{Environment.NewLine}";
  2085. }
  2086. Assert.Equal (expected, breakLines);
  2087. }
  2088. [Theory]
  2089. [InlineData (null, 50, new string [] { })] // null input
  2090. [InlineData ("", 50, new string [] { })] // Empty input
  2091. [InlineData ("1", 50, new [] { "1" })] // Short input
  2092. [InlineData ("12", 50, new [] { "12" })]
  2093. [InlineData ("123", 50, new [] { "123" })]
  2094. [InlineData ("123456", 50, new [] { "123456" })] // No spaces
  2095. [InlineData ("1234567", 50, new [] { "1234567" })] // No spaces
  2096. [InlineData (" ", 50, new [] { " " })] // Just Spaces; should result in a single space
  2097. [InlineData (" ", 50, new [] { " " })]
  2098. [InlineData (" ", 50, new [] { " " })]
  2099. [InlineData ("12 456", 50, new [] { "12 456" })] // Single Spaces
  2100. [InlineData (" 2 456", 50, new [] { " 2 456" })] // Leading spaces should be preserved.
  2101. [InlineData (" 2 456 8", 50, new [] { " 2 456 8" })]
  2102. [InlineData ("A sentence has words. ", 50, new [] { "A sentence has words. " })] // Complex example
  2103. [InlineData ("12 567", 50, new [] { "12 567" })] // Double Spaces
  2104. [InlineData (" 3 567", 50, new [] { " 3 567" })] // Double Leading spaces should be preserved.
  2105. [InlineData (" 3 678 1", 50, new [] { " 3 678 1" })]
  2106. [InlineData ("1 456", 50, new [] { "1 456" })]
  2107. [InlineData (
  2108. "A sentence has words. ",
  2109. 50,
  2110. new [] { "A sentence has words. " }
  2111. )] // Double space Complex example
  2112. public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_50 (
  2113. string text,
  2114. int width,
  2115. IEnumerable<string> resultLines
  2116. )
  2117. {
  2118. List<string> wrappedLines = TextFormatter.WordWrapText (text, width);
  2119. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2120. Assert.Equal (resultLines, wrappedLines);
  2121. var breakLines = "";
  2122. foreach (string line in wrappedLines)
  2123. {
  2124. breakLines += $"{line}{Environment.NewLine}";
  2125. }
  2126. var expected = string.Empty;
  2127. foreach (string line in resultLines)
  2128. {
  2129. expected += $"{line}{Environment.NewLine}";
  2130. }
  2131. Assert.Equal (expected, breakLines);
  2132. }
  2133. [Theory]
  2134. [InlineData ("A sentence has words.", 14, -7, new [] { "A sentence ", "has words." })]
  2135. [InlineData ("A sentence has words.", 8, -13, new [] { "A ", "sentence", " has ", "words." })]
  2136. [InlineData ("A sentence has words.", 6, -15, new [] { "A ", "senten", "ce ", "has ", "words." })]
  2137. [InlineData ("A sentence has words.", 3, -18, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds." })]
  2138. [InlineData (
  2139. "A sentence has words.",
  2140. 2,
  2141. -19,
  2142. new [] { "A ", "se", "nt", "en", "ce", " ", "ha", "s ", "wo", "rd", "s." }
  2143. )]
  2144. [InlineData (
  2145. "A sentence has words.",
  2146. 1,
  2147. -20,
  2148. new []
  2149. {
  2150. "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "."
  2151. }
  2152. )]
  2153. public void WordWrap_PreserveTrailingSpaces_True (
  2154. string text,
  2155. int maxWidth,
  2156. int widthOffset,
  2157. IEnumerable<string> resultLines
  2158. )
  2159. {
  2160. List<string> wrappedLines;
  2161. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  2162. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  2163. wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true);
  2164. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2165. Assert.True (
  2166. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  2167. );
  2168. Assert.True (
  2169. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  2170. );
  2171. Assert.Equal (resultLines, wrappedLines);
  2172. }
  2173. [Theory]
  2174. [InlineData ("文に は言葉 があり ます。", 14, 0, new [] { "文に は言葉 ", "があり ます。" })]
  2175. [InlineData ("文に は言葉 があり ます。", 3, -11, new [] { "文", "に ", "は", "言", "葉 ", "が", "あ", "り ", "ま", "す", "。" })]
  2176. [InlineData (
  2177. "文に は言葉 があり ます。",
  2178. 2,
  2179. -12,
  2180. new [] { "文", "に", " ", "は", "言", "葉", " ", "が", "あ", "り", " ", "ま", "す", "。" }
  2181. )]
  2182. [InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { })]
  2183. public void WordWrap_PreserveTrailingSpaces_True_Wide_Runes (
  2184. string text,
  2185. int maxWidth,
  2186. int widthOffset,
  2187. IEnumerable<string> resultLines
  2188. )
  2189. {
  2190. List<string> wrappedLines;
  2191. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  2192. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  2193. wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true);
  2194. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2195. Assert.True (
  2196. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  2197. );
  2198. Assert.True (
  2199. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  2200. );
  2201. Assert.Equal (resultLines, wrappedLines);
  2202. }
  2203. [Theory]
  2204. [InlineData ("A sentence has words. ", 3, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds.", " " })]
  2205. [InlineData (
  2206. "A sentence has words. ",
  2207. 3,
  2208. new [] { "A ", " ", "sen", "ten", "ce ", " ", " ", " ", "has", " ", "wor", "ds.", " " }
  2209. )]
  2210. public void WordWrap_PreserveTrailingSpaces_True_With_Simple_Runes_Width_3 (
  2211. string text,
  2212. int width,
  2213. IEnumerable<string> resultLines
  2214. )
  2215. {
  2216. List<string> wrappedLines = TextFormatter.WordWrapText (text, width, true);
  2217. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2218. Assert.Equal (resultLines, wrappedLines);
  2219. var breakLines = "";
  2220. foreach (string line in wrappedLines)
  2221. {
  2222. breakLines += $"{line}{Environment.NewLine}";
  2223. }
  2224. var expected = string.Empty;
  2225. foreach (string line in resultLines)
  2226. {
  2227. expected += $"{line}{Environment.NewLine}";
  2228. }
  2229. Assert.Equal (expected, breakLines);
  2230. // Double space Complex example - this is how VS 2022 does it
  2231. //text = "A sentence has words. ";
  2232. //breakLines = "";
  2233. //wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true);
  2234. //foreach (var line in wrappedLines) {
  2235. // breakLines += $"{line}{Environment.NewLine}";
  2236. //}
  2237. //expected = "A " + Environment.NewLine +
  2238. // " se" + Environment.NewLine +
  2239. // " nt" + Environment.NewLine +
  2240. // " en" + Environment.NewLine +
  2241. // " ce" + Environment.NewLine +
  2242. // " " + Environment.NewLine +
  2243. // " " + Environment.NewLine +
  2244. // " " + Environment.NewLine +
  2245. // " ha" + Environment.NewLine +
  2246. // " s " + Environment.NewLine +
  2247. // " wo" + Environment.NewLine +
  2248. // " rd" + Environment.NewLine +
  2249. // " s." + Environment.NewLine;
  2250. //Assert.Equal (expected, breakLines);
  2251. }
  2252. [Theory]
  2253. [InlineData ("A sentence\t\t\t has words.", 14, -10, new [] { "A sentence\t", "\t\t has ", "words." })]
  2254. [InlineData (
  2255. "A sentence\t\t\t has words.",
  2256. 8,
  2257. -16,
  2258. new [] { "A ", "sentence", "\t\t", "\t ", "has ", "words." }
  2259. )]
  2260. [InlineData (
  2261. "A sentence\t\t\t has words.",
  2262. 3,
  2263. -21,
  2264. new [] { "A ", "sen", "ten", "ce", "\t", "\t", "\t", " ", "has", " ", "wor", "ds." }
  2265. )]
  2266. [InlineData (
  2267. "A sentence\t\t\t has words.",
  2268. 2,
  2269. -22,
  2270. new [] { "A ", "se", "nt", "en", "ce", "\t", "\t", "\t", " ", "ha", "s ", "wo", "rd", "s." }
  2271. )]
  2272. [InlineData (
  2273. "A sentence\t\t\t has words.",
  2274. 1,
  2275. -23,
  2276. new []
  2277. {
  2278. "A",
  2279. " ",
  2280. "s",
  2281. "e",
  2282. "n",
  2283. "t",
  2284. "e",
  2285. "n",
  2286. "c",
  2287. "e",
  2288. "\t",
  2289. "\t",
  2290. "\t",
  2291. " ",
  2292. "h",
  2293. "a",
  2294. "s",
  2295. " ",
  2296. "w",
  2297. "o",
  2298. "r",
  2299. "d",
  2300. "s",
  2301. "."
  2302. }
  2303. )]
  2304. public void WordWrap_PreserveTrailingSpaces_True_With_Tab (
  2305. string text,
  2306. int maxWidth,
  2307. int widthOffset,
  2308. IEnumerable<string> resultLines,
  2309. int tabWidth = 4
  2310. )
  2311. {
  2312. List<string> wrappedLines;
  2313. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  2314. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  2315. wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true, tabWidth);
  2316. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2317. Assert.True (
  2318. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  2319. );
  2320. Assert.True (
  2321. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  2322. );
  2323. Assert.Equal (resultLines, wrappedLines);
  2324. }
  2325. [Theory]
  2326. [InlineData ("Constantinople", 14, 0, new [] { "Constantinople" })]
  2327. [InlineData ("Constantinople", 12, -2, new [] { "Constantinop", "le" })]
  2328. [InlineData ("Constantinople", 9, -5, new [] { "Constanti", "nople" })]
  2329. [InlineData ("Constantinople", 7, -7, new [] { "Constan", "tinople" })]
  2330. [InlineData ("Constantinople", 5, -9, new [] { "Const", "antin", "ople" })]
  2331. [InlineData ("Constantinople", 4, -10, new [] { "Cons", "tant", "inop", "le" })]
  2332. [InlineData (
  2333. "Constantinople",
  2334. 1,
  2335. -13,
  2336. new [] { "C", "o", "n", "s", "t", "a", "n", "t", "i", "n", "o", "p", "l", "e" }
  2337. )]
  2338. public void WordWrap_SingleWordLine (
  2339. string text,
  2340. int maxWidth,
  2341. int widthOffset,
  2342. IEnumerable<string> resultLines
  2343. )
  2344. {
  2345. List<string> wrappedLines;
  2346. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  2347. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  2348. wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
  2349. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2350. Assert.True (
  2351. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  2352. );
  2353. Assert.True (
  2354. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  2355. );
  2356. Assert.Equal (resultLines, wrappedLines);
  2357. }
  2358. [Theory]
  2359. [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 20, 0, new [] { "This\u00A0is\u00A0a\u00A0sentence." })]
  2360. [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 19, -1, new [] { "This\u00A0is\u00A0a\u00A0sentence." })]
  2361. [InlineData (
  2362. "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence.",
  2363. 19,
  2364. 0,
  2365. new [] { "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence." }
  2366. )]
  2367. public void WordWrap_Unicode_2LinesWithNonBreakingSpace (
  2368. string text,
  2369. int maxWidth,
  2370. int widthOffset,
  2371. IEnumerable<string> resultLines
  2372. )
  2373. {
  2374. List<string> wrappedLines;
  2375. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  2376. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  2377. wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
  2378. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2379. Assert.True (
  2380. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  2381. );
  2382. Assert.True (
  2383. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  2384. );
  2385. Assert.Equal (resultLines, wrappedLines);
  2386. }
  2387. [Theory]
  2388. [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 19, 0, new [] { "This\u00A0is\u00A0a\u00A0sentence." })]
  2389. [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 18, -1, new [] { "This\u00A0is\u00A0a\u00A0sentence", "." })]
  2390. [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 17, -2, new [] { "This\u00A0is\u00A0a\u00A0sentenc", "e." })]
  2391. [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 14, -5, new [] { "This\u00A0is\u00A0a\u00A0sent", "ence." })]
  2392. [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 10, -9, new [] { "This\u00A0is\u00A0a\u00A0", "sentence." })]
  2393. [InlineData (
  2394. "This\u00A0is\u00A0a\u00A0sentence.",
  2395. 7,
  2396. -12,
  2397. new [] { "This\u00A0is", "\u00A0a\u00A0sent", "ence." }
  2398. )]
  2399. [InlineData (
  2400. "This\u00A0is\u00A0a\u00A0sentence.",
  2401. 5,
  2402. -14,
  2403. new [] { "This\u00A0", "is\u00A0a\u00A0", "sente", "nce." }
  2404. )]
  2405. [InlineData (
  2406. "This\u00A0is\u00A0a\u00A0sentence.",
  2407. 1,
  2408. -18,
  2409. new []
  2410. {
  2411. "T", "h", "i", "s", "\u00A0", "i", "s", "\u00A0", "a", "\u00A0", "s", "e", "n", "t", "e", "n", "c", "e", "."
  2412. }
  2413. )]
  2414. public void WordWrap_Unicode_LineWithNonBreakingSpace (
  2415. string text,
  2416. int maxWidth,
  2417. int widthOffset,
  2418. IEnumerable<string> resultLines
  2419. )
  2420. {
  2421. List<string> wrappedLines;
  2422. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  2423. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  2424. wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
  2425. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2426. Assert.True (
  2427. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  2428. );
  2429. Assert.True (
  2430. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  2431. );
  2432. Assert.Equal (resultLines, wrappedLines);
  2433. }
  2434. [Theory]
  2435. [InlineData (
  2436. "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ",
  2437. 51,
  2438. 0,
  2439. new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" }
  2440. )]
  2441. [InlineData (
  2442. "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ",
  2443. 50,
  2444. -1,
  2445. new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" }
  2446. )]
  2447. [InlineData (
  2448. "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ",
  2449. 46,
  2450. -5,
  2451. new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" }
  2452. )]
  2453. [InlineData (
  2454. "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ",
  2455. 26,
  2456. -25,
  2457. new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" }
  2458. )]
  2459. [InlineData (
  2460. "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ",
  2461. 17,
  2462. -34,
  2463. new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" }
  2464. )]
  2465. [InlineData (
  2466. "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ",
  2467. 13,
  2468. -38,
  2469. new [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" }
  2470. )]
  2471. [InlineData (
  2472. "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ",
  2473. 1,
  2474. -50,
  2475. new []
  2476. {
  2477. "ก",
  2478. "ข",
  2479. "ฃ",
  2480. "ค",
  2481. "ฅ",
  2482. "ฆ",
  2483. "ง",
  2484. "จ",
  2485. "ฉ",
  2486. "ช",
  2487. "ซ",
  2488. "ฌ",
  2489. "ญ",
  2490. "ฎ",
  2491. "ฏ",
  2492. "ฐ",
  2493. "ฑ",
  2494. "ฒ",
  2495. "ณ",
  2496. "ด",
  2497. "ต",
  2498. "ถ",
  2499. "ท",
  2500. "ธ",
  2501. "น",
  2502. "บ",
  2503. "ป",
  2504. "ผ",
  2505. "ฝ",
  2506. "พ",
  2507. "ฟ",
  2508. "ภ",
  2509. "ม",
  2510. "ย",
  2511. "ร",
  2512. "ฤ",
  2513. "ล",
  2514. "ฦ",
  2515. "ว",
  2516. "ศ",
  2517. "ษ",
  2518. "ส",
  2519. "ห",
  2520. "ฬ",
  2521. "อ",
  2522. "ฮ",
  2523. "ฯ",
  2524. "ะั",
  2525. "า",
  2526. "ำ"
  2527. }
  2528. )]
  2529. public void WordWrap_Unicode_SingleWordLine (
  2530. string text,
  2531. int maxWidth,
  2532. int widthOffset,
  2533. IEnumerable<string> resultLines
  2534. )
  2535. {
  2536. List<string> wrappedLines;
  2537. IEnumerable<Rune> zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0);
  2538. Assert.Single (zeroWidth);
  2539. Assert.Equal ('ั', zeroWidth.ElementAt (0).Value);
  2540. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  2541. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  2542. wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
  2543. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2544. Assert.True (
  2545. expectedClippedWidth
  2546. >= (wrappedLines.Count > 0
  2547. ? wrappedLines.Max (
  2548. l => l.GetRuneCount ()
  2549. + zeroWidth.Count ()
  2550. - 1
  2551. + widthOffset
  2552. )
  2553. : 0)
  2554. );
  2555. Assert.True (
  2556. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  2557. );
  2558. Assert.Equal (resultLines, wrappedLines);
  2559. }
  2560. /// <summary>WordWrap strips CRLF</summary>
  2561. [Theory]
  2562. [InlineData (
  2563. "A sentence has words.\nA paragraph has lines.",
  2564. 44,
  2565. 0,
  2566. new [] { "A sentence has words.A paragraph has lines." }
  2567. )]
  2568. [InlineData (
  2569. "A sentence has words.\nA paragraph has lines.",
  2570. 43,
  2571. -1,
  2572. new [] { "A sentence has words.A paragraph has lines." }
  2573. )]
  2574. [InlineData (
  2575. "A sentence has words.\nA paragraph has lines.",
  2576. 38,
  2577. -6,
  2578. new [] { "A sentence has words.A paragraph has", "lines." }
  2579. )]
  2580. [InlineData (
  2581. "A sentence has words.\nA paragraph has lines.",
  2582. 34,
  2583. -10,
  2584. new [] { "A sentence has words.A paragraph", "has lines." }
  2585. )]
  2586. [InlineData (
  2587. "A sentence has words.\nA paragraph has lines.",
  2588. 27,
  2589. -17,
  2590. new [] { "A sentence has words.A", "paragraph has lines." }
  2591. )]
  2592. // Unicode
  2593. [InlineData (
  2594. "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.",
  2595. 69,
  2596. 0,
  2597. new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." }
  2598. )]
  2599. [InlineData (
  2600. "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.",
  2601. 68,
  2602. -1,
  2603. new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." }
  2604. )]
  2605. [InlineData (
  2606. "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.",
  2607. 63,
  2608. -6,
  2609. new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has", "Линии." }
  2610. )]
  2611. [InlineData (
  2612. "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.",
  2613. 59,
  2614. -10,
  2615. new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт", "has Линии." }
  2616. )]
  2617. [InlineData (
  2618. "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.",
  2619. 52,
  2620. -17,
  2621. new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode", "Пункт has Линии." }
  2622. )]
  2623. public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
  2624. {
  2625. List<string> wrappedLines;
  2626. Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
  2627. int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
  2628. wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
  2629. Assert.Equal (wrappedLines.Count, resultLines.Count ());
  2630. Assert.True (
  2631. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)
  2632. );
  2633. Assert.True (
  2634. expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
  2635. );
  2636. Assert.Equal (resultLines, wrappedLines);
  2637. }
  2638. [Theory]
  2639. [InlineData ("No crlf", "No crlf")]
  2640. // CRLF
  2641. [InlineData ("\r\nThis has crlf in the beginning", "This has crlf in the beginning")]
  2642. [InlineData ("This has crlf\r\nin the middle", "This has crlfin the middle")]
  2643. [InlineData ("This has crlf in the end\r\n", "This has crlf in the end")]
  2644. // LFCR
  2645. [InlineData ("\n\rThis has lfcr in the beginning", "This has lfcr in the beginning")]
  2646. [InlineData ("This has lfcr\n\rin the middle", "This has lfcrin the middle")]
  2647. [InlineData ("This has lfcr in the end\n\r", "This has lfcr in the end")]
  2648. // CR
  2649. [InlineData ("\rThis has cr in the beginning", "This has cr in the beginning")]
  2650. [InlineData ("This has cr\rin the middle", "This has crin the middle")]
  2651. [InlineData ("This has cr in the end\r", "This has cr in the end")]
  2652. // LF
  2653. [InlineData ("\nThis has lf in the beginning", "This has lf in the beginning")]
  2654. [InlineData ("This has lf\nin the middle", "This has lfin the middle")]
  2655. [InlineData ("This has lf in the end\n", "This has lf in the end")]
  2656. public void StripCRLF_RemovesCrLf (string input, string expected)
  2657. {
  2658. string actual = TextFormatter.StripCRLF(input, keepNewLine: false);
  2659. Assert.Equal (expected, actual);
  2660. }
  2661. [Theory]
  2662. [InlineData ("No crlf", "No crlf")]
  2663. // CRLF
  2664. [InlineData ("\r\nThis has crlf in the beginning", "\nThis has crlf in the beginning")]
  2665. [InlineData ("This has crlf\r\nin the middle", "This has crlf\nin the middle")]
  2666. [InlineData ("This has crlf in the end\r\n", "This has crlf in the end\n")]
  2667. // LFCR
  2668. [InlineData ("\n\rThis has lfcr in the beginning", "\n\rThis has lfcr in the beginning")]
  2669. [InlineData ("This has lfcr\n\rin the middle", "This has lfcr\n\rin the middle")]
  2670. [InlineData ("This has lfcr in the end\n\r", "This has lfcr in the end\n\r")]
  2671. // CR
  2672. [InlineData ("\rThis has cr in the beginning", "\rThis has cr in the beginning")]
  2673. [InlineData ("This has cr\rin the middle", "This has cr\rin the middle")]
  2674. [InlineData ("This has cr in the end\r", "This has cr in the end\r")]
  2675. // LF
  2676. [InlineData ("\nThis has lf in the beginning", "\nThis has lf in the beginning")]
  2677. [InlineData ("This has lf\nin the middle", "This has lf\nin the middle")]
  2678. [InlineData ("This has lf in the end\n", "This has lf in the end\n")]
  2679. public void StripCRLF_KeepNewLine_RemovesCarriageReturnFromCrLf (string input, string expected)
  2680. {
  2681. string actual = TextFormatter.StripCRLF(input, keepNewLine: true);
  2682. Assert.Equal (expected, actual);
  2683. }
  2684. [Theory]
  2685. [InlineData ("No crlf", "No crlf")]
  2686. // CRLF
  2687. [InlineData ("\r\nThis has crlf in the beginning", " This has crlf in the beginning")]
  2688. [InlineData ("This has crlf\r\nin the middle", "This has crlf in the middle")]
  2689. [InlineData ("This has crlf in the end\r\n", "This has crlf in the end ")]
  2690. // LFCR
  2691. [InlineData ("\n\rThis has lfcr in the beginning", " This has lfcr in the beginning")]
  2692. [InlineData ("This has lfcr\n\rin the middle", "This has lfcr in the middle")]
  2693. [InlineData ("This has lfcr in the end\n\r", "This has lfcr in the end ")]
  2694. // CR
  2695. [InlineData ("\rThis has cr in the beginning", " This has cr in the beginning")]
  2696. [InlineData ("This has cr\rin the middle", "This has cr in the middle")]
  2697. [InlineData ("This has cr in the end\r", "This has cr in the end ")]
  2698. // LF
  2699. [InlineData ("\nThis has lf in the beginning", " This has lf in the beginning")]
  2700. [InlineData ("This has lf\nin the middle", "This has lf in the middle")]
  2701. [InlineData ("This has lf in the end\n", "This has lf in the end ")]
  2702. public void ReplaceCRLFWithSpace_ReplacesCrLfWithSpace (string input, string expected)
  2703. {
  2704. string actual = TextFormatter.ReplaceCRLFWithSpace(input);
  2705. Assert.Equal (expected, actual);
  2706. }
  2707. // ============================================================
  2708. // MIGRATED TESTS FROM UnitTests/Text/TextFormatterTests.cs
  2709. // These tests now use CreateFakeDriver() from ParallelizableBase
  2710. // instead of relying on Application.Driver via [SetupFakeDriver]
  2711. // ============================================================
  2712. [Theory]
  2713. [InlineData ("A", 0, "")]
  2714. [InlineData ("A", 1, "A")]
  2715. [InlineData ("A", 2, "A")]
  2716. [InlineData ("A", 3, " A")]
  2717. [InlineData ("AB", 1, "A")]
  2718. [InlineData ("AB", 2, "AB")]
  2719. [InlineData ("ABC", 3, "ABC")]
  2720. [InlineData ("ABC", 4, "ABC")]
  2721. [InlineData ("ABC", 5, " ABC")]
  2722. [InlineData ("ABC", 6, " ABC")]
  2723. [InlineData ("ABC", 9, " ABC")]
  2724. public void Draw_Horizontal_Centered (string text, int width, string expectedText)
  2725. {
  2726. var driver = CreateFakeDriver (width > 0 ? width : 1, 1);
  2727. TextFormatter tf = new ()
  2728. {
  2729. Text = text,
  2730. Alignment = Alignment.Center,
  2731. ConstrainToWidth = width,
  2732. ConstrainToHeight = 1
  2733. };
  2734. tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver);
  2735. DriverAssert.AssertDriverContentsWithFrameAre (expectedText, _output, driver);
  2736. }
  2737. [Theory]
  2738. [InlineData ("A", 0, "")]
  2739. [InlineData ("A", 1, "A")]
  2740. [InlineData ("A", 2, "A")]
  2741. [InlineData ("A B", 3, "A B")]
  2742. [InlineData ("A B", 1, "A")]
  2743. [InlineData ("A B", 2, "A")]
  2744. [InlineData ("A B", 4, "A B")]
  2745. [InlineData ("A B", 5, "A B")]
  2746. [InlineData ("A B", 6, "A B")]
  2747. [InlineData ("A B", 10, "A B")]
  2748. [InlineData ("ABC ABC", 10, "ABC ABC")]
  2749. public void Draw_Horizontal_Justified (string text, int width, string expectedText)
  2750. {
  2751. var driver = CreateFakeDriver (width > 0 ? width : 1, 1);
  2752. TextFormatter tf = new ()
  2753. {
  2754. Text = text,
  2755. Alignment = Alignment.Fill,
  2756. ConstrainToWidth = width,
  2757. ConstrainToHeight = 1
  2758. };
  2759. tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver);
  2760. DriverAssert.AssertDriverContentsWithFrameAre (expectedText, _output, driver);
  2761. }
  2762. [Theory]
  2763. [InlineData ("A", 0, "")]
  2764. [InlineData ("A", 1, "A")]
  2765. [InlineData ("A", 2, "A")]
  2766. [InlineData ("AB", 1, "A")]
  2767. [InlineData ("AB", 2, "AB")]
  2768. [InlineData ("ABC", 3, "ABC")]
  2769. [InlineData ("ABC", 4, "ABC")]
  2770. [InlineData ("ABC", 6, "ABC")]
  2771. public void Draw_Horizontal_Left (string text, int width, string expectedText)
  2772. {
  2773. var driver = CreateFakeDriver (width > 0 ? width : 1, 1);
  2774. TextFormatter tf = new ()
  2775. {
  2776. Text = text,
  2777. Alignment = Alignment.Start,
  2778. ConstrainToWidth = width,
  2779. ConstrainToHeight = 1
  2780. };
  2781. tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver);
  2782. DriverAssert.AssertDriverContentsWithFrameAre (expectedText, _output, driver);
  2783. }
  2784. [Theory]
  2785. [InlineData ("A", 0, "")]
  2786. [InlineData ("A", 1, "A")]
  2787. [InlineData ("A", 2, " A")]
  2788. [InlineData ("AB", 1, "B")]
  2789. [InlineData ("AB", 2, "AB")]
  2790. [InlineData ("ABC", 3, "ABC")]
  2791. [InlineData ("ABC", 4, " ABC")]
  2792. [InlineData ("ABC", 6, " ABC")]
  2793. public void Draw_Horizontal_Right (string text, int width, string expectedText)
  2794. {
  2795. var driver = CreateFakeDriver (width > 0 ? width : 1, 1);
  2796. TextFormatter tf = new ()
  2797. {
  2798. Text = text,
  2799. Alignment = Alignment.End,
  2800. ConstrainToWidth = width,
  2801. ConstrainToHeight = 1
  2802. };
  2803. tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver);
  2804. DriverAssert.AssertDriverContentsWithFrameAre (expectedText, _output, driver);
  2805. }
  2806. }