added all files to project

This commit is contained in:
2022-03-10 10:36:59 +01:00
parent 09dd957b33
commit 46a936d7de
25351 changed files with 3883356 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
{
"LOADING": "Loading …",
"TODAY": "Today",
"TOMORROW": "Tomorrow",
"DAYAFTERTOMORROW": "In 2 days",
"RUNNING": "Ends in",
"EMPTY": "No upcoming events.",
"WEEK": "Week {weekNumber}",
"N": "N",
"NNE": "NNE",
"NE": "NE",
"ENE": "ENE",
"E": "E",
"ESE": "ESE",
"SE": "SE",
"SSE": "SSE",
"S": "S",
"SSW": "SSW",
"SW": "SW",
"WSW": "WSW",
"W": "W",
"WNW": "WNW",
"NW": "NW",
"NNW": "NNW",
"UPDATE_NOTIFICATION": "MagicMirror² update available.",
"UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.",
"UPDATE_INFO_SINGLE": "The current installation is COMMIT_COUNT commit behind on the BRANCH_NAME branch.",
"UPDATE_INFO_MULTIPLE": "The current installation is COMMIT_COUNT commits behind on the BRANCH_NAME branch."
}
+190
View File
@@ -0,0 +1,190 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:MagicMirrorTest
X-WR-TIMEZONE:America/Santiago
X-WR-CALDESC:Testing propose MagicMirror
BEGIN:VTIMEZONE
TZID:America/Santiago
X-LIC-LOCATION:America/Santiago
BEGIN:STANDARD
TZOFFSETFROM:-0300
TZOFFSETTO:-0400
TZNAME:-04
DTSTART:19700510T000000
RDATE:19700510T030000
RDATE:19710509T030000
RDATE:19720514T030000
RDATE:19730513T030000
RDATE:19740512T030000
RDATE:19750511T030000
RDATE:19760509T030000
RDATE:19770515T030000
RDATE:19780514T030000
RDATE:19790513T030000
RDATE:19800511T030000
RDATE:19810510T030000
RDATE:19820509T030000
RDATE:19830515T030000
RDATE:19840513T030000
RDATE:19850512T030000
RDATE:19860511T030000
RDATE:19870510T030000
RDATE:19880515T030000
RDATE:19890514T030000
RDATE:19900513T030000
RDATE:19910512T030000
RDATE:19920510T030000
RDATE:19930509T030000
RDATE:19940515T030000
RDATE:19950514T030000
RDATE:19960512T030000
RDATE:19970511T030000
RDATE:19980510T030000
RDATE:19990509T030000
RDATE:20000514T030000
RDATE:20010513T030000
RDATE:20020512T030000
RDATE:20030511T030000
RDATE:20040509T030000
RDATE:20050515T030000
RDATE:20060514T030000
RDATE:20070513T030000
RDATE:20080511T030000
RDATE:20090510T030000
RDATE:20100509T030000
RDATE:20110515T030000
RDATE:20120513T030000
RDATE:20130512T030000
RDATE:20140511T030000
RDATE:20150510T030000
RDATE:20160515T030000
RDATE:20170514T030000
RDATE:20180513T030000
RDATE:20190512T030000
RDATE:20200510T030000
RDATE:20210509T030000
RDATE:20220515T030000
RDATE:20230514T030000
RDATE:20240512T030000
RDATE:20250511T030000
RDATE:20260510T030000
RDATE:20270509T030000
RDATE:20280514T030000
RDATE:20290513T030000
RDATE:20300512T030000
RDATE:20310511T030000
RDATE:20320509T030000
RDATE:20330515T030000
RDATE:20340514T030000
RDATE:20350513T030000
RDATE:20360511T030000
RDATE:20370510T030000
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:-0300
TZOFFSETTO:-0400
TZNAME:-04
DTSTART:20380509T000000
RRULE:FREQ=YEARLY;BYMONTH=5;BYDAY=2SU
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:-0400
TZOFFSETTO:-0300
TZNAME:-03
DTSTART:19700809T000000
RDATE:19700809T040000
RDATE:19710815T040000
RDATE:19720813T040000
RDATE:19730812T040000
RDATE:19740811T040000
RDATE:19750810T040000
RDATE:19760815T040000
RDATE:19770814T040000
RDATE:19780813T040000
RDATE:19790812T040000
RDATE:19800810T040000
RDATE:19810809T040000
RDATE:19820815T040000
RDATE:19830814T040000
RDATE:19840812T040000
RDATE:19850811T040000
RDATE:19860810T040000
RDATE:19870809T040000
RDATE:19880814T040000
RDATE:19890813T040000
RDATE:19900812T040000
RDATE:19910811T040000
RDATE:19920809T040000
RDATE:19930815T040000
RDATE:19940814T040000
RDATE:19950813T040000
RDATE:19960811T040000
RDATE:19970810T040000
RDATE:19980809T040000
RDATE:19990815T040000
RDATE:20000813T040000
RDATE:20010812T040000
RDATE:20020811T040000
RDATE:20030810T040000
RDATE:20040815T040000
RDATE:20050814T040000
RDATE:20060813T040000
RDATE:20070812T040000
RDATE:20080810T040000
RDATE:20090809T040000
RDATE:20100815T040000
RDATE:20110814T040000
RDATE:20120812T040000
RDATE:20130811T040000
RDATE:20140810T040000
RDATE:20150809T040000
RDATE:20160814T040000
RDATE:20170813T040000
RDATE:20180812T040000
RDATE:20190811T040000
RDATE:20200809T040000
RDATE:20210815T040000
RDATE:20220814T040000
RDATE:20230813T040000
RDATE:20240811T040000
RDATE:20250810T040000
RDATE:20260809T040000
RDATE:20270815T040000
RDATE:20280813T040000
RDATE:20290812T040000
RDATE:20300811T040000
RDATE:20310810T040000
RDATE:20320815T040000
RDATE:20330814T040000
RDATE:20340813T040000
RDATE:20350812T040000
RDATE:20360810T040000
RDATE:20370809T040000
END:DAYLIGHT
BEGIN:DAYLIGHT
TZOFFSETFROM:-0400
TZOFFSETTO:-0300
TZNAME:-03
DTSTART:20380815T000000
RRULE:FREQ=YEARLY;BYMONTH=8;BYDAY=2SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=America/Santiago:20170309T100000
DTEND;TZID=America/Santiago:20170309T110000
RRULE:FREQ=MONTHLY;INTERVAL=30;BYMONTHDAY=9
DTSTAMP:20170310T172720Z
UID:80rl9kuu5bq49gme99eklov27k@google.com
CREATED:20170310T172400Z
DESCRIPTION:
LAST-MODIFIED:20170310T172400Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:TestEvent
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR
@@ -0,0 +1,56 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//ical.marudot.com//iCal Event Maker
X-WR-CALNAME:TestEvents
NAME:TestEvents
CALSCALE:GREGORIAN
BEGIN:VTIMEZONE
TZID:Europe/Berlin
TZURL:http://tzurl.org/zoneinfo-outlook/Europe/Berlin
X-LIC-LOCATION:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20200719T094531Z
UID:20200719T094531Z-1871115387@marudot.com
DTSTART;TZID=Europe/Berlin:20300101T120000
DTEND;TZID=Europe/Berlin:20300101T130000
SUMMARY:TestEvent
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20200719T094531Z
UID:20200719T094531Z-1929725136@marudot.com
DTSTART;TZID=Europe/Berlin:20300701T120000
RRULE:FREQ=YEARLY;BYMONTH=7;BYMONTHDAY=1
DTEND;TZID=Europe/Berlin:20300701T130000
SUMMARY:TestEventRepeat
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20200719T094531Z
UID:20200719T094531Z-371801474@marudot.com
DTSTART;VALUE=DATE:20300401
DTEND;VALUE=DATE:20300402
SUMMARY:TestEventDay
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20200719T094531Z
UID:20200719T094531Z-133401084@marudot.com
DTSTART;VALUE=DATE:20301001
RRULE:FREQ=YEARLY;BYMONTH=10;BYMONTHDAY=1
DTEND;VALUE=DATE:20301002
SUMMARY:TestEventRepeatDay
END:VEVENT
END:VCALENDAR
@@ -0,0 +1,37 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:xxx@gmail.com
X-WR-TIMEZONE:Europe/Zurich
BEGIN:VTIMEZONE
TZID:Etc/UTC
X-LIC-LOCATION:Etc/UTC
BEGIN:STANDARD
TZOFFSETFROM:+0000
TZOFFSETTO:+0000
TZNAME:GMT
DTSTART:19700101T00000--äüüßßß-0
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;VALUE=DATE:20210325
DTEND;VALUE=DATE:20210326
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1
DTSTAMP:20210421T154106Z
UID:zzz@google.com
REATED:20200831T200244Z
DESCRIPTION:
LAST-MODIFIED:20200831T200244Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Birthday
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER:-P0DT7H0M0S
END:VALARM
END:VEVENT
+33
View File
@@ -0,0 +1,33 @@
{
"LOADING": "Loading …",
"TODAY": "Today",
"TOMORROW": "Tomorrow",
"DAYAFTERTOMORROW": "In 2 days",
"RUNNING": "Ends in",
"EMPTY": "No upcoming events.",
"WEEK": "Week {weekNumber}",
"N": "N",
"NNE": "NNE",
"NE": "NE",
"ENE": "ENE",
"E": "E",
"ESE": "ESE",
"SE": "SE",
"SSE": "SSE",
"S": "S",
"SSW": "SSW",
"SW": "SW",
"WSW": "WSW",
"W": "W",
"WNW": "WNW",
"NW": "NW",
"NNW": "NNW",
"UPDATE_NOTIFICATION": "MagicMirror² update available.",
"UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.",
"UPDATE_INFO_SINGLE": "The current installation is COMMIT_COUNT commit behind on the BRANCH_NAME branch.",
"UPDATE_INFO_MULTIPLE": "The current installation is COMMIT_COUNT commits behind on the BRANCH_NAME branch."
}
@@ -0,0 +1,410 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
<title>Rodrigo Ramírez Norambuena</title>
<atom:link href="https://rodrigoramirez.com/feed/" rel="self" type="application/rss+xml"/>
<link>https://rodrigoramirez.com</link>
<description>Temas sobre Linux, VoIP, Open Source, tecnología y lo relacionado.</description>
<lastBuildDate>Fri, 21 Oct 2016 21:30:22 +0000</lastBuildDate>
<language>es-ES</language>
<sy:updatePeriod>hourly</sy:updatePeriod>
<sy:updateFrequency>1</sy:updateFrequency>
<generator>https://wordpress.org/?v=4.7.3</generator>
<item>
<title>QPanel 0.13.0</title>
<link>https://rodrigoramirez.com/qpanel-0-13-0/</link>
<comments>https://rodrigoramirez.com/qpanel-0-13-0/#comments</comments>
<pubDate>Tue, 20 Sep 2016 11:16:08 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<category><![CDATA[spy]]></category>
<category><![CDATA[supervision]]></category>
<category><![CDATA[templates]]></category>
<category><![CDATA[whisper]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1299</guid>
<description><![CDATA[<p>Ya está disponible la versión 0.13.0 de QPanel Para instalar esta nueva versión, la debes descargar de https://github.com/roramirez/qpanel/tree/0.13.0 En al README.md puedes encontrar las instrucciones para hacer que funcione en tu sistema. En esta nueva versión cuenta con los siguientes cambios: Se establece un limite para el reciclado del tiempo de conexión a la base [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-13-0/">QPanel 0.13.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible la versión 0.13.0 de QPanel</p>
<p>Para instalar esta nueva versión, la debes descargar de</p>
<ul>
<li><a href="https://github.com/roramirez/qpanel/tree/0.13.0">https://github.com/roramirez/qpanel/tree/0.13.0</a></li>
</ul>
<p>En al README.md puedes encontrar las instrucciones para hacer que funcione en tu sistema.</p>
<p>En esta nueva versión cuenta con los siguientes cambios:</p>
<ul>
<li>Se establece un limite para el reciclado del tiempo de conexión a la base de datos que contenga QueueLog. Esto evita problemas en bases de datos como MySQL que finaliza o da timeout a las conexiones.</li>
<li>Ahora la py-asterisk va dentro del archivo requirements.txt y no como submodulo del proyecto.</li>
<li>Se remueven la mayoría de las libs externas para Javascript y CSS para manejarlos desde ahora con <a href="https://bower.io/">Bower</a>.</li>
<li>Se incluye un script para WSGI que permite su utilización con Apache.</li>
<li>Actualización para los idiomas Ruso y Portugues.</li>
</ul>
<p>Si deseas colaborar con el proyecto puedes agregar nuevas sugerencias mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a> ó colaborar mediante <a href="https://github.com/roramirez/qpanel/blob/dd42cf0f534408505f57b0d387dffee2f3688711/README.md#how-to-contribute">mediante un Pull Request.</a></p>
<p>Ahora si necesitas <a href="https://boxtub.com/qpanel/">soporte comercial para instalaciones, personalizaciones o nuevas características  lo puedes solicitar en https://boxtub.com/qpanel/</a></p>
<p>&nbsp;</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-13-0/">QPanel 0.13.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-13-0/feed/</wfw:commentRss>
<slash:comments>3</slash:comments>
</item>
<item>
<title>Problema VirtualBox &#8220;starting virtual machine&#8221; &#8230;</title>
<link>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/</link>
<comments>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/#respond</comments>
<pubDate>Sat, 10 Sep 2016 22:50:13 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[no arranca]]></category>
<category><![CDATA[Problema]]></category>
<category><![CDATA[VirtualBox]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1284</guid>
<description><![CDATA[<p>Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox.  La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual quería arrancar, se quedaba en un largo limbo con el mensaje &#8220;starting virtual machine&#8221;, como el de la imagen de a continuación. [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/">Problema VirtualBox &#8220;starting virtual machine&#8221; &#8230;</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p>Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox.  La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual quería arrancar, se quedaba en un largo limbo con el mensaje &#8220;starting virtual machine&#8221;, como el de la imagen de a continuación.</p>
<p><a href="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09.png"><img class="aligncenter wp-image-1290 size-full" src="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09.png" alt="Starting virtual machine ... VirtualBox" width="648" height="554" srcset="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09.png 648w, https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09-300x256.png 300w" sizes="(max-width: 648px) 100vw, 648px" /></a></p>
<p>Ninguna, pero ninguna maquina arrancó, se quedaban en ese mensaje. Fue de esos instantes en que sudas helado &#8230; <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>Con un poco de investigación fue a parar al archivo<em> ~/.VirtualBox/VBoxSVC.log </em>que indicaba</p>
<pre>$ tail -f ~/.VirtualBox/VBoxSVC.log
00:08:32.932717 nspr-7 Failed to open "/dev/vboxdrvu", errno=13, rc=VERR_VM_DRIVER_NOT_ACCESSIBLE
00:08:33.555836 nspr-6 Failed to open "/dev/vboxdrvu", errno=13, rc=VERR_VM_DRIVER_NOT_ACCESSIBLE</pre>
<p>&nbsp;</p>
<p>Fui&#8230; algo de donde agarrarse. Mirando un poco mas se trataba de problemas con los permisos al vboxdrvu, mirando indicaba que tenía 0600.</p>
<p>&nbsp;</p>
<pre>$ ls -lh /dev/vboxdrvu
crw------- 1 root root 10, 56 Sep 10 12:47 /dev/vboxdrvu</pre>
<p>&nbsp;</p>
<p>El tema es que deben estar en 0666,  le cambias los permisos y eso soluciona el problema <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<pre>
$ sudo chmod 0666 /dev/vboxdrvu
$ ls -lh /dev/vboxdrvu
crw-rw-rw- 1 root root 10, 56 Sep 10 12:47 /dev/vboxdrvu</pre>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/">Problema VirtualBox &#8220;starting virtual machine&#8221; &#8230;</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>Mejorando la consola interactiva de Python</title>
<link>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/</link>
<comments>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/#comments</comments>
<pubDate>Tue, 06 Sep 2016 04:24:43 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[desarrollo]]></category>
<category><![CDATA[Desarrollo]]></category>
<category><![CDATA[Python]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1247</guid>
<description><![CDATA[<p>Cuando estás desarrollando en Python es muy cool estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente. La consola de Python funciona y cumple su cometido. Solo al tipear  python  te permite entrar en modo interactivo e ir probando cosas. El punto es que a veces [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/">Mejorando la consola interactiva de Python</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p>Cuando estás desarrollando en Python es muy <em>cool</em> estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente.</p>
<p>La consola de Python funciona y cumple su cometido. Solo al tipear  <em>python  </em>te permite entrar en modo interactivo e ir probando cosas.</p>
<p>El punto es que a veces uno necesita ir un poco más allá. Como autocomentado de código o resaltado de sintaxis, para eso tengo dos truco que utilizo generalmente.</p>
<h2>Truco a)</h2>
<p>Este permite añadirle algunos esteriodes a la consolta, en realidad uno, el autocompletado. Esto es de gran ayuda para ir conociendo los metodo que puede tener un objecto, funciones u operaciones.</p>
<p>Para esto se ocupo <em>rlcompleter</em> y  <em>readline. </em></p>
<p>&nbsp;</p>
<p>Lo que hace que hacer luego de tipear python es agregar lo siguiente dentro de la consola interativa</p>
<p><em>import rlcompleter, readline</em><br />
<em>readline.parse_and_bind(&#8216;tab:complete&#8217;)</em></p>
<p>Ya con esto te permite autocomentar código <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p><a href="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-03-01-14-32.png"><img class="aligncenter wp-image-1279 size-full" src="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-03-01-14-32.png" width="689" height="421" srcset="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-03-01-14-32.png 689w, https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-03-01-14-32-300x183.png 300w" sizes="(max-width: 689px) 100vw, 689px" /></a></p>
<p>&nbsp;</p>
<h2>Truco b)</h2>
<p>Esto es mejorar un poco más. Es utilizar embed de <a href="https://ipython.org/">IPython,</a>  ya en la consola digita (copias o pegas) lo siguiente</p>
<p><em>from IPython import embed</em><br />
<em>embed()</em></p>
<p>Y el resultado será lo que se ve a continuación&#8230; bueno, no?</p>
<p>&nbsp;</p>
<p><a href="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-07-25-15-48-39.png"><img class="aligncenter wp-image-1262 size-full" src="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-07-25-15-48-39.png" width="743" height="293" srcset="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-07-25-15-48-39.png 743w, https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-07-25-15-48-39-300x118.png 300w" sizes="(max-width: 743px) 100vw, 743px" /></a></p>
<p>&nbsp;</p>
<p>Si no quieres estar escribiendo cada vez que entras, agregas estas instrucciones en tu archivo  <em>~/.pythonrc.py </em> y lo hará cada vez que entras en el modo interactivo de la consola de Python. Lo que si, tu archivo pythonrc.py debe estar seteado en variable de entorno PYTHONSTARTUP</p>
<p>ejemplo</p>
<p><em>export  PYTHONSTARTUP=~/.pythonrc.py</em></p>
<p>O lo agregas a un bashrc, zshrc o la shell que ocupes.</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/">Mejorando la consola interactiva de Python</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/feed/</wfw:commentRss>
<slash:comments>4</slash:comments>
</item>
<item>
<title>QPanel 0.12.0 con estadísticas</title>
<link>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/</link>
<comments>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/#respond</comments>
<pubDate>Mon, 22 Aug 2016 04:19:03 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<category><![CDATA[spy]]></category>
<category><![CDATA[supervision]]></category>
<category><![CDATA[templates]]></category>
<category><![CDATA[whisper]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1268</guid>
<description><![CDATA[<p>Ya está disponible una nueva versión de QPanel, esta es la 0.12.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.12.0 En esta nueva versión las funcionalidades agregadas son: Permite remover los agentes de las cola Posibilidad de cancelar llamadas que están en espera de atención Estadísticas por rango de fecha obtenidas desde [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/">QPanel 0.12.0 con estadísticas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible una nueva versión de QPanel, esta es la 0.12.0</p>
<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p>
<ul>
<li><a href="https://github.com/roramirez/qpanel/tree/0.12.0">https://github.com/roramirez/qpanel/tree/0.12.0</a></li>
</ul>
<p>En esta nueva versión las funcionalidades agregadas son:</p>
<ul>
<li>Permite remover los agentes de las cola</li>
<li>Posibilidad de cancelar llamadas que están en espera de atención</li>
<li>Estadísticas por rango de fecha obtenidas desde el queue_log de Asterisk</li>
<li>Se actualiza a Flask 0.11</li>
</ul>
<p>Si deseas colaborar con el proyecto puedes agregar nuevas sugerencias mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a> ó colaborar mediante <a href="https://github.com/roramirez/qpanel/blob/dd42cf0f534408505f57b0d387dffee2f3688711/README.md#how-to-contribute">mediante un Pull Request</a></p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/">QPanel 0.12.0 con estadísticas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>QPanel 0.11.0 con Spy, Whisper y mas</title>
<link>https://rodrigoramirez.com/qpanel-spy-supervisor/</link>
<comments>https://rodrigoramirez.com/qpanel-spy-supervisor/#comments</comments>
<pubDate>Thu, 21 Jul 2016 01:53:21 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<category><![CDATA[spy]]></category>
<category><![CDATA[supervision]]></category>
<category><![CDATA[templates]]></category>
<category><![CDATA[whisper]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1245</guid>
<description><![CDATA[<p>Ya está disponible una nueva versión de QPanel, esta es la 0.11.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.11.0 Esta versión hemos agregado  algunas funcionalidades que los usuarios  han ido solicitando. Para esta versión es posible realizar Spy, Whisper o Barge a un canal para la supervisión de los miembros que [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-spy-supervisor/">QPanel 0.11.0 con Spy, Whisper y mas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible una nueva versión de QPanel, esta es la 0.11.0</p>
<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p>
<ul>
<li><a href="https://github.com/roramirez/qpanel/tree/0.11.0">https://github.com/roramirez/qpanel/tree/0.11.0</a></li>
</ul>
<p>Esta versión hemos agregado  algunas funcionalidades que los usuarios  han ido solicitando.</p>
<p>Para esta versión es posible realizar Spy, Whisper o Barge a un canal para la supervisión de los miembros que están en una cola.</p>
<p>También el sistema de plantillas se hecho una refactorización para eliminar exceso de codigo HTML usando uno de base.</p>
<p>Se han agregado una suite de tests unitarios que al contar del avance del proyecto deberían ir incrementando.</p>
<p>Se ha solucionado un bug con la actualización del color del estado del agente cuando es uno nuevo agregado a la cola.</p>
<p>&nbsp;</p>
<p>El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a>.</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-spy-supervisor/">QPanel 0.11.0 con Spy, Whisper y mas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-spy-supervisor/feed/</wfw:commentRss>
<slash:comments>4</slash:comments>
</item>
<item>
<title>Añadir Swap a un sistema</title>
<link>https://rodrigoramirez.com/crear-swap/</link>
<comments>https://rodrigoramirez.com/crear-swap/#respond</comments>
<pubDate>Fri, 15 Jul 2016 05:07:43 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1234</guid>
<description><![CDATA[<p>Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap. La  memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM. El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/crear-swap/">Añadir Swap a un sistema</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p>Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap.</p>
<p>La  memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM.</p>
<p>El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto un espacio para la Swap, lo que te lleva a que el sistema pueda tener crash durante la ejecución.</p>
<p>Para comprobar la asignación de memoria, al ejecutar el comando <em>free</em> nos debería mostrar como algo similar a lo siguiente</p>
<p>&nbsp;</p>
<pre>$  free -m
             total       used       free     shared    buffers     cached
Mem:           494        488          6          1         54         75
-/+ buffers/cache:        357        136
Swap:            0          0          0</pre>
<p>En la zona de swap indica que no asignada, valor 0.</p>
<p>Para asignar swap al sistema se debe  un archivo en disco para que sea utilizado como espacio de intercambio, en este caso lo vamos  crear uno  de 3GB en la raíz del sistema</p>
<pre class="code-pre "><code>fallocate -l 3G /swapfile</code></pre>
<p>Comprobamos que ha sido creado</p>
<pre>$ ls -lh /swapfile
-rw-r--r-- 1 root root 3.0G Jul 11 13:10 /swapfile
</pre>
<h3>Habilitación del archivo Swap</h3>
<p>Ahora nos toca habilitar el archivo creado. Para eso le asignaremos los permisos</p>
<pre>chmod 600 /swapfile</pre>
<p>Lo siguiente es para convertir el  archivo para swap</p>
<pre>mkswap /swapfile</pre>
<p>Para habilitar y asignarla eso como memoria swap al sistema usamos</p>
<pre>swapon /swapfile</pre>
<p>Ya con esto podrémos ver en nuestro sistema la memoria asignada para swap</p>
<pre>$ free -m
             total       used       free     shared    buffers     cached
Mem:           494        486          7          1         51         77
-/+ buffers/cache:        358        136
Swap:         3071          0       3071</pre>
<p>&nbsp;</p>
<p>Para que al reiniciar el sistema esto se mantenga, debemos agregar la siguiente línea al archivo /etc/fstab</p>
<pre><span class="pl-s">/swapfile none swap sw 0 0</span></pre>
<p>&nbsp;</p>
<p>Podemos editar /etc/fstab con algún editor como vim, nano o podemos agregar la linea directamente en la desde la cli de la siguiente manera</p>
<pre><span class="pl-c1">echo</span> <span class="pl-s"><span class="pl-pds">"</span>/swapfile none swap sw 0 0<span class="pl-pds">"</span></span> <span class="pl-k">&gt;&gt;</span> /etc/fstab</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/crear-swap/">Añadir Swap a un sistema</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/crear-swap/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>QPanel 0.10.0 con vista consolidada</title>
<link>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/</link>
<comments>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/#respond</comments>
<pubDate>Mon, 20 Jun 2016 19:32:55 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1227</guid>
<description><![CDATA[<p>Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible. Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.10.0 Esta versión versión nos preocupamos de realizar mejoras, refactorizaciones y agregamos una nueva funcionalidad. La nueva funcionalidad incluida es  que ahora es posible contar con una vista consolidada para [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/">QPanel 0.10.0 con vista consolidada</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p><img class="alignleft" src="https://raw.githubusercontent.com/roramirez/qpanel/0.10.0/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="403" height="227" />Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible.</p>
<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p>
<ul>
<li><a href="https://github.com/roramirez/qpanel/tree/0.10.0">https://github.com/roramirez/qpanel/tree/0.10.0</a></li>
</ul>
<p>Esta versión versión nos preocupamos de realizar mejoras, refactorizaciones y agregamos una nueva funcionalidad.</p>
<p>La nueva funcionalidad incluida es  que ahora es posible contar con una vista consolidada para la información de todas las colas. Que hace tener un mejor control y visualización de lo que está pasando en las colas.</p>
<p>El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a>.</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/">QPanel 0.10.0 con vista consolidada</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>Nerdearla 2016, WebRTC Glue</title>
<link>https://rodrigoramirez.com/nerdearla-2016/</link>
<comments>https://rodrigoramirez.com/nerdearla-2016/#respond</comments>
<pubDate>Wed, 15 Jun 2016 17:55:41 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[baires]]></category>
<category><![CDATA[charla]]></category>
<category><![CDATA[Computación]]></category>
<category><![CDATA[informatica]]></category>
<category><![CDATA[tech]]></category>
<category><![CDATA[ti]]></category>
<category><![CDATA[webrtc]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1218</guid>
<description><![CDATA[<p>Días atrás estuve participando en el evento llamado Nerdearla en Buenos Aires.  El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes. Habían muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/nerdearla-2016/">Nerdearla 2016, WebRTC Glue</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p>Días atrás estuve participando en el evento llamado <a href="https://nerdear.la/">Nerdearla</a> en Buenos Aires.  El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes.</p>
<p>Habían muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te lo perdiste te recomiendo que estés pendiente para el proximo año.</p>
<p>&nbsp;</p>
<p>Te podias encontrar con una nuestra como esta<a href="https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS.jpg"><img class="aligncenter size-medium wp-image-1221" src="https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-300x169.jpg" alt="Kaypro II" width="300" height="169" srcset="https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-300x169.jpg 300w, https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-768x432.jpg 768w, https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-1024x576.jpg 1024w, https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS.jpg 1200w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<p>Puedes dar un vistaso a lo registrado por algunos <a href="https://twitter.com/hashtag/nerdearla?f=tweets&amp;vertical=default&amp;src=hash">usuarios en Twitter</a></p>
<p>El primer día hice un workshop denominado WebRTC Glue, donde muestra como hacer como unificar la experiencia de atención del centro de contacto directamente en la web. Es una presentación práctica donde puedes ver los ejemplos y usarlos como gustes. Están en <a href="https://gitlab.com/roramirez/webrtc-glue">el repositorio en Gitlab</a>. La presentación <a href="/charlas/webrtc-glue/index.html">la puedes ver aquí</a></p>
<p>&nbsp;</p>
<p><a href="https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW.jpg"><img class="aligncenter size-medium wp-image-1222" src="https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW-300x169.jpg" alt="WebRTC Glue" width="300" height="169" srcset="https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW-300x169.jpg 300w, https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW-768x432.jpg 768w, https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW-1024x576.jpg 1024w, https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW.jpg 1200w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<p>Haber si nos vemos el próximo año.</p>
<p>&nbsp;</p>
<p><strong>Update</strong>: Puedes ver una parte sin la demostración del workshop</p>
<p><iframe src="https://www.youtube.com/embed/qDreeYk-UK4?list=PLTTdzfRyGY1vQukK8L4QeAfYdbURSzFq5" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"></iframe><br />
&nbsp;</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/nerdearla-2016/">Nerdearla 2016, WebRTC Glue</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/nerdearla-2016/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>QPanel 0.9.0</title>
<link>https://rodrigoramirez.com/qpanel-0-9-0/</link>
<comments>https://rodrigoramirez.com/qpanel-0-9-0/#respond</comments>
<pubDate>Mon, 09 May 2016 18:40:23 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[callcenter]]></category>
<category><![CDATA[colas]]></category>
<category><![CDATA[monitor]]></category>
<category><![CDATA[monitoreo]]></category>
<category><![CDATA[panel]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queues]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1206</guid>
<description><![CDATA[<p>El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.9.0 Esta versión versión nos preocupamos de realizar mejoras y refactorizaciones en el codigo para dar un mejor rendimiento, como también de la compatibilidad con la versión 11 de [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-9-0/">QPanel 0.9.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p><img class="alignleft" src="https://raw.githubusercontent.com/roramirez/qpanel/0.9.0/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="403" height="227" />El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0</p>
<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p>
<ul>
<li><a href="https://github.com/roramirez/qpanel/tree/0.9.0">https://github.com/roramirez/qpanel/tree/0.9.0</a></li>
</ul>
<p>Esta versión versión nos preocupamos de realizar mejoras y refactorizaciones en el codigo para dar un mejor rendimiento, como también de la compatibilidad con la versión 11 de Asterisk.</p>
<p>Dentro de las cosas que podamos mencionar:</p>
<ul>
<li> Actualización del repositorio y versión de py-asterisk, biblioteca para trabajar con Asterisk. Acá la ocupamos principalmente para uso del Manager.</li>
<li>Portación de parche de funcionalidades como pausa, tiempo, razón de una pausa para Asterisk 11.</li>
<li>Cambio del comportamiento en el conteo cuando el participante en una cola está ocupado (busy)</li>
</ul>
<p>El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a>.</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-9-0/">QPanel 0.9.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-9-0/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>Mandar un email desde la shell</title>
<link>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/</link>
<comments>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/#comments</comments>
<pubDate>Wed, 13 Apr 2016 13:05:13 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[mini-tips]]></category>
<category><![CDATA[bash]]></category>
<category><![CDATA[cli]]></category>
<category><![CDATA[Email]]></category>
<category><![CDATA[mail]]></category>
<category><![CDATA[sh]]></category>
<category><![CDATA[shell]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1172</guid>
<description><![CDATA[<p>Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando mail en un servidor con Linux. Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mandar-un-email-desde-la-shell/">Mandar un email desde la shell</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p>Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando <em>mail</em> en un servidor con Linux.</p>
<p>Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un <em>echo</em> le pasas por pipe a <em>mail</em></p>
<pre>echo "Cuerpo del mensaje" | mail -s Asunto a@rodrigoramirez.com</pre>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mandar-un-email-desde-la-shell/">Mandar un email desde la shell</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/feed/</wfw:commentRss>
<slash:comments>4</slash:comments>
</item>
</channel>
</rss>
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
/* Magic Mirror Test default config for modules
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
exports.configFactory = function (options) {
return Object.assign(
{
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
}
},
modules: []
},
options
);
};
+13
View File
@@ -0,0 +1,13 @@
/* Magic Mirror Test config sample ipWhitelist
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
ipWhitelist: []
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+11
View File
@@ -0,0 +1,11 @@
/* Magic Mirror Test config sample environment
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory();
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+21
View File
@@ -0,0 +1,21 @@
/* Magic Mirror Test config sample module alert
*
* By rejas
* MIT Licensed.
*/
let config = {
modules: [
{
module: "alert",
config: {
display_time: 1000000,
welcome_message: true
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,32 @@
/* Magic Mirror Test config default calendar with auth by default
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
calendars: [
{
maximumNumberOfDays: 10000,
url: "http://localhost:8080/tests/configs/data/calendar_test.ics",
auth: {
user: "MagicMirror",
pass: "CallMeADog"
}
}
]
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,33 @@
/* Magic Mirror Test config default calendar
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
calendars: [
{
maximumNumberOfDays: 10000,
url: "http://localhost:8080/tests/configs/data/calendar_test.ics",
auth: {
user: "MagicMirror",
pass: "CallMeADog",
method: "basic"
}
}
]
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,32 @@
/* Magic Mirror Test config default calendar with auth by default
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
calendars: [
{
maximumNumberOfDays: 10000,
url: "http://localhost:8010/tests/configs/data/calendar_test.ics",
auth: {
user: "MagicMirror",
pass: "CallMeADog"
}
}
]
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+32
View File
@@ -0,0 +1,32 @@
/* Magic Mirror Test config custom calendar
*
* By Rejas
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
calendars: [
{
maximumEntries: 4,
maximumNumberOfDays: 10000,
symbol: "birthday-cake",
fullDaySymbol: "calendar-day",
recurringSymbol: "undo",
url: "http://localhost:8080/tests/configs/data/calendar_test_icons.ics"
}
]
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+28
View File
@@ -0,0 +1,28 @@
/* Magic Mirror Test config default calendar
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
calendars: [
{
maximumNumberOfDays: 10000,
url: "http://localhost:8080/tests/configs/data/calendar_test.ics"
}
]
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,35 @@
/* Magic Mirror Test calendar calendar
*
* This configuration is a wrong authentication
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
calendars: [
{
maximumNumberOfDays: 10000,
url: "http://localhost:8020/tests/configs/data/calendar_test.ics",
auth: {
user: "MagicMirror",
pass: "StairwayToHeaven",
method: "basic"
}
}
]
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,30 @@
/* Magic Mirror Test config default calendar
* with authentication old config
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
calendars: [
{
maximumNumberOfDays: 10000,
url: "http://localhost:8080/tests/configs/data/calendar_test.ics",
user: "MagicMirror",
pass: "CallMeADog"
}
]
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,29 @@
/* Magic Mirror Test config custom calendar
*
* By Rejas
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
calendars: [
{
maximumEntries: 6,
maximumNumberOfDays: 3650,
url: "http://localhost:8080/tests/configs/data/calendar_test_recurring.ics"
}
]
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+20
View File
@@ -0,0 +1,20 @@
/* Magic Mirror Test config for default clock module
*
* By Sergey Morozov
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "clock",
position: "middle_center"
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+18
View File
@@ -0,0 +1,18 @@
/* Magic Mirror Test config for default clock module
*
* By Sergey Morozov
* MIT Licensed.
*/
let config = {
modules: [
{
module: "clock",
position: "middle_center"
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,21 @@
/* Magic Mirror Test config for analog clock face
*
* MIT Licensed.
*/
let config = {
modules: [
{
module: "clock",
position: "middle_center",
config: {
displayType: "analog",
analogFace: "face-006"
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,23 @@
/* Magic Mirror Test config for default clock module
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "clock",
position: "middle_center",
config: {
displaySeconds: false
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,23 @@
/* Magic Mirror Test config for default clock module
*
* By Sergey Morozov
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "clock",
position: "middle_center",
config: {
showPeriodUpper: true
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,23 @@
/* Magic Mirror Test config for default clock module
*
* By Johan Hammar
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "clock",
position: "middle_center",
config: {
showTime: false
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,23 @@
/* Magic Mirror Test config for default clock module
*
* By Johan Hammar
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "clock",
position: "middle_center",
config: {
showWeek: true
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,21 @@
/* Magic Mirror Test config for default clock module
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
language: "es",
timeFormat: 12,
modules: [
{
module: "clock",
position: "middle_center"
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,20 @@
/* Magic Mirror Test config for default clock module
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
language: "es",
modules: [
{
module: "clock",
position: "middle_center"
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,24 @@
/* Magic Mirror Test config for default clock module
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
language: "es",
timeFormat: 12,
modules: [
{
module: "clock",
position: "middle_center",
config: {
showPeriodUpper: true
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,25 @@
/* Magic Mirror Test config for default clock module
* Language es for showWeek feature
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
language: "es",
timeFormat: 12,
modules: [
{
module: "clock",
position: "middle_center",
config: {
showWeek: true
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,28 @@
/* Magic Mirror Test config compliments with anytime type
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "compliments",
position: "middle_center",
config: {
compliments: {
morning: [],
afternoon: [],
evening: [],
anytime: ["Anytime here"]
}
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,29 @@
/* Magic Mirror Test config compliments with date type
*
* By Rejas
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "compliments",
position: "middle_center",
config: {
mockDate: "2020-01-01",
compliments: {
morning: [],
afternoon: [],
evening: [],
"....-01-01": ["Happy new year!"]
}
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,25 @@
/* Magic Mirror Test config compliments with anytime type
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "compliments",
position: "middle_center",
config: {
compliments: {
anytime: ["Anytime here"]
}
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,27 @@
/* Magic Mirror Test config for default compliments
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "compliments",
position: "middle_center",
config: {
compliments: {
morning: ["Hi", "Good Morning", "Morning test"],
afternoon: ["Hello", "Good Afternoon", "Afternoon test"],
evening: ["Hello There", "Good Evening", "Evening test"]
}
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+29
View File
@@ -0,0 +1,29 @@
/* Magic Mirror Test config for display setters module using the helloworld module
*
* By Rejas
* MIT Licensed.
*/
let config = {
modules: [
{
module: "helloworld",
position: "top_bar",
header: "test_header",
config: {
text: "Test Display Header"
}
},
{
module: "helloworld",
position: "bottom_bar",
config: {
text: "Test Hide Header"
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,21 @@
/* Magic Mirror Test config sample module hello world
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
modules: [
{
module: "helloworld",
position: "bottom_bar",
config: {
text: "Test HelloWorld Module"
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,18 @@
/* Magic Mirror Test config sample module hello world default config
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
modules: [
{
module: "helloworld",
position: "bottom_bar"
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+28
View File
@@ -0,0 +1,28 @@
/* Magic Mirror Test config newsfeed module
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "newsfeed",
position: "bottom_bar",
config: {
feeds: [
{
title: "Rodrigo Ramirez Blog",
url: "http://localhost:8080/tests/configs/data/feed_test_rodrigoramirez.xml"
}
]
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,27 @@
/* Magic Mirror Test config newsfeed module
*
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "newsfeed",
position: "bottom_bar",
config: {
feeds: [
{
title: "Incorrect Url",
url: "this is not a valid url"
}
]
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,29 @@
/* Magic Mirror Test config newsfeed module
*
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "newsfeed",
position: "bottom_bar",
config: {
feeds: [
{
title: "Rodrigo Ramirez Blog",
url: "http://localhost:8080/tests/configs/data/feed_test_rodrigoramirez.xml"
}
],
prohibitedWords: ["QPanel"],
showDescription: true
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+28
View File
@@ -0,0 +1,28 @@
/* Magic Mirror Test config for position setters module using the helloworld module
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
modules:
// Using exotic content. This is why don't accept go to JSON configuration file
(function () {
let positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
let modules = Array();
for (let idx in positions) {
modules.push({
module: "helloworld",
position: positions[idx],
config: {
text: "Text in " + positions[idx]
}
});
}
return modules;
})()
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,33 @@
/* Magic Mirror Test config current weather compliments
*
* By rejas https://github.com/rejas
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
modules: [
{
module: "compliments",
position: "top_bar",
config: {
compliments: {
snow: ["snow"]
},
updateInterval: 4000
}
},
{
module: "weather",
position: "bottom_bar",
config: {
location: "Munich",
apiKey: "fake key",
initialLoadDelay: 3000
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,25 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
location: "Munich",
apiKey: "fake key",
initialLoadDelay: 3000
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,29 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
location: "Munich",
apiKey: "fake key",
initialLoadDelay: 3000,
useBeaufort: false,
showWindDirectionAsArrow: true,
showSun: false,
showHumidity: true,
roundTemp: true,
degreeLabel: true
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,27 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
units: "imperial",
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
location: "Munich",
apiKey: "fake key",
initialLoadDelay: 3000,
decimalSymbol: ",",
showHumidity: true
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,27 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
type: "forecast",
location: "Munich",
apiKey: "fake key",
weatherEndpoint: "/forecast/daily",
initialLoadDelay: 3000
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,30 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
timeFormat: 12,
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
type: "forecast",
location: "Munich",
apiKey: "fake key",
weatherEndpoint: "/forecast/daily",
initialLoadDelay: 3000,
showPrecipitationAmount: true,
colored: true,
tableClass: "myTableClass"
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
@@ -0,0 +1,28 @@
/* Magic Mirror Test config default weather
*
* By rejas
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
units: "imperial",
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
type: "forecast",
location: "Munich",
apiKey: "fake key",
weatherEndpoint: "/forecast/daily",
initialLoadDelay: 3000,
decimalSymbol: "_"
}
}
]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+13
View File
@@ -0,0 +1,13 @@
/* Magic Mirror Test config sample ipWhitelist
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
ipWhitelist: ["x.x.x.x"]
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+13
View File
@@ -0,0 +1,13 @@
/* Magic Mirror Test config sample environment set port 8090
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8090
});
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+13
View File
@@ -0,0 +1,13 @@
/* Magic Mirror Test default config for modules
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
let config = {
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.10.1"]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
+32
View File
@@ -0,0 +1,32 @@
const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("Electron app environment", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/default.js");
helpers.getDocument(done);
});
afterAll(function () {
helpers.stopApplication();
});
it("get request from http://localhost:8080 should return 200", function (done) {
fetch("http://localhost:8080").then((res) => {
expect(res.status).toBe(200);
done();
});
});
it("get request from http://localhost:8080/nothing should return 404", function (done) {
fetch("http://localhost:8080/nothing").then((res) => {
expect(res.status).toBe(404);
done();
});
});
it("should show the title MagicMirror²", function () {
const elem = document.querySelector("title");
expect(elem).not.toBe(null);
expect(elem.textContent).toBe("MagicMirror²");
});
});
+31
View File
@@ -0,0 +1,31 @@
const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("All font files from roboto.css should be downloadable", function () {
const fontFiles = [];
// Statements below filters out all 'url' lines in the CSS file
const fileContent = require("fs").readFileSync(__dirname + "/../../fonts/roboto.css", "utf8");
const regex = /\burl\(['"]([^'"]+)['"]\)/g;
let match = regex.exec(fileContent);
while (match !== null) {
// Push 1st match group onto fontFiles stack
fontFiles.push(match[1]);
// Find the next one
match = regex.exec(fileContent);
}
beforeAll(function () {
helpers.startApplication("tests/configs/without_modules.js");
});
afterAll(function () {
helpers.stopApplication();
});
test.each(fontFiles)("should return 200 HTTP code for file '%s'", (fontFile, done) => {
const fontUrl = "http://localhost:8080/fonts/" + fontFile;
fetch(fontUrl).then((res) => {
expect(res.status).toBe(200);
done();
});
});
});
+32
View File
@@ -0,0 +1,32 @@
const jsdom = require("jsdom");
exports.startApplication = function (configFilename, exec) {
jest.resetModules();
if (global.app) {
global.app.stop();
}
// Set config sample for use in test
process.env.MM_CONFIG_FILE = configFilename;
if (exec) exec;
global.app = require("app.js");
global.app.start();
};
exports.stopApplication = function () {
if (global.app) {
global.app.stop();
}
};
exports.getDocument = function (callback, ms) {
const url = "http://" + (config.address || "localhost") + ":" + (config.port || "8080");
jsdom.JSDOM.fromURL(url, { resources: "usable", runScripts: "dangerously" }).then((dom) => {
dom.window.name = "jsdom";
dom.window.onload = function () {
global.document = dom.window.document;
setTimeout(() => {
callback();
}, ms);
};
});
};
+36
View File
@@ -0,0 +1,36 @@
const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("ipWhitelist directive configuration", function () {
describe("Set ipWhitelist without access", function () {
beforeAll(function () {
helpers.startApplication("tests/configs/noIpWhiteList.js");
});
afterAll(function () {
helpers.stopApplication();
});
it("should return 403", function (done) {
fetch("http://localhost:8080").then((res) => {
expect(res.status).toBe(403);
done();
});
});
});
describe("Set ipWhitelist []", function () {
beforeAll(function () {
helpers.startApplication("tests/configs/empty_ipWhiteList.js");
});
afterAll(function () {
helpers.stopApplication();
});
it("should return 200", function (done) {
fetch("http://localhost:8080").then((res) => {
expect(res.status).toBe(200);
done();
});
});
});
});
+21
View File
@@ -0,0 +1,21 @@
/**
* Suppresses errors concerning web server already shut down.
*
* @param {string} err The error message.
*/
function mockError(err) {
if (err.includes("ECONNREFUSED") || err.includes("ECONNRESET") || err.includes("socket hang up") || err.includes("exports is not defined")) {
jest.fn();
} else {
console.dir(err);
}
}
global.console = {
log: jest.fn(),
dir: console.dir,
error: mockError,
warn: console.warn,
info: jest.fn(),
debug: console.debug
};
+17
View File
@@ -0,0 +1,17 @@
const helpers = require("../global-setup");
describe("Alert module", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/alert/default.js");
helpers.getDocument(done, 1000);
});
afterAll(function () {
helpers.stopApplication();
});
it("should show the welcome message", function () {
const elem = document.querySelector(".ns-box .ns-box-inner .light.bright.small");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Welcome, start was successful!");
});
});
+71
View File
@@ -0,0 +1,71 @@
const helpers = require("../global-setup");
describe("Clock set to spanish language module", function () {
afterAll(function () {
helpers.stopApplication();
});
const testMatch = function (element, regex) {
const elem = document.querySelector(element);
expect(elem).not.toBe(null);
expect(elem.textContent).toMatch(regex);
};
describe("with default 24hr clock config", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/es/clock_24hr.js");
helpers.getDocument(done, 1000);
});
it("shows date with correct format", function () {
const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/;
testMatch(".clock .date", dateRegex);
});
it("shows time in 24hr format", function () {
const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/;
testMatch(".clock .time", timeRegex);
});
});
describe("with default 12hr clock config", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/es/clock_12hr.js");
helpers.getDocument(done, 1000);
});
it("shows date with correct format", function () {
const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/;
testMatch(".clock .date", dateRegex);
});
it("shows time in 12hr format", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/;
testMatch(".clock .time", timeRegex);
});
});
describe("with showPeriodUpper config enabled", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/es/clock_showPeriodUpper.js");
helpers.getDocument(done, 1000);
});
it("shows 12hr time with upper case AM/PM", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/;
testMatch(".clock .time", timeRegex);
});
});
describe("with showWeek config enabled", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/es/clock_showWeek.js");
helpers.getDocument(done, 1000);
});
it("shows week with correct format", function () {
const weekRegex = /^Semana [0-9]{1,2}$/;
testMatch(".clock .week", weekRegex);
});
});
});
+116
View File
@@ -0,0 +1,116 @@
const helpers = require("../global-setup");
const moment = require("moment");
describe("Clock module", function () {
afterAll(function () {
helpers.stopApplication();
});
const testMatch = function (element, regex) {
const elem = document.querySelector(element);
expect(elem).not.toBe(null);
expect(elem.textContent).toMatch(regex);
};
describe("with default 24hr clock config", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/clock_24hr.js");
helpers.getDocument(done, 1000);
});
it("should show the date in the correct format", function () {
const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
testMatch(".clock .date", dateRegex);
});
it("should show the time in 24hr format", function () {
const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/;
testMatch(".clock .time", timeRegex);
});
});
describe("with default 12hr clock config", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/clock_12hr.js");
helpers.getDocument(done, 1000);
});
it("should show the date in the correct format", function () {
const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
testMatch(".clock .date", dateRegex);
});
it("should show the time in 12hr format", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/;
testMatch(".clock .time", timeRegex);
});
});
describe("with showPeriodUpper config enabled", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/clock_showPeriodUpper.js");
helpers.getDocument(done, 1000);
});
it("should show 12hr time with upper case AM/PM", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/;
testMatch(".clock .time", timeRegex);
});
});
describe("with displaySeconds config disabled", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/clock_displaySeconds_false.js");
helpers.getDocument(done, 1000);
});
it("should show 12hr time without seconds am/pm", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/;
testMatch(".clock .time", timeRegex);
});
});
describe("with showTime config disabled", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/clock_showTime.js");
helpers.getDocument(done, 1000);
});
it("should show not show the time when digital clock is shown", function () {
const elem = document.querySelector(".clock .digital .time");
expect(elem).toBe(null);
});
});
describe("with showWeek config enabled", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/clock_showWeek.js");
helpers.getDocument(done, 1000);
});
it("should show the week in the correct format", function () {
const weekRegex = /^Week [0-9]{1,2}$/;
testMatch(".clock .week", weekRegex);
});
it("should show the week with the correct number of week of year", function () {
const currentWeekNumber = moment().week();
const weekToShow = "Week " + currentWeekNumber;
const elem = document.querySelector(".clock .week");
expect(elem).not.toBe(null);
expect(elem.textContent).toBe(weekToShow);
});
});
describe("with analog clock face enabled", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/clock_analog.js");
helpers.getDocument(done, 1000);
});
it("should show the analog clock face", () => {
const elem = document.querySelector(".clockCircle");
expect(elem).not.toBe(null);
});
});
});
+88
View File
@@ -0,0 +1,88 @@
const helpers = require("../global-setup");
/**
* move similar tests in function doTest
*
* @param {Array} complimentsArray The array of compliments.
*/
function doTest(complimentsArray) {
let elem = document.querySelector(".compliments");
expect(elem).not.toBe(null);
elem = document.querySelector(".module-content");
expect(elem).not.toBe(null);
expect(complimentsArray).toContain(elem.textContent);
}
describe("Compliments module", function () {
afterAll(function () {
helpers.stopApplication();
});
describe("parts of days", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js");
helpers.getDocument(done, 1000);
});
it("if Morning compliments for that part of day", function () {
const hour = new Date().getHours();
if (hour >= 3 && hour < 12) {
// if morning check
doTest(["Hi", "Good Morning", "Morning test"]);
}
});
it("if Afternoon show Compliments for that part of day", function () {
const hour = new Date().getHours();
if (hour >= 12 && hour < 17) {
// if afternoon check
doTest(["Hello", "Good Afternoon", "Afternoon test"]);
}
});
it("if Evening show Compliments for that part of day", function () {
const hour = new Date().getHours();
if (!(hour >= 3 && hour < 12) && !(hour >= 12 && hour < 17)) {
// if evening check
doTest(["Hello There", "Good Evening", "Evening test"]);
}
});
});
describe("Feature anytime in compliments module", function () {
describe("Set anytime and empty compliments for morning, evening and afternoon ", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/compliments/compliments_anytime.js");
helpers.getDocument(done, 1000);
});
it("Show anytime because if configure empty parts of day compliments and set anytime compliments", function () {
doTest(["Anytime here"]);
});
});
describe("Only anytime present in configuration compliments", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/compliments/compliments_only_anytime.js");
helpers.getDocument(done, 1000);
});
it("Show anytime compliments", function () {
doTest(["Anytime here"]);
});
});
});
describe("Feature date in compliments module", function () {
describe("Set date and empty compliments for anytime, morning, evening and afternoon", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/compliments/compliments_date.js");
helpers.getDocument(done, 1000);
});
it("Show happy new year compliment on new years day", function () {
doTest(["Happy new year!"]);
});
});
});
});
+33
View File
@@ -0,0 +1,33 @@
const helpers = require("../global-setup");
describe("Test helloworld module", function () {
afterAll(function () {
helpers.stopApplication();
});
describe("helloworld set config text", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/helloworld/helloworld.js");
helpers.getDocument(done, 1000);
});
it("Test message helloworld module", function () {
const elem = document.querySelector(".helloworld");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Test HelloWorld Module");
});
});
describe("helloworld default config text", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/helloworld/helloworld_default.js");
helpers.getDocument(done, 1000);
});
it("Test message helloworld module", function () {
const elem = document.querySelector(".helloworld");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Hello World!");
});
});
});
+63
View File
@@ -0,0 +1,63 @@
const helpers = require("../global-setup");
describe("Newsfeed module", function () {
afterAll(function () {
helpers.stopApplication();
});
describe("Default configuration", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/newsfeed/default.js");
helpers.getDocument(done, 3000);
});
it("should show the newsfeed title", function () {
const elem = document.querySelector(".newsfeed .newsfeed-source");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Rodrigo Ramirez Blog");
});
it("should show the newsfeed article", function () {
const elem = document.querySelector(".newsfeed .newsfeed-title");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("QPanel");
});
it("should NOT show the newsfeed description", () => {
const elem = document.querySelector(".newsfeed .newsfeed-desc");
expect(elem).toBe(null);
});
});
describe("Custom configuration", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/newsfeed/prohibited_words.js");
helpers.getDocument(done, 3000);
});
it("should not show articles with prohibited words", function () {
const elem = document.querySelector(".newsfeed .newsfeed-title");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Problema VirtualBox");
});
it("should show the newsfeed description", () => {
const elem = document.querySelector(".newsfeed .newsfeed-desc");
expect(elem).not.toBe(null);
expect(elem.textContent.length).not.toBe(0);
});
});
describe("Invalid configuration", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/newsfeed/incorrect_url.js");
helpers.getDocument(done, 3000);
});
it("should show malformed url warning", function () {
const elem = document.querySelector(".newsfeed .small");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Error in the Newsfeed module. Malformed url.");
});
});
});
+24
View File
@@ -0,0 +1,24 @@
const helpers = require("./global-setup");
describe("Display of modules", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/display.js");
helpers.getDocument(done);
});
afterAll(function () {
helpers.stopApplication();
});
it("should show the test header", function () {
const elem = document.querySelector("#module_0_helloworld .module-header");
expect(elem).not.toBe(null);
// textContent gibt hier lowercase zurück, das uppercase wird durch css realisiert, was daher nicht in textContent landet
expect(elem.textContent).toBe("test_header");
});
it("should show no header if no header text is specified", function () {
const elem = document.querySelector("#module_1_helloworld .module-header");
expect(elem).not.toBe(null);
expect(elem.textContent).toBe("undefined");
});
});
+22
View File
@@ -0,0 +1,22 @@
const helpers = require("./global-setup");
describe("Position of modules", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/positions.js");
helpers.getDocument(done, 1000);
});
afterAll(function () {
helpers.stopApplication();
});
const positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
for (const position of positions) {
const className = position.replace("_", ".");
it("should show text in " + position, function () {
const elem = document.querySelector("." + className);
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Text in " + position);
});
}
});
+36
View File
@@ -0,0 +1,36 @@
const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("port directive configuration", function () {
describe("Set port 8090", function () {
beforeAll(function () {
helpers.startApplication("tests/configs/port_8090.js");
});
afterAll(function () {
helpers.stopApplication();
});
it("should return 200", function (done) {
fetch("http://localhost:8090").then((res) => {
expect(res.status).toBe(200);
done();
});
});
});
describe("Set port 8100 on environment variable MM_PORT", function () {
beforeAll(function () {
helpers.startApplication("tests/configs/port_8090.js", (process.env.MM_PORT = 8100));
});
afterAll(function () {
helpers.stopApplication();
});
it("should return 200", function (done) {
fetch("http://localhost:8100").then((res) => {
expect(res.status).toBe(200);
done();
});
});
});
});
+227
View File
@@ -0,0 +1,227 @@
const fs = require("fs");
const path = require("path");
const translations = require("../../translations/translations.js");
const helmet = require("helmet");
const { JSDOM } = require("jsdom");
const express = require("express");
const sinon = require("sinon");
describe("Translations", function () {
let server;
beforeAll(function () {
const app = express();
app.use(helmet());
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
next();
});
app.use("/translations", express.static(path.join(__dirname, "..", "..", "translations")));
server = app.listen(3000);
});
afterAll(function () {
server.close();
});
it("should have a translation file in the specified path", function () {
for (let language in translations) {
const file = fs.statSync(translations[language]);
expect(file.isFile()).toBe(true);
}
});
describe("loadTranslations", () => {
let dom;
beforeEach(() => {
dom = new JSDOM(
`<script>var Translator = {}; var Log = {log: function(){}}; var config = {language: 'de'};</script>\
<script src="file://${path.join(__dirname, "..", "..", "js", "class.js")}"></script>\
<script src="file://${path.join(__dirname, "..", "..", "js", "module.js")}"></script>`,
{ runScripts: "dangerously", resources: "usable" }
);
});
it("should load translation file", (done) => {
dom.window.onload = async function () {
const { Translator, Module, config } = dom.window;
config.language = "en";
Translator.load = sinon.stub().callsFake((_m, _f, _fb, callback) => callback());
Module.register("name", { getTranslations: () => translations });
const MMM = Module.create("name");
const loaded = sinon.stub();
MMM.loadTranslations(loaded);
expect(loaded.callCount).toBe(1);
expect(Translator.load.args.length).toBe(1);
expect(Translator.load.calledWith(MMM, "translations/en.json", false, sinon.match.func)).toBe(true);
done();
};
});
it("should load translation + fallback file", (done) => {
dom.window.onload = async function () {
const { Translator, Module } = dom.window;
Translator.load = sinon.stub().callsFake((_m, _f, _fb, callback) => callback());
Module.register("name", { getTranslations: () => translations });
const MMM = Module.create("name");
const loaded = sinon.stub();
MMM.loadTranslations(loaded);
expect(loaded.callCount).toBe(1);
expect(Translator.load.args.length).toBe(2);
expect(Translator.load.calledWith(MMM, "translations/de.json", false, sinon.match.func)).toBe(true);
expect(Translator.load.calledWith(MMM, "translations/en.json", true, sinon.match.func)).toBe(true);
done();
};
});
it("should load translation fallback file", (done) => {
dom.window.onload = async function () {
const { Translator, Module, config } = dom.window;
config.language = "--";
Translator.load = sinon.stub().callsFake((_m, _f, _fb, callback) => callback());
Module.register("name", { getTranslations: () => translations });
const MMM = Module.create("name");
const loaded = sinon.stub();
MMM.loadTranslations(loaded);
expect(loaded.callCount).toBe(1);
expect(Translator.load.args.length).toBe(1);
expect(Translator.load.calledWith(MMM, "translations/en.json", true, sinon.match.func)).toBe(true);
done();
};
});
it("should load no file", (done) => {
dom.window.onload = async function () {
const { Translator, Module } = dom.window;
Translator.load = sinon.stub();
Module.register("name", {});
const MMM = Module.create("name");
const loaded = sinon.stub();
MMM.loadTranslations(loaded);
expect(loaded.callCount).toBe(1);
expect(Translator.load.callCount).toBe(0);
done();
};
});
});
const mmm = {
name: "TranslationTest",
file(file) {
return `http://localhost:3000/${file}`;
}
};
describe("Parsing language files through the Translator class", function () {
for (let language in translations) {
it(`should parse ${language}`, function (done) {
const dom = new JSDOM(
`<script>var translations = ${JSON.stringify(translations)}; var Log = {log: function(){}};</script>\
<script src="file://${path.join(__dirname, "..", "..", "js", "translator.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = function () {
const { Translator } = dom.window;
Translator.load(mmm, translations[language], false, function () {
expect(typeof Translator.translations[mmm.name]).toBe("object");
expect(Object.keys(Translator.translations[mmm.name]).length).toBeGreaterThanOrEqual(1);
done();
});
};
});
}
});
describe("Same keys", function () {
let base;
let missing = [];
beforeAll(function (done) {
const dom = new JSDOM(
`<script>var translations = ${JSON.stringify(translations)}; var Log = {log: function(){}};</script>\
<script src="file://${path.join(__dirname, "..", "..", "js", "translator.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = function () {
const { Translator } = dom.window;
Translator.load(mmm, translations.en, false, function () {
base = Object.keys(Translator.translations[mmm.name]).sort();
done();
});
};
});
afterAll(function () {
console.log(missing);
});
for (let language in translations) {
if (language === "en") {
continue;
}
describe(`Translation keys of ${language}`, function () {
let keys;
beforeAll(function (done) {
const dom = new JSDOM(
`<script>var translations = ${JSON.stringify(translations)}; var Log = {log: function(){}};</script>\
<script src="file://${path.join(__dirname, "..", "..", "js", "translator.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = function () {
const { Translator } = dom.window;
Translator.load(mmm, translations[language], false, function () {
keys = Object.keys(Translator.translations[mmm.name]).sort();
done();
});
};
});
it(`${language} keys should be in base`, function () {
keys.forEach(function (key) {
expect(base.indexOf(key)).toBeGreaterThanOrEqual(0);
});
});
it(`${language} should contain all base keys`, function () {
// TODO: when all translations are fixed, use
// expect(keys).toEqual(base);
// instead of the try-catch-block
try {
expect(keys).toEqual(base);
} catch (e) {
if (e.message.match(/expect.*toEqual/)) {
const diff = base.filter((key) => !keys.includes(key));
missing.push(`Missing Translations for language ${language}: ${diff}`);
} else {
throw e;
}
}
});
});
}
});
});
+35
View File
@@ -0,0 +1,35 @@
const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("Vendors", function () {
beforeAll(function () {
helpers.startApplication("tests/configs/default.js");
});
afterAll(function () {
helpers.stopApplication();
});
describe("Get list vendors", function () {
const vendors = require(__dirname + "/../../vendor/vendor.js");
Object.keys(vendors).forEach((vendor) => {
it(`should return 200 HTTP code for vendor "${vendor}"`, function (done) {
const urlVendor = "http://localhost:8080/vendor/" + vendors[vendor];
fetch(urlVendor).then((res) => {
expect(res.status).toBe(200);
done();
});
});
});
Object.keys(vendors).forEach((vendor) => {
it(`should return 404 HTTP code for vendor https://localhost/"${vendor}"`, function (done) {
const urlVendor = "http://localhost:8080/" + vendors[vendor];
fetch(urlVendor).then((res) => {
expect(res.status).toBe(404);
done();
});
});
});
});
});
+23
View File
@@ -0,0 +1,23 @@
const helpers = require("./global-setup");
describe("Check configuration without modules", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/without_modules.js");
helpers.getDocument(done, 1000);
});
afterAll(function () {
helpers.stopApplication();
});
it("Show the message MagicMirror title", function () {
const elem = document.querySelector("#module_1_helloworld .module-content");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Magic Mirror2");
});
it("Show the text Michael's website", function () {
const elem = document.querySelector("#module_5_helloworld .module-content");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("www.michaelteeuw.nl");
});
});
+53
View File
@@ -0,0 +1,53 @@
const helpers = require("./global-setup");
describe("Development console tests", function () {
helpers.setupTimeout(this);
let app = null;
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/env.js";
});
describe("Without 'dev' commandline argument", function () {
beforeAll(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterAll(function () {
return helpers.stopApplication(app);
});
it("should not open dev console when absent", async function () {
await app.client.waitUntilWindowLoaded();
return expect(await app.browserWindow.isDevToolsOpened()).toBe(false);
});
});
describe("With 'dev' commandline argument", function () {
beforeAll(function () {
return helpers
.startApplication({
args: ["js/electron.js", "dev"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterAll(function () {
return helpers.stopApplication(app);
});
it("should open dev console when provided", async function () {
expect(await app.client.getWindowCount()).toBe(2);
});
});
});
+40
View File
@@ -0,0 +1,40 @@
const helpers = require("./global-setup");
describe("Electron app environment", function () {
helpers.setupTimeout(this);
let app = null;
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/env.js";
});
beforeEach(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
it("should open a browserwindow", async function () {
await app.client.waitUntilWindowLoaded();
app.browserWindow.focus();
expect(await app.client.getWindowCount()).toBe(1);
expect(await app.browserWindow.isMinimized()).toBe(false);
expect(await app.browserWindow.isDevToolsOpened()).toBe(false);
expect(await app.browserWindow.isVisible()).toBe(true);
expect(await app.browserWindow.isFocused()).toBe(true);
const bounds = await app.browserWindow.getBounds();
expect(bounds.width).toBeGreaterThan(0);
expect(bounds.height).toBeGreaterThan(0);
expect(await app.browserWindow.getTitle()).toBe("MagicMirror²");
});
});
+53
View File
@@ -0,0 +1,53 @@
/*
* Magic Mirror Global Setup Test Suite
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
const Application = require("spectron").Application;
const assert = require("assert");
const path = require("path");
const EventEmitter = require("events");
exports.getElectronPath = function () {
let electronPath = path.join(__dirname, "..", "..", "node_modules", ".bin", "electron");
if (process.platform === "win32") {
electronPath += ".cmd";
}
return electronPath;
};
// Set timeout - if this is run as CI Job, increase timeout
exports.setupTimeout = function (test) {
if (process.env.CI) {
jest.setTimeout(30000);
} else {
jest.setTimeout(10000);
}
};
exports.startApplication = function (options) {
const emitter = new EventEmitter();
emitter.setMaxListeners(100);
options.path = exports.getElectronPath();
if (process.env.CI) {
options.startTimeout = 30000;
}
const app = new Application(options);
return app.start().then(function () {
assert.strictEqual(app.isRunning(), true);
return app;
});
};
exports.stopApplication = function (app) {
if (!app || !app.isRunning()) {
return;
}
return app.stop().then(function () {
assert.strictEqual(app.isRunning(), false);
});
};
+29
View File
@@ -0,0 +1,29 @@
const path = require("path");
const auth = require("express-basic-auth");
const express = require("express");
const app = express();
const basicAuth = auth({
realm: "MagicMirror Area restricted.",
users: { MagicMirror: "CallMeADog" }
});
app.use(basicAuth);
// Set available directories
const directories = ["/tests/configs"];
const rootPath = path.resolve(__dirname + "/../../../");
for (let directory of directories) {
app.use(directory, express.static(path.resolve(rootPath + directory)));
}
let server;
exports.listen = function () {
server = app.listen.apply(app, arguments);
};
exports.close = function (callback) {
server.close(callback);
};
+150
View File
@@ -0,0 +1,150 @@
const helpers = require("../global-setup");
const serverBasicAuth = require("./basic-auth.js");
describe("Calendar module", function () {
helpers.setupTimeout(this);
let app = null;
beforeEach(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
describe("Default configuration", function () {
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/default.js";
});
it("should show the default maximumEntries of 10", async () => {
await app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
const events = await app.client.$$(".calendar .event");
return expect(events.length).toBe(10);
});
it("should show the default calendar symbol in each event", async () => {
await app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
const icons = await app.client.$$(".calendar .event .fa-calendar");
return expect(icons.length).not.toBe(0);
});
});
describe("Custom configuration", function () {
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/custom.js";
});
it("should show the custom maximumEntries of 4", async () => {
await app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
const events = await app.client.$$(".calendar .event");
return expect(events.length).toBe(4);
});
it("should show the custom calendar symbol in each event", async () => {
await app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
const icons = await app.client.$$(".calendar .event .fa-birthday-cake");
return expect(icons.length).toBe(4);
});
it("should show two custom icons for repeating events", async () => {
await app.client.waitUntilTextExists(".calendar", "TestEventRepeat", 10000);
const icons = await app.client.$$(".calendar .event .fa-undo");
return expect(icons.length).toBe(2);
});
it("should show two custom icons for day events", async () => {
await app.client.waitUntilTextExists(".calendar", "TestEventDay", 10000);
const icons = await app.client.$$(".calendar .event .fa-calendar-day");
return expect(icons.length).toBe(2);
});
});
describe("Recurring event", function () {
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/recurring.js";
});
it("should show the recurring birthday event 6 times", async () => {
await app.client.waitUntilTextExists(".calendar", "Mar 25th", 10000);
const events = await app.client.$$(".calendar .event");
return expect(events.length).toBe(6);
});
});
describe("Changed port", function () {
beforeAll(function () {
serverBasicAuth.listen(8010);
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/changed-port.js";
});
afterAll(function (done) {
serverBasicAuth.close(done());
});
it("should return TestEvents", function () {
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
});
});
describe("Basic auth", function () {
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/basic-auth.js";
});
it("should return TestEvents", function () {
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
});
});
describe("Basic auth by default", function () {
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/auth-default.js";
});
it("should return TestEvents", function () {
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
});
});
describe("Basic auth backward compatibility configuration: DEPRECATED", function () {
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/old-basic-auth.js";
});
it("should return TestEvents", function () {
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
});
});
describe("Fail Basic auth", function () {
beforeAll(function () {
serverBasicAuth.listen(8020);
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/fail-basic-auth.js";
});
afterAll(function (done) {
serverBasicAuth.close(done());
});
it("should show Unauthorized error", function () {
return app.client.waitUntilTextExists(".calendar", "Error in the calendar module. Authorization failed", 10000);
});
});
});
+4
View File
@@ -0,0 +1,4 @@
const generateWeather = require("./weather_current");
const generateWeatherForecast = require("./weather_forecast");
module.exports = { generateWeather, generateWeatherForecast };
@@ -0,0 +1,64 @@
const _ = require("lodash");
/**
* @param {object} extendedData extra data to add to the default mock data
* @returns {string} mocked current weather data
*/
function generateWeather(extendedData = {}) {
return JSON.stringify(
_.merge(
{},
{
coord: {
lon: 11.58,
lat: 48.14
},
weather: [
{
id: 615,
main: "Snow",
description: "light rain and snow",
icon: "13d"
},
{
id: 500,
main: "Rain",
description: "light rain",
icon: "10d"
}
],
base: "stations",
main: {
temp: 1.49,
pressure: 1005,
humidity: 93.7,
temp_min: 1,
temp_max: 2
},
visibility: 7000,
wind: {
speed: 11.8,
deg: 250
},
clouds: {
all: 75
},
dt: 1547387400,
sys: {
type: 1,
id: 1267,
message: 0.0031,
country: "DE",
sunrise: 1547362817,
sunset: 1547394301
},
id: 2867714,
name: "Munich",
cod: 200
},
extendedData
)
);
}
module.exports = generateWeather;
@@ -0,0 +1,115 @@
const _ = require("lodash");
/**
* @param {object} extendedData extra data to add to the default mock data
* @returns {string} mocked forecast weather data
*/
function generateWeatherForecast(extendedData = {}) {
return JSON.stringify(
_.merge(
{},
{
city: {
id: 2867714,
name: "Munich",
coord: { lon: 11.5754, lat: 48.1371 },
country: "DE",
population: 1260391,
timezone: 7200
},
cod: "200",
message: 0.9653487,
cnt: 7,
list: [
{
dt: 1568372400,
sunrise: 1568350044,
sunset: 1568395948,
temp: { day: 24.44, min: 15.35, max: 24.44, night: 15.35, eve: 18, morn: 23.03 },
pressure: 1031.65,
humidity: 70,
weather: [{ id: 801, main: "Clouds", description: "few clouds", icon: "02d" }],
speed: 3.35,
deg: 314,
clouds: 21
},
{
dt: 1568458800,
sunrise: 1568436525,
sunset: 1568482223,
temp: { day: 20.81, min: 13.56, max: 21.02, night: 13.56, eve: 16.6, morn: 15.88 },
pressure: 1028.81,
humidity: 72,
weather: [{ id: 500, main: "Rain", description: "light rain", icon: "10d" }],
speed: 2.21,
deg: 81,
clouds: 100
},
{
dt: 1568545200,
sunrise: 1568523007,
sunset: 1568568497,
temp: { day: 22.65, min: 13.76, max: 22.88, night: 15.27, eve: 17.45, morn: 13.76 },
pressure: 1023.75,
humidity: 64,
weather: [{ id: 800, main: "Clear", description: "sky is clear", icon: "01d" }],
speed: 1.15,
deg: 7,
clouds: 0
},
{
dt: 1568631600,
sunrise: 1568609489,
sunset: 1568654771,
temp: { day: 23.45, min: 13.95, max: 23.45, night: 13.95, eve: 17.75, morn: 15.21 },
pressure: 1020.41,
humidity: 64,
weather: [{ id: 800, main: "Clear", description: "sky is clear", icon: "01d" }],
speed: 3.07,
deg: 298,
clouds: 7
},
{
dt: 1568718000,
sunrise: 1568695970,
sunset: 1568741045,
temp: { day: 20.55, min: 10.95, max: 20.55, night: 10.95, eve: 14.82, morn: 13.24 },
pressure: 1019.4,
humidity: 66,
weather: [{ id: 800, main: "Clear", description: "sky is clear", icon: "01d" }],
speed: 2.8,
deg: 333,
clouds: 2
},
{
dt: 1568804400,
sunrise: 1568782452,
sunset: 1568827319,
temp: { day: 18.15, min: 7.75, max: 18.15, night: 7.75, eve: 12.45, morn: 9.41 },
pressure: 1017.56,
humidity: 52,
weather: [{ id: 800, main: "Clear", description: "sky is clear", icon: "01d" }],
speed: 2.92,
deg: 34,
clouds: 0
},
{
dt: 1568890800,
sunrise: 1568868934,
sunset: 1568913593,
temp: { day: 14.85, min: 5.56, max: 15.05, night: 5.56, eve: 9.56, morn: 6.25 },
pressure: 1022.7,
humidity: 59,
weather: [{ id: 800, main: "Clear", description: "sky is clear", icon: "01d" }],
speed: 2.89,
deg: 51,
clouds: 1
}
]
},
extendedData
)
);
}
module.exports = generateWeatherForecast;
+309
View File
@@ -0,0 +1,309 @@
const fs = require("fs");
const moment = require("moment");
const path = require("path");
const wdajaxstub = require("webdriverajaxstub");
const helpers = require("../global-setup");
const { generateWeather, generateWeatherForecast } = require("./mocks");
describe("Weather module", function () {
let app;
helpers.setupTimeout(this);
/**
*
* @param {object} responses mocked data to be returned
* @returns {Promise} Resolved once the electron app is started
*/
async function setup(responses) {
app = await helpers.startApplication({
args: ["js/electron.js"],
waitTimeout: 100000
});
wdajaxstub.init(app.client, responses);
app.client.setupStub();
}
/**
*
* @param {string} element css selector
* @returns {Promise<Element>} Promise with the element once it is rendered
*/
async function getElement(element) {
return await app.client.$(element);
}
/**
* @param {string} element css selector
* @param {string} result Expected text in given selector
* @returns {Promise<boolean>} Promise with True if the text matches
*/
async function getText(element, result) {
const elem = await getElement(element);
return await elem.getText(element).then(function (text) {
expect(text.trim()).toBe(result);
});
}
afterEach(function () {
return helpers.stopApplication(app);
});
describe("Current weather", function () {
let template;
beforeAll(function () {
template = fs.readFileSync(path.join(__dirname, "..", "..", "..", "modules", "default", "weather", "current.njk"), "utf8");
});
describe("Default configuration", function () {
beforeAll(function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/weather/currentweather_default.js";
});
it("should render wind speed and wind direction", async function () {
const weather = generateWeather();
await setup({ template, data: weather });
return getText(".weather .normal.medium span:nth-child(2)", "6 WSW");
});
it("should render sunrise", async function () {
const sunrise = moment().startOf("day").unix();
const sunset = moment().startOf("day").unix();
const weather = generateWeather({ sys: { sunrise, sunset } });
await setup({ template, data: weather });
return getText(".weather .normal.medium span:nth-child(4)", "12:00 am");
});
it("should render sunset", async function () {
const sunrise = moment().startOf("day").unix();
const sunset = moment().endOf("day").unix();
const weather = generateWeather({ sys: { sunrise, sunset } });
await setup({ template, data: weather });
return getText(".weather .normal.medium span:nth-child(4)", "11:59 pm");
});
it("should render temperature with icon", async function () {
const weather = generateWeather();
await setup({ template, data: weather });
return getText(".weather .large.light span.bright", "1.5°");
});
it("should render feels like temperature", async function () {
const weather = generateWeather();
await setup({ template, data: weather });
return getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -5.6°");
});
});
describe("Compliments Integration", function () {
beforeAll(function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/weather/currentweather_compliments.js";
});
it("should render a compliment based on the current weather", async function () {
const weather = generateWeather();
await setup({ template, data: weather });
return app.client.waitUntilTextExists(".compliments .module-content span", "snow");
});
});
describe("Configuration Options", function () {
beforeAll(function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/weather/currentweather_options.js";
});
it("should render useBeaufort = false", async function () {
const weather = generateWeather();
await setup({ template, data: weather });
return getText(".weather .normal.medium span:nth-child(2)", "12");
});
it("should render showWindDirectionAsArrow = true", async function () {
const weather = generateWeather();
await setup({ template, data: weather });
const elem = await getElement(".weather .normal.medium sup i.fa-long-arrow-up");
return elem.getHTML(".weather .normal.medium sup i.fa-long-arrow-up").then(function (text) {
expect(text).toContain("transform:rotate(250deg);");
});
});
it("should render showHumidity = true", async function () {
const weather = generateWeather();
await setup({ template, data: weather });
return getText(".weather .normal.medium span:nth-child(3)", "93.7");
});
it("should render degreeLabel = true", async function () {
const weather = generateWeather();
await setup({ template, data: weather });
return (await getText(".weather .large.light span.bright", "1°C")) && (await getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -6°C"));
});
});
describe("Current weather units", function () {
beforeAll(function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/weather/currentweather_units.js";
});
it("should render imperial units", async function () {
const weather = generateWeather({
main: {
temp: (1.49 * 9) / 5 + 32,
temp_min: (1 * 9) / 5 + 32,
temp_max: (2 * 9) / 5 + 32
},
wind: {
speed: 11.8 * 2.23694
}
});
await setup({ template, data: weather });
return (await getText(".weather .normal.medium span:nth-child(2)", "6 WSW")) && (await getText(".weather .large.light span.bright", "34,7°")) && getText(".weather .normal.medium.feelslike span.dimmed", "Feels like 22,0°");
});
it("should render custom decimalSymbol = ','", async function () {
const weather = generateWeather({
main: {
temp: (1.49 * 9) / 5 + 32,
temp_min: (1 * 9) / 5 + 32,
temp_max: (2 * 9) / 5 + 32
},
wind: {
speed: 11.8 * 2.23694
}
});
await setup({ template, data: weather });
return (await getText(".weather .normal.medium span:nth-child(3)", "93,7")) && (await getText(".weather .large.light span.bright", "34,7°")) && getText(".weather .normal.medium.feelslike span.dimmed", "Feels like 22,0°");
});
});
});
describe("Weather Forecast", function () {
let template;
beforeAll(function () {
template = fs.readFileSync(path.join(__dirname, "..", "..", "..", "modules", "default", "weather", "forecast.njk"), "utf8");
});
describe("Default configuration", function () {
beforeAll(function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/weather/forecastweather_default.js";
});
it("should render days", async function () {
const weather = generateWeatherForecast();
await setup({ template, data: weather });
const days = ["Today", "Tomorrow", "Sun", "Mon", "Tue"];
for (const [index, day] of days.entries()) {
await getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`, day);
}
});
it("should render icons", async function () {
const weather = generateWeatherForecast();
await setup({ template, data: weather });
const icons = ["day-cloudy", "rain", "day-sunny", "day-sunny", "day-sunny"];
for (const [index, icon] of icons.entries()) {
await getElement(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(2) span.wi-${icon}`);
}
});
it("should render max temperatures", async function () {
const weather = generateWeatherForecast();
await setup({ template, data: weather });
const temperatures = ["24.4°", "21.0°", "22.9°", "23.4°", "20.6°"];
for (const [index, temp] of temperatures.entries()) {
await getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp);
}
});
it("should render min temperatures", async function () {
const weather = generateWeatherForecast();
await setup({ template, data: weather });
const temperatures = ["15.3°", "13.6°", "13.8°", "13.9°", "10.9°"];
for (const [index, temp] of temperatures.entries()) {
await getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(4)`, temp);
}
});
it("should render fading of rows", async function () {
const weather = generateWeatherForecast();
await setup({ template, data: weather });
const opacities = [1, 1, 0.8, 0.5333333333333333, 0.2666666666666667];
const elem = await getElement(".weather table.small");
for (const [index, opacity] of opacities.entries()) {
const html = await elem.getHTML(`.weather table.small tr:nth-child(${index + 1})`);
expect(html).toContain(`<tr style="opacity: ${opacity};">`);
}
});
});
describe("Configuration Options", function () {
beforeAll(function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/weather/forecastweather_options.js";
});
it("should render custom table class", async function () {
const weather = generateWeatherForecast();
await setup({ template, data: weather });
await getElement(".weather table.myTableClass");
});
it("should render colored rows", async function () {
const weather = generateWeatherForecast();
await setup({ template, data: weather });
const rows = await app.client.$$(".weather table.myTableClass tr.colored");
expect(rows.length).toBe(5);
});
});
describe("Forecast weather units", function () {
beforeAll(function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/weather/forecastweather_units.js";
});
it("should render custom decimalSymbol = '_'", async function () {
const weather = generateWeatherForecast();
await setup({ template, data: weather });
const temperatures = ["24_4°", "21_0°", "22_9°", "23_4°", "20_6°"];
for (const [index, temp] of temperatures.entries()) {
await getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp);
}
});
});
});
});
+33
View File
@@ -0,0 +1,33 @@
function plugin (wdInstance, requests) {
if (typeof wdInstance.addCommand !== "function") {
throw new Error("You can't use WebdriverAjaxStub with this version of WebdriverIO");
}
function stub({template, data}, done) {
window.XMLHttpRequest = function () {
this.open = function (method, url) {
this.method = method;
this.url = url;
};
this.send = function () {
this.status = 200;
this.readyState = 4;
const response = this.url.includes(".njk") ? template : data;
this.response = response;
this.responseText = response;
this.onreadystatechange();
};
return this;
};
done();
}
wdInstance.addCommand("setupStub", function() {
return wdInstance.executeAsync(stub, requests);
});
}
module.exports.init = plugin;
+108
View File
@@ -0,0 +1,108 @@
const path = require("path");
const { JSDOM } = require("jsdom");
describe("File js/class", function () {
describe("Test function cloneObject", function () {
let clone;
let dom;
beforeAll(function (done) {
dom = new JSDOM(
`<script>var Log = {log: function() {}};</script>\
<script src="file://${path.join(__dirname, "..", "..", "..", "js", "class.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = function () {
const { cloneObject } = dom.window;
clone = cloneObject;
done();
};
});
it("should clone object", function () {
const expected = { name: "Rodrigo", web: "https://rodrigoramirez.com", project: "MagicMirror" };
const obj = clone(expected);
expect(obj).toEqual(expected);
expect(expected === obj).toBe(false);
});
it("should clone array", function () {
const expected = [1, null, undefined, "TEST"];
const obj = clone(expected);
expect(obj).toEqual(expected);
expect(expected === obj).toBe(false);
});
it("should clone number", function () {
let expected = 1;
let obj = clone(expected);
expect(obj).toBe(expected);
expected = 1.23;
obj = clone(expected);
expect(obj).toBe(expected);
});
it("should clone string", function () {
const expected = "Perfect stranger";
const obj = clone(expected);
expect(obj).toBe(expected);
});
it("should clone undefined", function () {
const expected = undefined;
const obj = clone(expected);
expect(obj).toBe(expected);
});
it("should clone null", function () {
const expected = null;
const obj = clone(expected);
expect(obj).toBe(expected);
});
it("should clone nested object", function () {
const expected = {
name: "fewieden",
link: "https://github.com/fewieden",
versions: ["2.0", "2.1", "2.2"],
answerForAllQuestions: 42,
properties: {
items: [{ foo: "bar" }, { lorem: "ipsum" }],
invalid: undefined,
nothing: null
}
};
const obj = clone(expected);
expect(obj).toEqual(expected);
expect(expected === obj).toBe(false);
expect(expected.versions === obj.versions).toBe(false);
expect(expected.properties === obj.properties).toBe(false);
expect(expected.properties.items === obj.properties.items).toBe(false);
expect(expected.properties.items[0] === obj.properties.items[0]).toBe(false);
expect(expected.properties.items[1] === obj.properties.items[1]).toBe(false);
});
describe("Test lockstring code", function () {
let log;
beforeAll(function () {
log = dom.window.Log.log;
dom.window.Log.log = function cmp(str) {
expect(str).toBe("lockStrings");
};
});
afterAll(function () {
dom.window.Log.log = log;
});
it("should clone object and log lockStrings", function () {
const expected = { name: "Module", lockStrings: "stringLock" };
const obj = clone(expected);
expect(obj).toEqual(expected);
expect(expected === obj).toBe(false);
});
});
});
});
+15
View File
@@ -0,0 +1,15 @@
const deprecated = require("../../../js/deprecated");
describe("Deprecated", function () {
it("should be an object", function () {
expect(typeof deprecated).toBe("object");
});
it("should contain configs array with deprecated options as strings", function () {
expect(Array.isArray(["deprecated.configs"])).toBe(true);
for (let option of deprecated.configs) {
expect(typeof option).toBe("string");
}
expect(deprecated.configs).toEqual(expect.arrayContaining(["kioskmode"]));
});
});
+288
View File
@@ -0,0 +1,288 @@
const path = require("path");
const helmet = require("helmet");
const { JSDOM } = require("jsdom");
const express = require("express");
const sockets = new Set();
describe("Translator", function () {
let server;
beforeAll(function () {
const app = express();
app.use(helmet());
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
next();
});
app.use("/translations", express.static(path.join(__dirname, "..", "..", "..", "tests", "configs", "data")));
server = app.listen(3000);
server.on("connection", (socket) => {
sockets.add(socket);
});
});
afterAll(function () {
for (const socket of sockets) {
socket.destroy();
sockets.delete(socket);
}
server.close();
});
describe("translate", function () {
const translations = {
"MMM-Module": {
Hello: "Hallo",
"Hello {username}": "Hallo {username}"
}
};
const coreTranslations = {
Hello: "XXX",
"Hello {username}": "XXX",
FOO: "Foo",
"BAR {something}": "Bar {something}"
};
const translationsFallback = {
"MMM-Module": {
Hello: "XXX",
"Hello {username}": "XXX",
FOO: "XXX",
"BAR {something}": "XXX",
"A key": "A translation"
}
};
const coreTranslationsFallback = {
FOO: "XXX",
"BAR {something}": "XXX",
Hello: "XXX",
"Hello {username}": "XXX",
"A key": "XXX",
Fallback: "core fallback"
};
/**
* @param {object} Translator the global Translator object
*/
function setTranslations(Translator) {
Translator.translations = translations;
Translator.coreTranslations = coreTranslations;
Translator.translationsFallback = translationsFallback;
Translator.coreTranslationsFallback = coreTranslationsFallback;
}
it("should return custom module translation", function (done) {
const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
dom.window.onload = function () {
const { Translator } = dom.window;
setTranslations(Translator);
let translation = Translator.translate({ name: "MMM-Module" }, "Hello");
expect(translation).toBe("Hallo");
translation = Translator.translate({ name: "MMM-Module" }, "Hello {username}", { username: "fewieden" });
expect(translation).toBe("Hallo fewieden");
done();
};
});
it("should return core translation", function (done) {
const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
dom.window.onload = function () {
const { Translator } = dom.window;
setTranslations(Translator);
let translation = Translator.translate({ name: "MMM-Module" }, "FOO");
expect(translation).toBe("Foo");
translation = Translator.translate({ name: "MMM-Module" }, "BAR {something}", { something: "Lorem Ipsum" });
expect(translation).toBe("Bar Lorem Ipsum");
done();
};
});
it("should return custom module translation fallback", function (done) {
const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
dom.window.onload = function () {
const { Translator } = dom.window;
setTranslations(Translator);
const translation = Translator.translate({ name: "MMM-Module" }, "A key");
expect(translation).toBe("A translation");
done();
};
});
it("should return core translation fallback", function (done) {
const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
dom.window.onload = function () {
const { Translator } = dom.window;
setTranslations(Translator);
const translation = Translator.translate({ name: "MMM-Module" }, "Fallback");
expect(translation).toBe("core fallback");
done();
};
});
it("should return translation with placeholder for missing variables", function (done) {
const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
dom.window.onload = function () {
const { Translator } = dom.window;
setTranslations(Translator);
const translation = Translator.translate({ name: "MMM-Module" }, "Hello {username}");
expect(translation).toBe("Hallo {username}");
done();
};
});
it("should return key if no translation was found", function (done) {
const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
dom.window.onload = function () {
const { Translator } = dom.window;
setTranslations(Translator);
const translation = Translator.translate({ name: "MMM-Module" }, "MISSING");
expect(translation).toBe("MISSING");
done();
};
});
});
describe("load", function () {
const mmm = {
name: "TranslationTest",
file(file) {
return `http://localhost:3000/translations/${file}`;
}
};
it("should load translations", function (done) {
const dom = new JSDOM(`<script>var Log = {log: function(){}};</script><script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
dom.window.onload = function () {
const { Translator } = dom.window;
const file = "TranslationTest.json";
Translator.load(mmm, file, false, function () {
const json = require(path.join(__dirname, "..", "..", "..", "tests", "configs", "data", file));
expect(Translator.translations[mmm.name]).toEqual(json);
done();
});
};
});
it("should load translation fallbacks", function (done) {
const dom = new JSDOM(`<script>var Log = {log: function(){}};</script><script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
dom.window.onload = function () {
const { Translator } = dom.window;
const file = "TranslationTest.json";
Translator.load(mmm, file, true, function () {
const json = require(path.join(__dirname, "..", "..", "..", "tests", "configs", "data", file));
expect(Translator.translationsFallback[mmm.name]).toEqual(json);
done();
});
};
});
it("should not load translations, if module fallback exists", function (done) {
const dom = new JSDOM(`<script>var Log = {log: function(){}};</script><script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
dom.window.onload = function () {
const { Translator, XMLHttpRequest } = dom.window;
const file = "TranslationTest.json";
XMLHttpRequest.prototype.send = function () {
throw "Shouldn't load files";
};
Translator.translationsFallback[mmm.name] = {
Hello: "Hallo"
};
Translator.load(mmm, file, false, function () {
expect(Translator.translations[mmm.name]).toBe(undefined);
expect(Translator.translationsFallback[mmm.name]).toEqual({
Hello: "Hallo"
});
done();
});
};
});
});
describe("loadCoreTranslations", function () {
it("should load core translations and fallback", function (done) {
const dom = new JSDOM(
`<script>var translations = {en: "http://localhost:3000/translations/en.json"}; var Log = {log: function(){}};</script>\
<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = function () {
const { Translator } = dom.window;
Translator.loadCoreTranslations("en");
const en = require(path.join(__dirname, "..", "..", "..", "tests", "configs", "data", "en.json"));
setTimeout(function () {
expect(Translator.coreTranslations).toEqual(en);
expect(Translator.coreTranslationsFallback).toEqual(en);
done();
}, 500);
};
});
it("should load core fallback if language cannot be found", function (done) {
const dom = new JSDOM(
`<script>var translations = {en: "http://localhost:3000/translations/en.json"}; var Log = {log: function(){}};</script>\
<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = function () {
const { Translator } = dom.window;
Translator.loadCoreTranslations("MISSINGLANG");
const en = require(path.join(__dirname, "..", "..", "..", "tests", "configs", "data", "en.json"));
setTimeout(function () {
expect(Translator.coreTranslations).toEqual({});
expect(Translator.coreTranslationsFallback).toEqual(en);
done();
}, 500);
};
});
});
describe("loadCoreTranslationsFallback", function () {
it("should load core translations fallback", function (done) {
const dom = new JSDOM(
`<script>var translations = {en: "http://localhost:3000/translations/en.json"}; var Log = {log: function(){}};</script>\
<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = function () {
const { Translator } = dom.window;
Translator.loadCoreTranslationsFallback();
const en = require(path.join(__dirname, "..", "..", "..", "tests", "configs", "data", "en.json"));
setTimeout(function () {
expect(Translator.coreTranslationsFallback).toEqual(en);
done();
}, 500);
};
});
it("should load core fallback if language cannot be found", function (done) {
const dom = new JSDOM(
`<script>var translations = {}; var Log = {log: function(){}};</script>\
<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = function () {
const { Translator } = dom.window;
Translator.loadCoreTranslations();
setTimeout(function () {
expect(Translator.coreTranslationsFallback).toEqual({});
done();
}, 500);
};
});
});
});
+38
View File
@@ -0,0 +1,38 @@
const Utils = require("../../../js/utils.js");
const colors = require("colors/safe");
describe("Utils", function () {
describe("colors", function () {
const colorsEnabled = colors.enabled;
afterEach(function () {
colors.enabled = colorsEnabled;
});
it("should have info, warn and error properties", function () {
expect(Utils.colors).toHaveProperty("info");
expect(Utils.colors).toHaveProperty("warn");
expect(Utils.colors).toHaveProperty("error");
});
it("properties should be functions", function () {
expect(typeof Utils.colors.info).toBe("function");
expect(typeof Utils.colors.warn).toBe("function");
expect(typeof Utils.colors.error).toBe("function");
});
it("should print colored message in supported consoles", function () {
colors.enabled = true;
expect(Utils.colors.info("some informations")).toBe("\u001b[34msome informations\u001b[39m");
expect(Utils.colors.warn("a warning")).toBe("\u001b[33ma warning\u001b[39m");
expect(Utils.colors.error("ERROR!")).toBe("\u001b[31mERROR!\u001b[39m");
});
it("should print message in unsupported consoles", function () {
colors.enabled = false;
expect(Utils.colors.info("some informations")).toBe("some informations");
expect(Utils.colors.warn("a warning")).toBe("a warning");
expect(Utils.colors.error("ERROR!")).toBe("ERROR!");
});
});
});
+128
View File
@@ -0,0 +1,128 @@
global.moment = require("moment");
describe("Functions into modules/default/calendar/calendar.js", function () {
// Fake for use by calendar.js
Module = {};
Module.definitions = {};
Module.register = function (name, moduleDefinition) {
Module.definitions[name] = moduleDefinition;
};
beforeAll(function () {
// load calendar.js
require("../../../modules/default/calendar/calendar.js");
});
describe("capFirst", function () {
const words = {
rodrigo: "Rodrigo",
"123m": "123m",
"magic mirror": "Magic mirror",
",a": ",a",
ñandú: "Ñandú"
};
Object.keys(words).forEach((word) => {
it(`for '${word}' should return '${words[word]}'`, function () {
expect(Module.definitions.calendar.capFirst(word)).toBe(words[word]);
});
});
});
describe("getLocaleSpecification", function () {
it("should return a valid moment.LocaleSpecification for a 12-hour format", function () {
expect(Module.definitions.calendar.getLocaleSpecification(12)).toEqual({ longDateFormat: { LT: "h:mm A" } });
});
it("should return a valid moment.LocaleSpecification for a 24-hour format", function () {
expect(Module.definitions.calendar.getLocaleSpecification(24)).toEqual({ longDateFormat: { LT: "HH:mm" } });
});
it("should return the current system locale when called without timeFormat number", function () {
expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: moment.localeData().longDateFormat("LT") } });
});
it("should return a 12-hour longDateFormat when using the 'en' locale", function () {
const localeBackup = moment.locale();
moment.locale("en");
expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "h:mm A" } });
moment.locale(localeBackup);
});
it("should return a 12-hour longDateFormat when using the 'au' locale", function () {
const localeBackup = moment.locale();
moment.locale("au");
expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "h:mm A" } });
moment.locale(localeBackup);
});
it("should return a 12-hour longDateFormat when using the 'eg' locale", function () {
const localeBackup = moment.locale();
moment.locale("eg");
expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "h:mm A" } });
moment.locale(localeBackup);
});
it("should return a 24-hour longDateFormat when using the 'nl' locale", function () {
const localeBackup = moment.locale();
moment.locale("nl");
expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "HH:mm" } });
moment.locale(localeBackup);
});
it("should return a 24-hour longDateFormat when using the 'fr' locale", function () {
const localeBackup = moment.locale();
moment.locale("fr");
expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "HH:mm" } });
moment.locale(localeBackup);
});
it("should return a 24-hour longDateFormat when using the 'uk' locale", function () {
const localeBackup = moment.locale();
moment.locale("uk");
expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "HH:mm" } });
moment.locale(localeBackup);
});
});
describe("shorten", function () {
const strings = {
" String with whitespace at the beginning that needs trimming": { length: 16, return: "String with whit&hellip;" },
"long string that needs shortening": { length: 16, return: "long string that&hellip;" },
"short string": { length: 16, return: "short string" },
"long string with no maxLength defined": { return: "long string with no maxLength defined" }
};
Object.keys(strings).forEach((string) => {
it(`for '${string}' should return '${strings[string].return}'`, function () {
expect(Module.definitions.calendar.shorten(string, strings[string].length)).toBe(strings[string].return);
});
});
it("should return an empty string if shorten is called with a non-string", function () {
expect(Module.definitions.calendar.shorten(100)).toBe("");
});
it("should not shorten the string if shorten is called with a non-number maxLength", function () {
expect(Module.definitions.calendar.shorten("This is a test string", "This is not a number")).toBe("This is a test string");
});
it("should wrap the string instead of shorten it if shorten is called with wrapEvents = true (with maxLength defined as 20)", function () {
expect(Module.definitions.calendar.shorten("This is a wrapEvent test. Should wrap the string instead of shorten it if called with wrapEvent = true", 20, true)).toBe(
"This is a <br>wrapEvent test. Should wrap <br>the string instead of <br>shorten it if called with <br>wrapEvent = true"
);
});
it("should wrap the string instead of shorten it if shorten is called with wrapEvents = true (without maxLength defined, default 25)", function () {
expect(Module.definitions.calendar.shorten("This is a wrapEvent test. Should wrap the string instead of shorten it if called with wrapEvent = true", undefined, true)).toBe(
"This is a wrapEvent <br>test. Should wrap the string <br>instead of shorten it if called <br>with wrapEvent = true"
);
});
it("should wrap and shorten the string in the second line if called with wrapEvents = true and maxTitleLines = 2", function () {
expect(Module.definitions.calendar.shorten("This is a wrapEvent and maxTitleLines test. Should wrap and shorten the string in the second line if called with wrapEvents = true and maxTitleLines = 2", undefined, true, 2)).toBe(
"This is a wrapEvent and <br>maxTitleLines test. Should wrap and &hellip;"
);
});
});
});
+31
View File
@@ -0,0 +1,31 @@
const path = require("path");
const { JSDOM } = require("jsdom");
describe("Test function cmpVersions in js/module.js", function () {
let cmp;
beforeAll(function (done) {
const dom = new JSDOM(
`<script>var Class = {extend: function() { return {}; }};</script>\
<script src="file://${path.join(__dirname, "..", "..", "..", "js", "module.js")}">`,
{ runScripts: "dangerously", resources: "usable" }
);
dom.window.onload = function () {
const { cmpVersions } = dom.window;
cmp = cmpVersions;
done();
};
});
it("should return -1 when comparing 2.1 to 2.2", function () {
expect(cmp("2.1", "2.2")).toBe(-1);
});
it("should be return 0 when comparing 2.2 to 2.2", function () {
expect(cmp("2.2", "2.2")).toBe(0);
});
it("should be return 1 when comparing 1.1 to 1.0", function () {
expect(cmp("1.1", "1.0")).toBe(1);
});
});
@@ -0,0 +1,66 @@
/* eslint no-multi-spaces: 0 */
describe("Functions module currentweather", function () {
// Fake for use by currentweather.js
Module = {};
config = {};
Module.definitions = {};
Module.register = function (name, moduleDefinition) {
Module.definitions[name] = moduleDefinition;
};
beforeAll(function () {
require("../../../modules/default/currentweather/currentweather.js");
Module.definitions.currentweather.config = {};
});
describe("roundValue", function () {
describe("this.config.roundTemp is true", function () {
beforeAll(function () {
Module.definitions.currentweather.config.roundTemp = true;
});
const values = [
// index 0 value
// index 1 expect
[1, "1"],
[1.0, "1"],
[1.02, "1"],
[10.12, "10"],
[2.0, "2"],
["2.12", "2"],
[10.1, "10"]
];
values.forEach((value) => {
it(`for ${value[0]} should be return ${value[1]}`, function () {
expect(Module.definitions.currentweather.roundValue(value[0])).toBe(value[1]);
});
});
});
describe("this.config.roundTemp is false", function () {
beforeAll(function () {
Module.definitions.currentweather.config.roundTemp = false;
});
const values = [
// index 0 value
// index 1 expect
[1, "1.0"],
[1.0, "1.0"],
[1.02, "1.0"],
[10.12, "10.1"],
[2.0, "2.0"],
["2.12", "2.1"],
[10.1, "10.1"],
[10.1, "10.1"]
];
values.forEach((value) => {
it(`for ${value[0]} should be return ${value[1]}`, function () {
expect(Module.definitions.currentweather.roundValue(value[0])).toBe(value[1]);
});
});
});
});
});
+15
View File
@@ -0,0 +1,15 @@
describe("Functions into modules/default/newsfeed/newsfeed.js", function () {
// Fake for use by newsletter.js
Module = {};
Module.definitions = {};
Module.register = function (name, moduleDefinition) {
Module.definitions[name] = moduleDefinition;
};
beforeAll(function () {
// load newsfeed.js
require("../../../modules/default/newsfeed/newsfeed.js");
});
test.skip("skip", () => {});
});
@@ -0,0 +1,126 @@
const path = require("path");
const git_Helper = require("../../../modules/default/updatenotification/git_helper.js");
const gitHelper = new git_Helper.gitHelper();
gitHelper.add("default");
const test1 = {
module: "test1",
folder: "",
res: {
stdout: "## master...origin/master [behind 8]",
stderr: ""
},
gitInfo: {
module: "default",
// commits behind:
behind: 0,
// branch name:
current: "develop",
// current hash:
hash: "",
// remote branch:
tracking: "",
isBehindInStatus: false
}
};
const test2 = {
module: "test2",
folder: "",
res: {
stdout: "## develop...origin/develop",
stderr: ""
}
};
const test3 = {
module: "test3",
folder: "",
res: {
stdout: "",
stderr: "error"
},
gitInfo: {
module: "default",
// commits behind:
behind: 2,
// branch name:
current: "develop",
// current hash:
hash: "",
// remote branch:
tracking: "",
isBehindInStatus: true
}
};
const test4 = {
module: "default",
folder: path.join(__dirname, "../../.."),
res: {
stdout: "",
stderr: " e40ddd4..06389e3 develop -> origin/develop"
},
gitInfo: {
module: "default",
// commits behind:
behind: 0,
// branch name:
current: "develop",
// current hash:
hash: "",
// remote branch:
tracking: "",
isBehindInStatus: false
}
};
describe("Updatenotification", function () {
it("should return valid output for git status", async function () {
const arr = await gitHelper.getStatus();
expect(arr.length).toBe(1);
const gitInfo = arr[0];
expect(gitInfo.current).not.toBe("");
expect(gitInfo.hash).not.toBe("");
}, 15000);
it("should return behind=8 for test1", async function () {
const gitInfo = await gitHelper.getStatusInfo(test1);
expect(gitInfo.behind).toBe(8);
expect(gitInfo.isBehindInStatus).toBe(true);
});
it("should return behind=0 for test2", async function () {
const gitInfo = await gitHelper.getStatusInfo(test2);
expect(gitInfo.behind).toBe(0);
expect(gitInfo.isBehindInStatus).toBe(false);
});
it("should return empty status object for test3", async function () {
const gitInfo = await gitHelper.getStatusInfo(test3);
expect(gitInfo).toBe(undefined);
});
it("should return empty repo object for test2", async function () {
// no gitInfo provided in res, so returns undefined
const gitInfo = await gitHelper.getRepoInfo(test2);
expect(gitInfo).toBe(undefined);
});
it("should return empty repo object for test1", async function () {
// no regex match for refs in empty string, so returns undefined
const gitInfo = await gitHelper.getRepoInfo(test1);
expect(gitInfo).toBe(undefined);
});
it("should return empty repo object for test4", async function () {
// git ref list throws error, so returns undefined
const gitInfo = await gitHelper.getRepoInfo(test4);
expect(gitInfo).toBe(undefined);
});
it("should return behind=2 for test3", async function () {
const gitInfo = await gitHelper.getRepoInfo(test3);
expect(gitInfo.behind).toBe(2);
});
});
@@ -0,0 +1,31 @@
const WeatherObject = require("../../../modules/default/weather/weatherobject.js");
global.moment = require("moment-timezone");
global.SunCalc = require("suncalc");
describe("WeatherObject", function () {
let originalTimeZone;
let weatherobject;
beforeAll(function () {
originalTimeZone = moment.tz.guess();
moment.tz.setDefault("Africa/Dar_es_Salaam");
weatherobject = new WeatherObject("metric", "metric", "metric", true);
});
it("should return true for daytime at noon", function () {
weatherobject.date = moment(12, "HH");
weatherobject.updateSunTime(-6.774877582342688, 37.63345667023327);
expect(weatherobject.isDayTime()).toBe(true);
});
it("should return false for daytime at midnight", function () {
weatherobject.date = moment(0, "HH");
weatherobject.updateSunTime(-6.774877582342688, 37.63345667023327);
expect(weatherobject.isDayTime()).toBe(false);
});
afterAll(function () {
moment.tz.setDefault(originalTimeZone);
});
});
@@ -0,0 +1,116 @@
/* eslint no-multi-spaces: 0 */
const moment = require("moment-timezone");
const data = require("../../configs/data/weatherforecast_data.json");
describe("Functions module weatherforecast", function () {
beforeAll(function () {
Module = {};
config = {};
Module.definitions = {};
Module.register = function (name, moduleDefinition) {
Module.definitions[name] = moduleDefinition;
};
require("../../../modules/default/weatherforecast/weatherforecast.js");
Module.definitions.weatherforecast.config = {};
});
describe("roundValue", function () {
describe("this.config.roundTemp is true", function () {
beforeAll(function () {
Module.definitions.weatherforecast.config.roundTemp = true;
});
const values = [
// index 0 value
// index 1 expect
[1, "1"],
[1.0, "1"],
[1.02, "1"],
[10.12, "10"],
[2.0, "2"],
["2.12", "2"],
[10.1, "10"]
];
values.forEach((value) => {
it(`for ${value[0]} should be return ${value[1]}`, function () {
expect(Module.definitions.weatherforecast.roundValue(value[0])).toBe(value[1]);
});
});
});
describe("this.config.roundTemp is false", function () {
beforeAll(function () {
Module.definitions.weatherforecast.config.roundTemp = false;
});
const values = [
// index 0 value
// index 1 expect
[1, "1.0"],
[1.0, "1.0"],
[1.02, "1.0"],
[10.12, "10.1"],
[2.0, "2.0"],
["2.12", "2.1"],
[10.1, "10.1"],
[10.1, "10.1"]
];
values.forEach((value) => {
it(`for ${value[0]} should be return ${value[1]}`, function () {
expect(Module.definitions.weatherforecast.roundValue(value[0])).toBe(value[1]);
});
});
});
});
describe("forecastIcons", function () {
Log = {
error: function () {}
};
let originalLocale;
let originalTimeZone;
beforeAll(function () {
originalLocale = moment.locale();
originalTimeZone = moment.tz.guess();
moment.locale("hi");
moment.tz.setDefault("Europe/Warsaw");
});
describe("forecastIcons sunset specified", function () {
beforeAll(function () {
Module.definitions.weatherforecast.Log = {};
Module.definitions.weatherforecast.forecast = [];
Module.definitions.weatherforecast.show = Module.definitions.weatherforecast.updateDom = function () {};
Module.definitions.weatherforecast.config = Module.definitions.weatherforecast.defaults;
});
it(`returns correct icons with sunset time`, function () {
Module.definitions.weatherforecast.processWeather(data.withSunset, moment);
let forecastData = Module.definitions.weatherforecast.forecast;
expect(forecastData.length).toBe(4);
expect(forecastData[2].icon).toBe("wi-rain");
});
});
describe("forecastIcons sunset not specified", function () {
beforeAll(function () {
Module.definitions.weatherforecast.forecast = [];
});
it(`returns correct icons with out sunset time`, function () {
Module.definitions.weatherforecast.processWeather(data.withoutSunset, moment);
let forecastData = Module.definitions.weatherforecast.forecast;
expect(forecastData.length).toBe(4);
expect(forecastData[2].icon).toBe("wi-rain");
});
});
afterAll(function () {
moment.locale(originalLocale);
moment.tz.setDefault(originalTimeZone);
});
});
});
@@ -0,0 +1,14 @@
const fs = require("fs");
const path = require("path");
const root_path = path.join(__dirname, "../../..");
describe("Default modules set in modules/default/defaultmodules.js", function () {
const expectedDefaultModules = require("../../../modules/default/defaultmodules");
for (const defaultModule of expectedDefaultModules) {
it(`contains a folder for modules/default/${defaultModule}"`, function () {
expect(fs.existsSync(path.join(root_path, "modules/default", defaultModule))).toBe(true);
});
}
});
+28
View File
@@ -0,0 +1,28 @@
const fs = require("fs");
const path = require("path");
const root_path = path.join(__dirname, "../../..");
const version = require(`${__dirname}/../../../package.json`).version;
describe("'global.root_path' set in js/app.js", function () {
const expectedSubPaths = ["modules", "serveronly", "js", "js/app.js", "js/main.js", "js/electron.js", "config"];
expectedSubPaths.forEach((subpath) => {
it(`contains a file/folder "${subpath}"`, function () {
expect(fs.existsSync(path.join(root_path, subpath))).toBe(true);
});
});
it("should not modify global.root_path for testing", function () {
expect(global.root_path).toBe(undefined);
});
it("should not modify global.version for testing", function () {
expect(global.version).toBe(undefined);
});
it("should expect the global.version equals package.json file", function () {
const versionPackage = JSON.parse(fs.readFileSync("package.json", "utf8")).version;
expect(version).toBe(versionPackage);
});
});