From 3dceae0674f7f0f354a3699199b794308f651873 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Wed, 22 Mar 2023 14:03:59 +0100 Subject: [PATCH 1/3] docs: update hedgehog image --- other/hedgehog.png | Bin 10992 -> 14145 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/other/hedgehog.png b/other/hedgehog.png index ce0a940c193a8757a41f37821ab14b83ffad44ee..3d84640a4711c8a40cd96a5db1e57ec05b74e5f2 100644 GIT binary patch literal 14145 zcmcIr<9B3FwC!+WYm$lCu{rU?M#s*?wv(CIwr$&(*vZ5b+qPf--k0|mybrhUUA=m( zuBvltpS|~~8>T2PfsBBU0001xr6hkSgYUooyWn8KpZx-c-y`-iS06-Y>-vtq~ ze{BN(@~g9$hO>&Dsk57*qY1#x&5hB**2>A)(B6d6&e1I6;yXS7Kmw5ZA*|}2d7kCw zPB^&mDcI0b`pCEvPx+{~$LMCQQ|r+G3xZ%3Ed=L}3<BDH)6R?>WS09^|VZ z#<@KU$WLwC9VtZKef}R%PmOp6>*EpaTA+J6}E1o;aW@3 z5~MFinv#M(X2%w~T+*c!X-;KHmEXGnal&Zre12F$Z&1#`sMgTu5{3cj>`efr=aajq z2b54pTW-cbX$>+4$+Z`Z$=|b2(r>DASkzi-g?OebHa&vmnpHT$el$Q^ReQayBE6YXO^KPEW!`Yz|WhI9Ri3{@GLWo-TSy1EQQ z$PV(IPc8mK%no$`e7;$1@Tq}T3lJQx zOFXHsKH;Ix!U+r18RRcHi=`UwYt-Z(o*s=d99!<5pMA@V<<;_Q{mr+1SPw^Z67p4#lYR7+RBM$Ca5!3{z4!Azd^jBDEu1=e2mx<@ZgU&EE++8{N# zskr6D4m)42CH{Ha662k(OmySDi`V*$ zZFYU?)0;V_N`w7wH9BP*k#6^X%J7YgBQ2A<&VG=r5S8gVNKf9^udg9j2TLJxu>10P zqwPT938-eR*>;3Uy+AXUsBQE@oD&07xNK3!HYt}~;c%)(bo~0UIC?WEl;Xl4grtSu z=$iVS^bi{8e8WB#QHC6}EdleYW!-lLdIKC=pV0ps{Z2mDqOV&CC70(l7B|8cO5M<` zg1)ADg~gf$5V8N>dky90JUQ_!9WM^2Hy+JY8AO~KKav4S+<4A+5cKzgZ*M+VkB>Ma zG(QUu6?Ju0a`tRGzS(jDprN10u=@7j$b>#bMU2-6ZuZ%3*Jvx~SA|euVn;+#-(5W9 zeS(>C(hg1gJVWHosd3)!1%ZM)E<9Vf(*WZ4i|#O!{dBG?VgPW@xI8#^#G;q+w@K>1 znFds!mscRbn;JOB4ChpZ^#!cp+FFueq|1n!8eG$cpk0Y+6&0)cuzR)K zrPHq*rX<3`zTPdSUOswX4r2jK4UKQ22*H0za9kk=hK6X*$Cy6GC>UzU!?f=nKO=SQ z`I*X_T-SfG+I}@M9zJSfy|)R}(Na+1C|=Ra?tHvmhLMpiYRY3ra>E7fy9BDbglC7M zYHi|?+GSHC^-}0gHwv*WVn4Qj6`SIBs z{|i`7)lp8H0Qh)bZ4)l13OH$eh&|aU*Vit(vsWA{%&f6Q`i8a+Wk(z%E}ec>Y>9!4 zCuoQ_JIfyyqhOOMzv-)cyxGGCCoC@yVYPIYUs*Xus$d@~*l+y8X1HVT{oZNwhHI5uYm4tytiTa+Ff1izToon}9SAg~$PorgIIt}9^()vo zWRiyLd|&0+R^G?VrT$gSqVf;+Pv|324tAb}8rS4?vS6MCF(+si3Dze>z^B9hX-2b= z%E)UuR>1jeIa)Mi%7AsH#OuzJ5Ik5wt1~czK^rAki@@^s%HH_xi8QbYv9P^8c`^ef zHb+0QqJjLS4{$yBG%M94@Z9+0AXla@{)|2S;%*(w~=Ds=>yWYsT$~ zIlG$5u&5}Icf`&GnzB61i!|w`&&@hWUw@O}J%|O6{#HPH{4l*ah3t89Zg&d;7MyNG z_$%rO%?XHc-!(H`X{Z=}CBg~Ss}U_xS2PjLk7@bx{>2#z7^WcUB$=QVmAFTb5FyR& z@j_OQz6;(0%uYiZIy#x9<>iL^<>!e>Ajp{_D`?LEqJ&Xdoi$3YG!gVj0=7`SE?&sk z)Wp6;h7iRjDtda4+^|N4k6}!=DD}&7qeR0*Ydn+j}BHmVN z(})Fue{!a509#u|*Yt=6%xXq?N%(7AwzE5cpIxymzPNoYF?JG%_h-L=qa)(Ei8ej$ z{{F&StuL`A56tArM)xW7s#}(QZdtjwtuOkqMcX%S?# zj?a3V-qu#`RSQrPRe8CUR9m8)RGH03bR^Pt^;ctNyt~9i)!MVwDpfV!MW?MR50q~L ze!A0|x>g6{HJn@pUtpdc4-@eqAv@36BL>44%KvuzkidwUB&P7m${oY4hmc$rUho7a zPBT6`uXCJ23DUtT&VG=@t^T?-uQ%)OMI$yBc~$0(zUbW|+3YPHQQX;?`_}lZ$4_>Q zhW_V8?#uOq5Su=*G#s!pCt=Kd1kQw= z)BV--?l%I2Jh+QAeiS!1TmVAE7`MX>nb~v+w+6W!8a!)#+&tgQj@ju)^B(U>*FF-7 zFkGykrgMozRH7S6SmM=(3@vYfl7xLM>IzR#7D(uhA#Nm4WZ*w}LOeMJC z;MJ#8*~q!RZF5~$Hx`b)EHAyvyVIG!yqw4U#nPD4#+^7)910tI!TWj=M_QWB99~2S zoEZ=w8{B+f%c`p>8OUSC#?o~Vos-BXZ_@c>T021OgHU0eJMa1aWM2>n+PCGu&K~9LLXV2LdH>^BTU-R^z z;}5%7*eMXI@O_bjlQ|FS(1y!cZ`@+#sDr+3EOkvcwm>N8`JdB@K{r-gJ{2HW>$~a= z@sW}lgWCt=MnuG|53k{dmkyuIF8k5!(eXLtU*Y9is&YomM@JijRM^tv6-pdZ!iGFJ z5#fEg{VStk7*&7skQt}s?~bv>!0{QR#6A;rMgba*NAIdzd({xd$@Y*BeRR~<2j5;> zP*9l0W@+yoO+fI91+jYkG4O}u+}rQ}Gt zsOWIw3T62{{96+E~#?W zJ(Swlg}R|QCO5Y<@{E_Ea|%&XObj_aF)#?mfcXfFRu6G@&00K89~e+CuKAty2vxR{ zwBZJOR~wN8E5rf9!*vO`Jl^yl>3oH`wTT0J^x@L9t+BD>r@%@yG*wQB$ja8-TNTRFl>@`!(aoNG@ zt5Cu$KqR3?X3vb9et7KT{?p_T3&V{iAbXuGmCYFI^UaPYsGy8NW2#rY*UO3oHl|(+3#lB)&(6Ih!DQnG`xfD>*8`%E2IkMwZ#1GLA(3M^0s>DA<=_8&KH#y z8KZ>@mZ1Z zLQ_NRs9KjpprXFC&f!2B7QRW}sClkg3rcDE&_7}n$Gts}C@dn( zjr#N&-#!YvL+`ue!o~7>M^%+cH_*Y9fs~U|t&CyfAMRYq+)Kb=!tF4&RDbVnaCNq!8A(q=NkusWd>?M~7oWI*obK4Sg0WNvN9Kp*XlVWFf znRlD0&Tgk|l|sb1G;lY_O6`qsqy_|F0VB7v~D!y6eB5{{45DOI|zEO z!w9`p0%mdqQ(nZM0HGQpLdo$Fy9Fdf95XQMf0knl4Eo?;I5%w_~NQFeGs3ISC^kV3M}zL6s*sjvXJrW)6k z-yP3F;Np@?$S)_Z>Q82bDsB}NO&;#1_N|AVp#52@a4q1_HL-%}hjlf!cg95_l-XJ? z8&Jcp5`O1nF&<<7e)5eAOd-zroPQ^nsnF2Sf(3w}#TuYk7EjVb6Pwt#J8tbQevz#O zAch`EydDEz@Vc#fc*v|>nJM5F)*6vUQFNfJq-28~Z=y_d+1=r(;v`?wFK6~^d0B%J zoQhEH-izl{_(oed=zC}b87ob)# z_CfAoz2Xuc4qN7?Yc(KmfE;CQT#ME-dGZ&1x zKswM>K8B^EtQM5GTvLvZd5Fo1H^YaZj!O-ui_4v-ScH7gY%V(n81W4>jAcKh;ZSZ& zWM!EqaOk{HbZb*sehT3hD-iuDb1HM?h2xb7VzpM}3PwH?{}fRA(TF-=U8k(7zR{%D zP=Yc%Jsj{fKrirS#3rFYY1mx);EV+QyZE!XeJC=K)53v;@=L;s zWslkH+O=#qLwYTmpm!Jl)$M(uZYc4)rx*r!HOzMZmX&joQckoR<1tQ&qvS~L6x1}^ zzd0i`Q6Vb(Q|(Ixd0wu_EWO40FuXg>RRQD@*c|20+#vPlV>7~KXpNiCdBziZ+5^BT z0N&}W`c?l#tI7=T_H-HdYc6YyXV)B5mr`fhN1MCMzRr}8M_dppoN14v zN5~Zb+JF7abe-`g4?y7Al3;2I_J)xwh2FXmH>?u4)k!mo*B{`9}A7}U$UxR zTrO?@scx{V7_Z#{x3(=>vH-Af1KPY<_dXTnGJs5f$WG0h)s@zE?zm)7@1}spVK4L~ zJ}q=jkG-I;Kz8MZed;8i!rwAc=IM7+9A#-DzYlUSz{QDrGJjsMq)w`R9Qq0NTQ@rn zKbf1#5%<&N9$crdbaWq9EGUN#h&}VuP5dK?*bY&_!Y9SC;zr2Uafh@U%kStWxJ%N5qt7CS=+0r4d5NkeWNcxq_qlpf)fzVzF3F?3162h>O z=B?)9$*B|499IG2D92O5=hP!~^rF&XXpmg$xL}@ew7zb-nbQ_)dbwuvIu)O#FCwh; zHEl&~M2)?%G?XOU<4!&^D^e1;(t1l_#YJQ>+rNZ?5Kg=7^#n;3A-y(;w3ARzc=j=L zb>rN>xfF0>iRl4Dxzuq1wVbI+`g;Q`43v~KMNM5H)FjR~3EbtzAJUucZ$^%D?EWp$ z8*CpaK$uepVKtoiE{fg#%YLxd;idEA9^s!e4*j~8RHJrjxVkYnk60dLonRltg{=l3ky`yIr6su z8cj9ar!~OyPz7Z?JyqCbhRHZdq9R^?&h(QS2$k>S% zr>$ULan@wwhoJ~wGkrUo>7CgovAxDh`!j|Rb_G@+T~(3Yng@ayb!a%;P`)L&{4ZHO zCZuT(9Amd8UN z$?99H>mGJuT$cJDzeU2_S|y#FiU`cd$!~q3ee39u;9qGpeK3b8Q041oxJcRm;!>Ks!{dYVZqViwSu3OTp{x$?r&ja5bOv9#aLv;7(Cs58mI2}dpFR8U znx56oWoz?Qa)L#`5abx9L0y@n}4GzbXW8ny>JKL|r4sErWm}X24H#)SSg{W1s zX#}9aUwBUMIuAFkU9aKDuLYay0}S4C6umn~=AubCuYy5at-((az+s6-M1yU9lV&9WpaXRf%0J{Fxxx!nb6SHuWMlV=c)^k{=8@*?Zs_V$JB#Xbf~ zmZUWmEB~5KDz71+Amn~veF6U0O*npZro7QUYDj5yTm89!-;}vOR3{i*PohGeeNW+E z=g9&M{*($t68+1XA<@6{45|;e#87cuapK+(2*N}1)X|M9G{e3+cl*Na$1;}}od?Bx z`{W-6M@mCmy~jjcO-^0YI`~jnczF9}wW(J&x-JyiY`KFKdvGP7t%2EEhGB3K$bw3~ zHw@6t7ty#Gkc|{4n3e)cjni*c`PE1y;Lmmygu>lQl&F-lq)Cm_Y(1@D$(qsyyt@26 zv6=O5_ohBx335Kg8xwUJM#nijRn#{DFm)m0=x4?_%EB3gXE9=z;9qRUp z5d~dKD%Zwm^v>VK^^qkA)DU~Wwp)eCo>C_4FnEPiVQbFiu{yItBFhapYwv(t>bNJ z2MZW6N)VW$sVM82AWA$wlJ(S9hK0HY*0d1#z0Q5#iFbUmKUSkXy+I&7`i3pr5}gPA z>}3oB#jD(Md-AUIMc&!F8EQD2@k#`5ogW7e8j)Tr6Y-&B*5nwW%+Hr5^a$GL4%Qau z^kPTGyBnNQm(SyHa`ZU!1~Dp;g6pt@69)!5j?i!@Nj@($I{r}+_N0nNiauJPa`fB{m(yML#FUvb@Y%R38#yR1zNKVBcs{zeP( zbF5bHo9*K?7#;#2h{A@~%=Z+!YyhZJbyX7JFb51{tkm9OrLb!0wIwRAHFz#Ml zE$;mW6jBu0U+egbt`uuw!kb~@F|WCz1oqT?+0d&_Oy(K7BDmv?l!u)#MZ}31D?=>H zY>%~6c)W3@r0C7K$m&mJbHT%9+UMp6 zu!3Vcgmh{fqXAi~0qTCf0Gzh)G0yRUJ)m-yICo$OICUMV7`w!VLBb9^VJVJa`q8)8 zb$DV7MF;)8jAkbkdomDY_oL1gM;QrQgb`NlFNNh(u?)b&FbF;1!fC6UZeK=f`fw7} z&tCDDch`CvpAWZj@QUj`jxOWQ(MCT*x?gy{{p&;+O}I_VYJ}fADz1v7gOk(2LtNKa zqV==~!w**n0&tp?jJw4pt=2bJ(Le_1*_nypnvp+Fh8nJ2WxI;ufUS+p)2_No-Optidx^xLnukRLSg?~vU}eBy@%XfX%)-% ziK%wZ5)grq-2bWHc<%ba5^hyqa@6#48+s?czNXZM3kCSa{UhwF^CcF-eAwEql=YfN zc;a0!2PA1=zd7Mr3;9Pkkp?}jSIkg@;c!`wPz#!{$nLA=uDG05gVW`P=gC+SuGU_2 z#t^`9iIG2PkPcn_!W%%$XDb1It@??{;KoxgtniaQq})Dv8!T~U7X-i%4F z?^e1XR**~*xTP&P^#uUH`Sagd04A9W@_HYDvUGCwzwuzgsutX>6^UZjR(7&*r z3&G@-tDb9mpQWVhPm_NRc8|8MDFn3Z#;a+#?63&b&X>9FrEOY)5TuK(3%7w=`)acH z#6jb2&se&)y58ch(F05~I9z@&BKTf06yTjRSQI%vd7!0$io4JK3k7Qb_LKV_dN198iq9Q^>v`mE>B4Y}7%Dp}4KRjrHb1>FDGxXlYY_ zc$Ch0=Lx;*`5vl8*TvF2d{yGnP%$UkTsb~Yw>PYzOs6yPlmw`g8TB?@Lp9f9Mqz@z z@crYDnd@*=Mg`_NUl4jq;zBv1mJ)Op@A5ZQFO=aX%f;W5mEUG6phS9yvPw8e2j-q& zyGpPRP<|-kzyl9SpAO3jU$xemJVhygCDw&c+-ssbs|5nY)|gS~CYY?1zSz6CnT#Ce z;bqy(3Li3=)_a3lEjylb=j|cP0MePXyk4145EFk#Y+#~wa@ET42Kn`5<6|<|uXNu} z^MZrFp`w_{u+92|hdY-5(M8^tsA<5|Z`lM4&Awo~14DIvIDWkHEuhtS4M3D7_}hVn ze{_t2tR*Ov447A17(a*mm&Qh}aVf97JsF(3@pCtQoT=1G+DQ$bhutpMWyhypt`}Fx z_@cYYY|Tw-L&zq-4owAb+{#9IM3iV~YYrwATyMsDns1L!$TQ_f@+3+~I{9^cRaeT( zm1DhDh}KW{&VHz-(7YgD*j>JrTVmF9cPt9D2cpRm&82{~Yb4QP)|)>o*e8lpj@MUQ zq-QPB4P~13$v;{ufQsAxtpSSq+df&CPSs{VizBS6sT)xIJ0bzyPajnr^;mwV=o{nG zGb9+BH`II!#l%R%f@_jqWLZL%J@Bs_ zXsRQ}&NY-s8LO{zg_Aes5@Iao-?%2ih%Y1akwj5~qt_RaqU9tdp0_TiFqK*Us7ekxDqNK_d??g9& z^*kc^ z+Cz!O1SUGYW3M$b;Tw-F$T;b}=;dE3i$I>@K@=%+m^{yq2Cd%P%5Bu_%7&W5yiYOTdT z{i0o(j~n^KD(4*Jl>9j$&#Qo*Ikk1fH&pFbPUpb>cGK7xk`1 zxeDytDm5O^Tq67c0Wo2_o!(r60y|+~+|u%nZkr2XvvP!jk_Lv! zj2)ady`C?^C^7_Qb~z4z98fFh!Z5H3^=z=H1n!Xr;sJ}0>^V>NX0i>96l0jdim#QM zz`=!c=d5tJ?ZU1Li}~n2F>&c?E8;V+eaIn@CVZ=4x#MV)|ITyuxga)9Wgz48<+nfC zYUuIIRE}0cP<(y4DVWH7Wtqri4_zBC>byOsiYIu6x#J}y72be;^1wJcl#1=a%_0|t zL%G}W)6+7IIrsXwK1Mns3@f+z#*_9ZIEz}UbKbFawTTQ|%Wp67#Z=v6eaMlP@wRWV zy_OrtyI;rY3pH9Dmp1IGOWsV8W_r{o$T0-W({YY=$DlC1R(LlDLnCCMpt!#xbMqZD z<00W8a{^;Cl}7Zz?4^i4V+jA~bjk@e%5c7SgA@@cpvLsD{daA9zSI45&d z=2K@*)?1<(b!&QhEvg~mce)naf1oX_(*g~K-8G#|$?4`}OmxE!=X1O&*d`NUjPedokv)7E-o|ww=S^XP-yIq7Vsi=y{pXT6D>x;Dj?yIQ`|a4$q`z#2 z_@xfr(BR$PtOw><&!j1x#B^b;WxruPfhIn0mdG^YxxJH1aciT0Xnr z^Ex;W<*}uTEdGx!i#aNl2rdp`HaZgS+V>p5;Sf8Mo0<3nyjTg%kqbgN*~~GPbGXnf zkVa&gaqz*@%WQ=e9=P&MafUqsYbv+5 z>VEvksz){v3xuc35G@|=V4TEn?{@gEZtN)qcuJ0mynBlkn}4Wd1zlvVZfmw=>6ECz zq28Lkjcg<)f>vC@1|Or0x7UsFaMsftSh`ZbH#7bA)vFRi(=4~LOo%l?6zL|joF7&{ z=W{3F;dxH)VVZ}83Va-Tu)qSW=|;-pV?{Rz$Dl!qp&wzxWXUh%3C*(v8r{Cqg#$8h z@nT(uE0#YT5qW#AP1ujFt{ugsrG~EnU>3~nSJwx`!vD$V1I7>)=P)EgFirDa_Wff! z90L2wT55*{Hb0JLDI@!-69}7qUCfC`LTVU{Mt)5dnqag4TZE-F0aw9%SGdgA zlykx~aA8oYDbX%beDeWYl@Hr9VX)NER2*QResbhC9j1`Ex)T>8z)k41^z9&@(GV zzioU)(BVR+k2bX6XZ~X2?@jh@5Q4e%kR>RM!vT=AK!u@lDaO1oUoB6y0U*r#w_Ox3@4}|8@2G2J})C33Lo6js)Tbg~-vkoRF-!Nt;|mkpvE{}XyON}MrZTO>FNLnl*bt2*$lyV zR~*rNzTKbfvB@BNIlfp194i?wBwBQ%0+iEm#Im#{w?!m%gI{)>7adO{Yok^=k0eg^(`FLQ0Cx`O)yuNgSr~6npiq>W~-J;xEo9Kj1PhiLBr4UKJJzO8M{$4-WIKH}dE%BR1iNu>v8kq02`iFiu@WvvGzdoKGr~AFDaQ@CQPBnYA z)oMDOc<%Y$#|h;9_;^^Hr7-$gUD&AxtYB3!t}_Bra!}DNP|?Z55@z-Lq%pIH8wk2( zW;)~Dc}BYAn9QD9N9NOzocRAJoUd+r(GEV*LL_F`ln@D@EPQxyHhFmx@NRp6Q~oD@ zmgw*f_RWgQyl`rz#wZmgNCbE?Km^g;g<@d!H8jwN^mPV3E8n25wDV!ZZSnz`b1=4> zP{V!cis{XmNY|Y_`#Rmr;C5AJNRP4Rt+ueTxIu_!YKjxLHlhInml|qn$}{t1txsN@ zP&@!7V8iwJj?*F!?stBHE$@xbNXbpLW@tQ?&SVd*L%}XeDB$a@l!|5t*_c zgnn^xGEthhzOw8+EdYS%&X&brO}Qrbgu2@M^fipRw!$|-YG#AQnZm2*QvgVG;@#lZqis&fpLmoMr{{{b(}kY`BjY;J`U!b+-8jmpd^!P}SS5nT%v#66|} zcz~rGPIRVRiEb?};p!KAMwSqh(`Y5o`j%Z9e;rfC!+_HI-tt%7EVA`@KY;ZI2Doi` zFi9lkVZxzb!w(P|et?L?pO0$@FX==K zcp!19CwSvaz&|i;Naw7G}+8HP{M6X<6(bbz#I5t z|0IhqG4)eXnG4yyo$vY&dCTsO^>U3yWr9DofS0JCw&Pr5Z4|+#0|FOFkPfR&v1U%< z_#T0G@IVF^vLht+6AEbtAKdZV#bxDjKdW zoJddp9v^#XL*Wfc3gA~U3Y~UYEj5;pZrelR)MdA$s0;6>u=%T2WV=XK+Ls_+(hvl( z<9xW)!xL-I-g~CXHwl*U>-!gh?3Xcz+*49TQS?Ak_yyIfhUK^HIIVrD3Pt-u?RlCbIKZk8?B zEi=Q=8o%f53z1oQMAAs`xzR2q{gy)O4kU3x&j1K&YAD{WKhuRPT;G)Eq_Ok7E9d50 zRu#Oi)1y7#?Ho>f@Tt%<^wb4omGO!aH70QAO%WM9rgbwTp}#znVi|TgySxO-Lx>3< z;V0+&0c*7?RYCKLdRkI)pnI!?oX@iwL6i5EObE`-Zu7X<$hPFpCMq_y3hXZV5Y^u} zQ)@84^Ko-dzh@@Pz+oykrl1wWG02kr`AtIdOPfTTT`s{i0W`0c!Mu=KT2?){HeyC% zc!h1-18gnoH<`4$ea|cu2x~|E-r^4p;U)ZSmOTv}|AcRB#o%oh8xd#cM~p`eiZz2~ zwML$dQv_mZ6Ac6}X#flv91sf49g^r*Nh4vy39wHzw0$zUKFC$tzp%J%!KZAzdkW&0 z)?*LVY@tSca<(pDXP2(Ab7ld9h?SG`ftpYD34Ew7SRI!iv2#WGMhxqEo^aal1nrns zePVWS$$2Bj;0QZB`RK>bm{)xp-@iM^fr+6{CNc%8c-&5}fDW0Eb6u>@HSfnhudsNKnUNaN{*X=&O;G ze)YyP+@8exL3bJ^0YL@uuRU?>f~9HMWY=%GPFnDZIXK}6uTF}aI-rbz-96F@(PK|M z1^c;eu8R_IK+Z2t#NoSJOOlR9`pR+9i++OM6OFo3qvlM0Hllx8hY>D!OR6iTz06l3 z%=%iO9NhErJgz{`$(QGLCU;}HP&4gGl4IP*{dm1k6HM*FHoIhKQui&(XXt;s{KU~N z%ac6M&rH#zX%wUbSb9 zy-w_Y&I3=}lt^XxaqTzExhe6XPv(1k+y4uEK0qoIGdMoHBqKiN)2q&KjxIuc9@xtt zn`!0S@U88-DTv=z(Wxd>`{YaN&2SNrKwsBc@}D3@-?LSlLUn3E1e+UCfjKLc?^k|k z3^XeIsiCrvl#GaHTd?FR&Qd1Z>GtjJP;|+|m?VKAcG}NcFe#_KIKJNNK*?ZR>}j_* z^3A))ggys!>@PcJ89`jVeipAU`DvjU>Aza*C|gQ$p=nJ*x@fdLXM+ z2Ex2a!b)mhXQs5NJvzRz5}|Dc%v6usCi@R|;dbuWmk$gr;lN4)KVaBbT^2B)*LsWw zGY>Djz4kfmZk||Ln^XYI_d(MwlE7>j6gAiKHNT?H4%fkxIBzKDwrE=MA)j}Vff$R7 zGVvD8KX$G41!L$U^yfU6O{Qiy2%x9%U^^;3Yzz@yCeO&O15ZB++cs!G>dQ`v9fX}O zgTA|PR0g~*#LPW!xVqv#RBy%7T=4|<=dQ9QT}6%97`sTEyqSA(5j5}zF1#JW-X*2Z z+=F=@Ch3yOb4=f~KSVWlvDUsk>7X945CYG!>Jh%rg;+}2)-jFBT7NARs#=N^Q~9I% z)TeGzR{Ix_$@irnJX?l1d_PRM1}b|@5d_lfMWLEsA<$;fu%y8GScwWgkk0iS2^VBf zWf2>R?MggX+!|~>ULXYNTHIfPtAU{zPwNlwUy#4Qm(bpzb`iPnF1sw~ z5b}P_ONEsHpthnXk(`rb8K5ukQy>2!!_Qm)-wS1}D^Gs5TNWCqXre>l*WUnAV)8$# IMGXA^2i6JKMF0Q* literal 10992 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 Date: Thu, 23 Mar 2023 19:18:04 +0100 Subject: [PATCH 2/3] docs: reproduce #373 (#375) --- .../src/app/issues/issue-373.spec.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 apps/example-app-karma/src/app/issues/issue-373.spec.ts diff --git a/apps/example-app-karma/src/app/issues/issue-373.spec.ts b/apps/example-app-karma/src/app/issues/issue-373.spec.ts new file mode 100644 index 00000000..cdbd0ccb --- /dev/null +++ b/apps/example-app-karma/src/app/issues/issue-373.spec.ts @@ -0,0 +1,69 @@ +import { Component } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/angular'; + +@Component({ + selector: 'app-login', + template: `

Login

+ +
+ +
+
Email is required
+
+ +
+
Password is required
+
+ +
`, +}) +class LoginComponent { + form: FormGroup = this.fb.group({ + email: ['', [Validators.required]], + password: ['', [Validators.required]], + }); + + constructor(private fb: FormBuilder) {} + + get email(): FormControl { + return this.form.get('email') as FormControl; + } + + get password(): FormControl { + return this.form.get('password') as FormControl; + } + + onSubmit(_fg: FormGroup): void { + // do nothing + } +} + +describe('LoginComponent', () => { + const setup = async () => { + return render(LoginComponent, { + imports: [ReactiveFormsModule], + }); + }; + + it('should create a component with inputs and a button to submit', async () => { + await setup(); + + expect(screen.getByRole('textbox', { name: 'email' })).toBeInTheDocument(); + expect(screen.getByLabelText('password')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'submit' })).toBeInTheDocument(); + }); + + it('should show a message required to password and login and a button must be disabled', async () => { + await setup(); + + await userEvent.click(screen.getByRole('textbox', { name: 'email' })); + await userEvent.click(screen.getByLabelText('password')); + await userEvent.tab(); + await userEvent.click(screen.getByRole('button', { name: 'submit' })); + + expect(screen.getAllByText(/required/i).length).toBe(2); + expect(screen.getByRole('button', { name: 'submit' })).toBeDisabled(); + }); +}); From 40de852fc08739ffa11c356055cbff96f8d51788 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Mon, 1 May 2023 19:56:51 +0200 Subject: [PATCH 3/3] feat: release v14 (#381) * feat: upgrade to @testing-library/dom 9 (#376) BREAKING CHANGE: For more info see https://github.com/testing-library/dom-testing-library/releases/tag/v9.0.0 * feat: remove change and changeInput in favor of rerender (#378) BREAKING CHANGE: Use `rerender` instead of `change`. Use `rerender` instead of `changechangeInput`. For more info see #365 --- .../src/app/issues/issue-222.spec.ts | 13 --- .../examples/16-input-getter-setter.spec.ts | 14 --- package.json | 2 +- projects/testing-library/package.json | 2 +- projects/testing-library/src/lib/models.ts | 16 --- .../src/lib/testing-library.ts | 64 ++++-------- projects/testing-library/tests/change.spec.ts | 96 ------------------ .../tests/changeInputs.spec.ts | 97 ------------------- .../testing-library/tests/rerender.spec.ts | 38 +++++++- 9 files changed, 59 insertions(+), 283 deletions(-) delete mode 100644 projects/testing-library/tests/change.spec.ts delete mode 100644 projects/testing-library/tests/changeInputs.spec.ts diff --git a/apps/example-app-karma/src/app/issues/issue-222.spec.ts b/apps/example-app-karma/src/app/issues/issue-222.spec.ts index 17e9a029..5da35d44 100644 --- a/apps/example-app-karma/src/app/issues/issue-222.spec.ts +++ b/apps/example-app-karma/src/app/issues/issue-222.spec.ts @@ -13,16 +13,3 @@ it('https://github.com/testing-library/angular-testing-library/issues/222 with r expect(screen.getByText('Hello Mark')).toBeTruthy(); }); - -it('https://github.com/testing-library/angular-testing-library/issues/222 with change', async () => { - const { change } = await render(`
Hello {{ name}}
`, { - componentProperties: { - name: 'Sarah', - }, - }); - - expect(screen.getByText('Hello Sarah')).toBeTruthy(); - await change({ name: 'Mark' }); - - expect(screen.getByText('Hello Mark')).toBeTruthy(); -}); diff --git a/apps/example-app/src/app/examples/16-input-getter-setter.spec.ts b/apps/example-app/src/app/examples/16-input-getter-setter.spec.ts index 53dee01f..4382d851 100644 --- a/apps/example-app/src/app/examples/16-input-getter-setter.spec.ts +++ b/apps/example-app/src/app/examples/16-input-getter-setter.spec.ts @@ -10,20 +10,6 @@ test('should run logic in the input setter and getter', async () => { expect(getterValueControl).toHaveTextContent('I am value from getter Angular'); }); -test('should run logic in the input setter and getter while changing', async () => { - const { change } = await render(InputGetterSetter, { componentProperties: { value: 'Angular' } }); - const valueControl = screen.getByTestId('value'); - const getterValueControl = screen.getByTestId('value-getter'); - - expect(valueControl).toHaveTextContent('I am value from setter Angular'); - expect(getterValueControl).toHaveTextContent('I am value from getter Angular'); - - await change({ value: 'React' }); - - expect(valueControl).toHaveTextContent('I am value from setter React'); - expect(getterValueControl).toHaveTextContent('I am value from getter React'); -}); - test('should run logic in the input setter and getter while re-rendering', async () => { const { rerender } = await render(InputGetterSetter, { componentProperties: { value: 'Angular' } }); diff --git a/package.json b/package.json index 1cbf74eb..ff89f736 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@ngrx/store": "15.1.0", "@nrwl/angular": "15.4.5", "@nrwl/nx-cloud": "15.0.2", - "@testing-library/dom": "^8.19.1", + "@testing-library/dom": "^9.0.0", "rxjs": "7.5.6", "tslib": "~2.3.1", "zone.js": "~0.11.4" diff --git a/projects/testing-library/package.json b/projects/testing-library/package.json index 168d767d..975050c1 100644 --- a/projects/testing-library/package.json +++ b/projects/testing-library/package.json @@ -35,7 +35,7 @@ "@angular/core": ">= 15.1.0" }, "dependencies": { - "@testing-library/dom": "^8.0.0", + "@testing-library/dom": "^9.0.0", "tslib": "^2.3.1" }, "publishConfig": { diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts index b4babe79..0a9b78ab 100644 --- a/projects/testing-library/src/lib/models.ts +++ b/projects/testing-library/src/lib/models.ts @@ -63,22 +63,6 @@ export interface RenderResult extend 'componentProperties' | 'componentInputs' | 'componentOutputs' | 'detectChangesOnRender' >, ) => Promise; - /** - * @deprecated - * Use rerender instead. For more info see {@link https://github.com/testing-library/angular-testing-library/issues/365 GitHub Issue} - * - * @description - * Keeps the current fixture intact and invokes ngOnChanges with the updated properties. - */ - change: (changedProperties: Partial) => void; - /** - * @deprecated - * Use rerender instead. For more info see {@link https://github.com/testing-library/angular-testing-library/issues/365 GitHub Issue} - * - * @description - * Keeps the current fixture intact, update the @Input properties and invoke ngOnChanges with the updated properties. - */ - changeInput: (changedInputProperties: Partial) => void; } export interface RenderComponentOptions { diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts index 2d492d6d..66cb819d 100644 --- a/projects/testing-library/src/lib/testing-library.ts +++ b/projects/testing-library/src/lib/testing-library.ts @@ -133,12 +133,7 @@ export async function render( >, ) => { const newComponentInputs = properties?.componentInputs ?? {}; - for (const inputKey of renderedInputKeys) { - if (!Object.prototype.hasOwnProperty.call(newComponentInputs, inputKey)) { - delete (fixture.componentInstance as any)[inputKey]; - } - } - setComponentInputs(fixture, newComponentInputs); + const changesInComponentInput = update(fixture, renderedInputKeys, newComponentInputs, setComponentInputs); renderedInputKeys = Object.keys(newComponentInputs); const newComponentOutputs = properties?.componentOutputs ?? {}; @@ -151,43 +146,21 @@ export async function render( renderedOutputKeys = Object.keys(newComponentOutputs); const newComponentProps = properties?.componentProperties ?? {}; - const changes = updateProps(fixture, renderedPropKeys, newComponentProps); + const changesInComponentProps = update(fixture, renderedPropKeys, newComponentProps, setComponentProperties); + renderedPropKeys = Object.keys(newComponentProps); + if (hasOnChangesHook(fixture.componentInstance)) { - fixture.componentInstance.ngOnChanges(changes); + fixture.componentInstance.ngOnChanges({ + ...changesInComponentInput, + ...changesInComponentProps, + }); } - renderedPropKeys = Object.keys(newComponentProps); if (properties?.detectChangesOnRender !== false) { fixture.componentRef.injector.get(ChangeDetectorRef).detectChanges(); } }; - const changeInput = (changedInputProperties: Partial) => { - if (Object.keys(changedInputProperties).length === 0) { - return; - } - - setComponentInputs(fixture, changedInputProperties); - - fixture.detectChanges(); - }; - - const change = (changedProperties: Partial) => { - if (Object.keys(changedProperties).length === 0) { - return; - } - - const changes = getChangesObj(fixture.componentInstance as Record, changedProperties); - - setComponentProperties(fixture, changedProperties); - - if (hasOnChangesHook(fixture.componentInstance)) { - fixture.componentInstance.ngOnChanges(changes); - } - - fixture.componentRef.injector.get(ChangeDetectorRef).detectChanges(); - }; - const navigate = async (elementOrPath: Element | string, basePath = ''): Promise => { const href = typeof elementOrPath === 'string' ? elementOrPath : elementOrPath.getAttribute('href'); const [path, params] = (basePath + href).split('?'); @@ -234,8 +207,6 @@ export async function render( detectChanges: () => detectChanges(), navigate, rerender, - change, - changeInput, // @ts-ignore: fixture assigned debugElement: fixture.debugElement, // @ts-ignore: fixture assigned @@ -389,27 +360,32 @@ function getChangesObj(oldProps: Record | null, newProps: Record( +function update( fixture: ComponentFixture, - prevRenderedPropsKeys: string[], - newProps: Record, + prevRenderedKeys: string[], + newValues: Record, + updateFunction: ( + fixture: ComponentFixture, + values: RenderTemplateOptions['componentInputs' | 'componentProperties'], + ) => void, ) { const componentInstance = fixture.componentInstance as Record; const simpleChanges: SimpleChanges = {}; - for (const key of prevRenderedPropsKeys) { - if (!Object.prototype.hasOwnProperty.call(newProps, key)) { + for (const key of prevRenderedKeys) { + if (!Object.prototype.hasOwnProperty.call(newValues, key)) { simpleChanges[key] = new SimpleChange(componentInstance[key], undefined, false); delete componentInstance[key]; } } - for (const [key, value] of Object.entries(newProps)) { + for (const [key, value] of Object.entries(newValues)) { if (value !== componentInstance[key]) { simpleChanges[key] = new SimpleChange(componentInstance[key], value, false); } } - setComponentProperties(fixture, newProps); + + updateFunction(fixture, newValues); return simpleChanges; } diff --git a/projects/testing-library/tests/change.spec.ts b/projects/testing-library/tests/change.spec.ts deleted file mode 100644 index d6d30f46..00000000 --- a/projects/testing-library/tests/change.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; -import { render, screen } from '../src/public_api'; - -@Component({ - selector: 'atl-fixture', - template: ` {{ firstName }} {{ lastName }} `, -}) -class FixtureComponent { - @Input() firstName = 'Sarah'; - @Input() lastName?: string; -} - -test('changes the component with updated props', async () => { - const { change } = await render(FixtureComponent); - expect(screen.getByText('Sarah')).toBeInTheDocument(); - - const firstName = 'Mark'; - change({ firstName }); - - expect(screen.getByText(firstName)).toBeInTheDocument(); -}); - -test('changes the component with updated props while keeping other props untouched', async () => { - const firstName = 'Mark'; - const lastName = 'Peeters'; - const { change } = await render(FixtureComponent, { - componentProperties: { - firstName, - lastName, - }, - }); - - expect(screen.getByText(`${firstName} ${lastName}`)).toBeInTheDocument(); - - const firstName2 = 'Chris'; - change({ firstName: firstName2 }); - - expect(screen.getByText(`${firstName2} ${lastName}`)).toBeInTheDocument(); -}); - -@Component({ - selector: 'atl-fixture', - template: ` {{ propOne }} {{ propTwo }}`, -}) -class FixtureWithNgOnChangesComponent implements OnChanges { - @Input() propOne = 'Init'; - @Input() propTwo = ''; - - // eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method, @typescript-eslint/no-empty-function - ngOnChanges() {} -} - -test('calls ngOnChanges on change', async () => { - const componentInputs = { propOne: 'One', propTwo: 'Two' }; - const { change, fixture } = await render(FixtureWithNgOnChangesComponent, { componentInputs }); - const spy = jest.spyOn(fixture.componentInstance, 'ngOnChanges'); - - expect(screen.getByText(`${componentInputs.propOne} ${componentInputs.propTwo}`)).toBeInTheDocument(); - - const propOne = 'UpdatedOne'; - const propTwo = 'UpdatedTwo'; - change({ propOne, propTwo }); - - expect(spy).toHaveBeenCalledTimes(1); - expect(screen.getByText(`${propOne} ${propTwo}`)).toBeInTheDocument(); -}); - -test('does not invoke ngOnChanges on change without props', async () => { - const componentInputs = { propOne: 'One', propTwo: 'Two' }; - const { change, fixture } = await render(FixtureWithNgOnChangesComponent, { componentInputs }); - const spy = jest.spyOn(fixture.componentInstance, 'ngOnChanges'); - - expect(screen.getByText(`${componentInputs.propOne} ${componentInputs.propTwo}`)).toBeInTheDocument(); - - change({}); - expect(spy).not.toHaveBeenCalled(); - - expect(screen.getByText(`${componentInputs.propOne} ${componentInputs.propTwo}`)).toBeInTheDocument(); -}); -@Component({ - changeDetection: ChangeDetectionStrategy.OnPush, - selector: 'atl-fixture', - template: `
Number
`, -}) -class FixtureWithOnPushComponent { - @Input() activeField = ''; -} - -test('update properties on change', async () => { - const { change } = await render(FixtureWithOnPushComponent); - const numberHtmlElementRef = screen.queryByTestId('number'); - - expect(numberHtmlElementRef).not.toHaveClass('active'); - change({ activeField: 'number' }); - expect(numberHtmlElementRef).toHaveClass('active'); -}); diff --git a/projects/testing-library/tests/changeInputs.spec.ts b/projects/testing-library/tests/changeInputs.spec.ts deleted file mode 100644 index 8a970829..00000000 --- a/projects/testing-library/tests/changeInputs.spec.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; -import { render, screen } from '../src/public_api'; - -@Component({ - selector: 'atl-fixture', - template: ` {{ firstName }} {{ lastName }} `, -}) -class FixtureComponent { - @Input() firstName = 'Sarah'; - @Input() lastName?: string; -} - -test('changes the component with updated props', async () => { - const { changeInput } = await render(FixtureComponent); - expect(screen.getByText('Sarah')).toBeInTheDocument(); - - const firstName = 'Mark'; - changeInput({ firstName }); - - expect(screen.getByText(firstName)).toBeInTheDocument(); -}); - -test('changes the component with updated props while keeping other props untouched', async () => { - const firstName = 'Mark'; - const lastName = 'Peeters'; - const { changeInput } = await render(FixtureComponent, { - componentInputs: { - firstName, - lastName, - }, - }); - - expect(screen.getByText(`${firstName} ${lastName}`)).toBeInTheDocument(); - - const firstName2 = 'Chris'; - changeInput({ firstName: firstName2 }); - - expect(screen.getByText(`${firstName2} ${lastName}`)).toBeInTheDocument(); -}); - -@Component({ - selector: 'atl-fixture', - template: ` {{ propOne }} {{ propTwo }}`, -}) -class FixtureWithNgOnChangesComponent implements OnChanges { - @Input() propOne = 'Init'; - @Input() propTwo = ''; - - // eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method, @typescript-eslint/no-empty-function - ngOnChanges() {} -} - -test('calls ngOnChanges on change', async () => { - const componentInputs = { propOne: 'One', propTwo: 'Two' }; - const { changeInput, fixture } = await render(FixtureWithNgOnChangesComponent, { componentInputs }); - const spy = jest.spyOn(fixture.componentInstance, 'ngOnChanges'); - - expect(screen.getByText(`${componentInputs.propOne} ${componentInputs.propTwo}`)).toBeInTheDocument(); - - const propOne = 'UpdatedOne'; - const propTwo = 'UpdatedTwo'; - changeInput({ propOne, propTwo }); - - expect(spy).toHaveBeenCalledTimes(1); - expect(screen.getByText(`${propOne} ${propTwo}`)).toBeInTheDocument(); -}); - -test('does not invoke ngOnChanges on change without props', async () => { - const componentInputs = { propOne: 'One', propTwo: 'Two' }; - const { changeInput, fixture } = await render(FixtureWithNgOnChangesComponent, { componentInputs }); - const spy = jest.spyOn(fixture.componentInstance, 'ngOnChanges'); - - expect(screen.getByText(`${componentInputs.propOne} ${componentInputs.propTwo}`)).toBeInTheDocument(); - - changeInput({}); - expect(spy).not.toHaveBeenCalled(); - - expect(screen.getByText(`${componentInputs.propOne} ${componentInputs.propTwo}`)).toBeInTheDocument(); -}); - -@Component({ - changeDetection: ChangeDetectionStrategy.OnPush, - selector: 'atl-fixture', - template: `
Number
`, -}) -class FixtureWithOnPushComponent { - @Input() activeField = ''; -} - -test('update properties on change', async () => { - const { changeInput } = await render(FixtureWithOnPushComponent); - const numberHtmlElementRef = screen.queryByTestId('number'); - - expect(numberHtmlElementRef).not.toHaveClass('active'); - changeInput({ activeField: 'number' }); - expect(numberHtmlElementRef).toHaveClass('active'); -}); diff --git a/projects/testing-library/tests/rerender.spec.ts b/projects/testing-library/tests/rerender.spec.ts index a06beaf0..9c252575 100644 --- a/projects/testing-library/tests/rerender.spec.ts +++ b/projects/testing-library/tests/rerender.spec.ts @@ -35,6 +35,7 @@ test('rerenders without props', async () => { await rerender(); expect(screen.getByText('Sarah')).toBeInTheDocument(); + expect(ngOnChangesSpy).toHaveBeenCalledTimes(1); // one time initially and one time for rerender }); test('rerenders the component with updated inputs', async () => { @@ -47,7 +48,42 @@ test('rerenders the component with updated inputs', async () => { expect(screen.getByText(firstName)).toBeInTheDocument(); }); -test('rerenders the component with updated props and resets other props', async () => { +test('rerenders the component with updated inputs and resets other props', async () => { + const firstName = 'Mark'; + const lastName = 'Peeters'; + const { rerender } = await render(FixtureComponent, { + componentInputs: { + firstName, + lastName, + }, + }); + + expect(screen.getByText(`${firstName} ${lastName}`)).toBeInTheDocument(); + + const firstName2 = 'Chris'; + await rerender({ componentInputs: { firstName: firstName2 } }); + + expect(screen.getByText(firstName2)).toBeInTheDocument(); + expect(screen.queryByText(firstName)).not.toBeInTheDocument(); + expect(screen.queryByText(lastName)).not.toBeInTheDocument(); + + expect(ngOnChangesSpy).toHaveBeenCalledTimes(2); // one time initially and one time for rerender + const rerenderedChanges = ngOnChangesSpy.mock.calls[1][0] as SimpleChanges; + expect(rerenderedChanges).toEqual({ + lastName: { + previousValue: 'Peeters', + currentValue: undefined, + firstChange: false, + }, + firstName: { + previousValue: 'Mark', + currentValue: 'Chris', + firstChange: false, + }, + }); +}); + +test('rerenders the component with updated props and resets other props with componentProperties', async () => { const firstName = 'Mark'; const lastName = 'Peeters'; const { rerender } = await render(FixtureComponent, {