diff --git a/MANIFEST.in b/MANIFEST.in index 94c8037..2f923a5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,5 @@ recursive-include vasl_templates/webapp/config *.* recursive-include vasl_templates/webapp/data *.* recursive-include vasl_templates/webapp/static *.* recursive-include vasl_templates/webapp/templates *.* + +recursive-include vasl_templates/webapp/tests/fixtures *.* diff --git a/setup.py b/setup.py index 37ba461..f41083d 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ setup( # Linux: mesa-libGL-devel ; @"C Development Tools and Libraries" # nb: WebEngine seems to be broken in 5.10.1 :-/ "PyQT5==5.10.0", + "click==6.7", ], extras_require = { "dev": [ diff --git a/vasl_templates/main.py b/vasl_templates/main.py index e889bac..2f1a69d 100755 --- a/vasl_templates/main.py +++ b/vasl_templates/main.py @@ -2,6 +2,8 @@ """ Main entry point for the application. """ import sys +import os +import os.path import threading import traceback import logging @@ -9,9 +11,11 @@ import urllib.request from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import Qt +import click from vasl_templates.main_window import MainWindow from vasl_templates.webapp import app as webapp +from vasl_templates.webapp import generate # --------------------------------------------------------------------- @@ -34,9 +38,22 @@ class LoggerProxy: # --------------------------------------------------------------------- -def main(): +@click.command() +@click.option( "--template-pack", help="Template pack to auto-load (ZIP file or directory)." ) +def main( template_pack ): """Main entry point for the application.""" + # configure the autoload template pack + if template_pack: + if template_pack.lower().endswith( ".zip" ): + rc = os.path.isfile( template_pack ) + else: + rc = os.path.isdir( template_pack ) + if not rc: + click.echo( "ERROR: The template pack must be a ZIP file or a directory containing the template files." ) + return 1 + generate.autoload_template_pack = template_pack + # connect stdout/stderr to Python logging # NOTE: Logging to the console causes crashes on Windows if we are frozen, so don't do that! sys.stdout = LoggerProxy( logging, logging.INFO ) @@ -85,4 +102,4 @@ def main(): # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if __name__ == "__main__": - sys.exit( main() ) + sys.exit( main() ) #pylint: disable=no-value-for-parameter diff --git a/vasl_templates/webapp/generate.py b/vasl_templates/webapp/generate.py index 82d928f..7102cd7 100644 --- a/vasl_templates/webapp/generate.py +++ b/vasl_templates/webapp/generate.py @@ -2,30 +2,81 @@ import os import json +import zipfile -from flask import jsonify +from flask import jsonify, abort from vasl_templates.webapp import app from vasl_templates.webapp.config.constants import DATA_DIR +autoload_template_pack = None + # --------------------------------------------------------------------- -@app.route( "/templates" ) -def get_templates(): - """Get the specified templates.""" +@app.route( "/templates/default" ) +def get_default_templates(): + """Get the default templates.""" - # load the default templates - templates = {} + # return the default templates dname = os.path.join( DATA_DIR, "default-templates" ) - for fname in os.listdir(dname): - if os.path.splitext(fname)[1] != ".j2": - continue - fname2 = os.path.join( dname, fname ) - with open(fname2,"r") as fp: - templates[os.path.splitext(fname)[0]] = fp.read() + return jsonify( _do_get_templates( dname ) ) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +@app.route( "/templates/autoload" ) +def get_autoload_templates(): + """Get the templates to auto-load at startup. + + We would like the user to be able to specify a template pack to auto-load + when starting the desktop app, but it's a little tricky to programatically + get the frontend Javascript to accept an upload. We could possibly do it + by using QWebChannel, but this would only work if the webapp was running + inside PyQt. Instead, we get the frontend to call this endpoint when it + starts up, to get the (optional) autoload templates. + """ + + # check if an autoload template pack has been configured + if not autoload_template_pack: + # nope - return an empty response + return jsonify( {} ) + # check if the template pack is a directory + if os.path.isdir( autoload_template_pack ): + # yup - return the template files in it + templates = _do_get_templates( autoload_template_pack ) + templates["_path_"] = autoload_template_pack + return jsonify( templates ) + + # return the template files in the specified ZIP file + if not os.path.isfile( autoload_template_pack ): + return jsonify( { "error": "Can't find template pack: {}".format(autoload_template_pack) } ) + templates = {} + with zipfile.ZipFile( autoload_template_pack, "r" ) as zip_file: + for fname in zip_file.namelist(): + if fname.endswith( "/" ): + continue + fname2 = os.path.split(fname)[1] + templates[os.path.splitext(fname2)[0]] = zip_file.read( fname ).decode( "utf-8" ) + templates["_path_"] = autoload_template_pack return jsonify( templates ) +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def _do_get_templates( dname ): + """Get the specified templates.""" + if not os.path.isdir( dname ): + abort( 404 ) + templates = {} + for root,_,fnames in os.walk(dname): + for fname in fnames: + fname = os.path.join( root, fname ) + if os.path.splitext(fname)[1] != ".j2": + continue + with open(fname,"r") as fp: + fname = os.path.split(fname)[1] + templates[os.path.splitext(fname)[0]] = fp.read() + return templates + # --------------------------------------------------------------------- @app.route( "/nationalities" ) diff --git a/vasl_templates/webapp/static/css/main.css b/vasl_templates/webapp/static/css/main.css index 3110670..b50cf1a 100644 --- a/vasl_templates/webapp/static/css/main.css +++ b/vasl_templates/webapp/static/css/main.css @@ -7,7 +7,7 @@ body { height: 100% ; } #menu { position: absolute ; top: 15px ; right: 15px ; z-index: 1 ; } #menu { height: 30px ; } -.PopMenu-Item { width: 8em } +.PopMenu-Item { width: 11em } .PopMenu-Item a { padding: 5px 10px 5px 10px ; } .PopMenu-Icon { display: none ; } @@ -88,3 +88,6 @@ input[type="text"] { margin-bottom: 0.25em ; } .growl-title { display: none ; } .growl ul { margin-left: 1em ; } +.growl .pre { font-family: "Courier New"; } +.growl div.pre { margin: 0 0 1em 1em ; font-size: 80% ; } +.growl .pre ul { margin-left: 0 ; } diff --git a/vasl_templates/webapp/static/generate.js b/vasl_templates/webapp/static/generate.js index fb4602b..de99c70 100644 --- a/vasl_templates/webapp/static/generate.js +++ b/vasl_templates/webapp/static/generate.js @@ -113,13 +113,18 @@ function generate_snippet( $btn ) showWarningMsg( "Both players have the same nationality!" ) ; // get the template to generate the snippet from - if ( ! (template_id in gDefaultTemplates) ) { + var templ ; + if ( template_id in gUserDefinedTemplates ) + templ = gUserDefinedTemplates[template_id] ; + else if ( template_id in gDefaultTemplates ) + templ = gDefaultTemplates[template_id] ; + else { showErrorMsg( "Unknown template: " + escapeHTML(template_id) ) ; return ; } var func, val ; try { - func = jinja.compile( gDefaultTemplates[template_id] ).render ; + func = jinja.compile( templ ).render ; } catch( ex ) { showErrorMsg( "Can't compile template:
" + escapeHTML(ex) + "" ) ; @@ -173,9 +178,8 @@ function on_load_scenario() // FOR TESTING PORPOISES! We can't control a file upload from Selenium (since // the browser will use native controls), so we store the result in a
>>24,p>>>=w,q-=w,w=v>>>16&255,!(16&w)){if(0===(64&w)){v=s[(65535&v)+(p&(1<k){a.msg="invalid distance too far back",c.mode=d;break a}if(p>>>=w,q-=w,w=h-i,y>w){if(w=y-w,w>m&&c.sane){a.msg="invalid distance too far back",c.mode=d;break a}if(z=0,A=o,0===n){if(z+=l-w,w 2;)C[h++]=A[z++],C[h++]=A[z++],C[h++]=A[z++],x-=3;x&&(C[h++]=A[z++],x>1&&(C[h++]=A[z++]))}else{z=h-y;do C[h++]=C[z++],C[h++]=C[z++],C[h++]=C[z++],x-=3;while(x>2);x&&(C[h++]=C[z++],x>1&&(C[h++]=C[z++]))}break}}break}}while(f >3,f-=x,q-=x<<3,p&=(1< >>24&255)+(a>>>8&65280)+((65280&a)<<8)+((255&a)<<24)}function e(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new s.Buf16(320),this.work=new s.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function f(a){var b;return a&&a.state?(b=a.state,a.total_in=a.total_out=b.total=0,a.msg="",b.wrap&&(a.adler=1&b.wrap),b.mode=L,b.last=0,b.havedict=0,b.dmax=32768,b.head=null,b.hold=0,b.bits=0,b.lencode=b.lendyn=new s.Buf32(pa),b.distcode=b.distdyn=new s.Buf32(qa),b.sane=1,b.back=-1,D):G}function g(a){var b;return a&&a.state?(b=a.state,b.wsize=0,b.whave=0,b.wnext=0,f(a)):G}function h(a,b){var c,d;return a&&a.state?(d=a.state,b<0?(c=0,b=-b):(c=(b>>4)+1,b<48&&(b&=15)),b&&(b<8||b>15)?G:(null!==d.window&&d.wbits!==b&&(d.window=null),d.wrap=c,d.wbits=b,g(a))):G}function i(a,b){var c,d;return a?(d=new e,a.state=d,d.window=null,c=h(a,b),c!==D&&(a.state=null),c):G}function j(a){return i(a,sa)}function k(a){if(ta){var b;for(q=new s.Buf32(512),r=new s.Buf32(32),b=0;b<144;)a.lens[b++]=8;for(;b<256;)a.lens[b++]=9;for(;b<280;)a.lens[b++]=7;for(;b<288;)a.lens[b++]=8;for(w(y,a.lens,0,288,q,0,a.work,{bits:9}),b=0;b<32;)a.lens[b++]=5;w(z,a.lens,0,32,r,0,a.work,{bits:5}),ta=!1}a.lencode=q,a.lenbits=9,a.distcode=r,a.distbits=5}function l(a,b,c,d){var e,f=a.state;return null===f.window&&(f.wsize=1<=f.wsize?(s.arraySet(f.window,b,c-f.wsize,f.wsize,0),f.wnext=0,f.whave=f.wsize):(e=f.wsize-f.wnext,e>d&&(e=d),s.arraySet(f.window,b,c-d,e,f.wnext),d-=e,d?(s.arraySet(f.window,b,c-d,d,0),f.wnext=d,f.whave=f.wsize):(f.wnext+=e,f.wnext===f.wsize&&(f.wnext=0),f.whave >>8&255,c.check=u(c.check,Ba,2,0),m=0,n=0,c.mode=M;break}if(c.flags=0,c.head&&(c.head.done=!1),!(1&c.wrap)||(((255&m)<<8)+(m>>8))%31){a.msg="incorrect header check",c.mode=ma;break}if((15&m)!==K){a.msg="unknown compression method",c.mode=ma;break}if(m>>>=4,n-=4,wa=(15&m)+8,0===c.wbits)c.wbits=wa;else if(wa>c.wbits){a.msg="invalid window size",c.mode=ma;break}c.dmax=1< >8&1),512&c.flags&&(Ba[0]=255&m,Ba[1]=m>>>8&255,c.check=u(c.check,Ba,2,0)),m=0,n=0,c.mode=N;case N:for(;n<32;){if(0===i)break a;i--,m+=e[g++]< >>8&255,Ba[2]=m>>>16&255,Ba[3]=m>>>24&255,c.check=u(c.check,Ba,4,0)),m=0,n=0,c.mode=O;case O:for(;n<16;){if(0===i)break a;i--,m+=e[g++]< >8),512&c.flags&&(Ba[0]=255&m,Ba[1]=m>>>8&255,c.check=u(c.check,Ba,2,0)),m=0,n=0,c.mode=P;case P:if(1024&c.flags){for(;n<16;){if(0===i)break a;i--,m+=e[g++]< >>8&255,c.check=u(c.check,Ba,2,0)),m=0,n=0}else c.head&&(c.head.extra=null);c.mode=Q;case Q:if(1024&c.flags&&(q=c.length,q>i&&(q=i),q&&(c.head&&(wa=c.head.extra_len-c.length,c.head.extra||(c.head.extra=new Array(c.head.extra_len)),s.arraySet(c.head.extra,e,g,q,wa)),512&c.flags&&(c.check=u(c.check,e,q,g)),i-=q,g+=q,c.length-=q),c.length))break a;c.length=0,c.mode=R;case R:if(2048&c.flags){if(0===i)break a;q=0;do wa=e[g+q++],c.head&&wa&&c.length<65536&&(c.head.name+=String.fromCharCode(wa));while(wa&&q>9&1,c.head.done=!0),a.adler=c.check=0,c.mode=W;break;case U:for(;n<32;){if(0===i)break a;i--,m+=e[g++]< >>=7&n,n-=7&n,c.mode=ja;break}for(;n<3;){if(0===i)break a;i--,m+=e[g++]< >>=1,n-=1,3&m){case 0:c.mode=Y;break;case 1:if(k(c),c.mode=ca,b===C){m>>>=2,n-=2;break a}break;case 2:c.mode=_;break;case 3:a.msg="invalid block type",c.mode=ma}m>>>=2,n-=2;break;case Y:for(m>>>=7&n,n-=7&n;n<32;){if(0===i)break a;i--,m+=e[g++]< >>16^65535)){a.msg="invalid stored block lengths",c.mode=ma;break}if(c.length=65535&m,m=0,n=0,c.mode=Z,b===C)break a;case Z:c.mode=$;case $:if(q=c.length){if(q>i&&(q=i),q>j&&(q=j),0===q)break a;s.arraySet(f,e,g,q,h),i-=q,g+=q,j-=q,h+=q,c.length-=q;break}c.mode=W;break;case _:for(;n<14;){if(0===i)break a;i--,m+=e[g++]< >>=5,n-=5,c.ndist=(31&m)+1,m>>>=5,n-=5,c.ncode=(15&m)+4,m>>>=4,n-=4,c.nlen>286||c.ndist>30){a.msg="too many length or distance symbols",c.mode=ma;break}c.have=0,c.mode=aa;case aa:for(;c.have >>=3,n-=3}for(;c.have<19;)c.lens[Ca[c.have++]]=0;if(c.lencode=c.lendyn,c.lenbits=7,ya={bits:c.lenbits},xa=w(x,c.lens,0,19,c.lencode,0,c.work,ya),c.lenbits=ya.bits,xa){a.msg="invalid code lengths set",c.mode=ma;break}c.have=0,c.mode=ba;case ba:for(;c.have >>24,ra=Aa>>>16&255,sa=65535&Aa,!(qa<=n);){if(0===i)break a;i--,m+=e[g++]< >>=qa,n-=qa,c.lens[c.have++]=sa;else{if(16===sa){for(za=qa+2;n >>=qa,n-=qa,0===c.have){a.msg="invalid bit length repeat",c.mode=ma;break}wa=c.lens[c.have-1],q=3+(3&m),m>>>=2,n-=2}else if(17===sa){for(za=qa+3;n >>=qa,n-=qa,wa=0,q=3+(7&m),m>>>=3,n-=3}else{for(za=qa+7;n >>=qa,n-=qa,wa=0,q=11+(127&m),m>>>=7,n-=7}if(c.have+q>c.nlen+c.ndist){a.msg="invalid bit length repeat",c.mode=ma;break}for(;q--;)c.lens[c.have++]=wa}}if(c.mode===ma)break;if(0===c.lens[256]){a.msg="invalid code -- missing end-of-block",c.mode=ma;break}if(c.lenbits=9,ya={bits:c.lenbits},xa=w(y,c.lens,0,c.nlen,c.lencode,0,c.work,ya),c.lenbits=ya.bits,xa){a.msg="invalid literal/lengths set",c.mode=ma;break}if(c.distbits=6,c.distcode=c.distdyn,ya={bits:c.distbits},xa=w(z,c.lens,c.nlen,c.ndist,c.distcode,0,c.work,ya),c.distbits=ya.bits,xa){a.msg="invalid distances set",c.mode=ma;break}if(c.mode=ca,b===C)break a;case ca:c.mode=da;case da:if(i>=6&&j>=258){a.next_out=h,a.avail_out=j,a.next_in=g,a.avail_in=i,c.hold=m,c.bits=n,v(a,p),h=a.next_out,f=a.output,j=a.avail_out,g=a.next_in,e=a.input,i=a.avail_in,m=c.hold,n=c.bits,c.mode===W&&(c.back=-1);break}for(c.back=0;Aa=c.lencode[m&(1< >>24,ra=Aa>>>16&255,sa=65535&Aa,!(qa<=n);){if(0===i)break a;i--,m+=e[g++]< >ta)],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(ta+qa<=n);){if(0===i)break a;i--,m+=e[g++]< >>=ta,n-=ta,c.back+=ta}if(m>>>=qa,n-=qa,c.back+=qa,c.length=sa,0===ra){c.mode=ia;break}if(32&ra){c.back=-1,c.mode=W;break}if(64&ra){a.msg="invalid literal/length code",c.mode=ma;break}c.extra=15&ra,c.mode=ea;case ea:if(c.extra){for(za=c.extra;n >>=c.extra,n-=c.extra,c.back+=c.extra}c.was=c.length,c.mode=fa;case fa:for(;Aa=c.distcode[m&(1< >>24,ra=Aa>>>16&255,sa=65535&Aa,!(qa<=n);){if(0===i)break a;i--,m+=e[g++]< >ta)],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(ta+qa<=n);){if(0===i)break a;i--,m+=e[g++]< >>=ta,n-=ta,c.back+=ta}if(m>>>=qa,n-=qa,c.back+=qa,64&ra){a.msg="invalid distance code",c.mode=ma;break}c.offset=sa,c.extra=15&ra,c.mode=ga;case ga:if(c.extra){for(za=c.extra;n >>=c.extra,n-=c.extra,c.back+=c.extra}if(c.offset>c.dmax){a.msg="invalid distance too far back",c.mode=ma;break}c.mode=ha;case ha:if(0===j)break a;if(q=p-j,c.offset>q){if(q=c.offset-q,q>c.whave&&c.sane){a.msg="invalid distance too far back",c.mode=ma;break}q>c.wnext?(q-=c.wnext,r=c.wsize-q):r=c.wnext-q,q>c.length&&(q=c.length),pa=c.window}else pa=f,r=h-c.offset,q=c.length;q>j&&(q=j),j-=q,c.length-=q;do f[h++]=pa[r++];while(--q);0===c.length&&(c.mode=da);break;case ia:if(0===j)break a;f[h++]=c.length,j--,c.mode=da;break;case ja:if(c.wrap){for(;n<32;){if(0===i)break a;i--,m|=e[g++]< =1&&0===P[G];G--);if(H>G&&(H=G),0===G)return p[q++]=20971520,p[q++]=20971520,s.bits=1,0;for(F=1;F 0&&(a===h||1!==G))return-1;for(Q[1]=0,D=1;D f||a===j&&L>g)return 1;for(;;){z=D-J,r[E] y?(A=R[S+r[E]],B=N[O+r[E]]):(A=96,B=0),t=1< >J)+u]=z<<24|A<<16|B|0;while(0!==u);for(t=1< >=1;if(0!==t?(M&=t-1,M+=t):M=0,E++,0===--P[D]){if(D===G)break;D=b[c+r[E]]}if(D>H&&(M&w)!==v){for(0===J&&(J=H),x+=F,I=D-J,K=1<f||a===j&&L>g)return 1;v=M&w,p[v]=H<<24|I<<16|x-q|0}}return 0!==M&&(p[x+M]=D-J<<24|64<<16|0),s.bits=H,0}},{"../utils/common":62}],72:[function(a,b,c){"use strict";b.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],73:[function(a,b,c){"use strict";function d(a){for(var b=a.length;--b>=0;)a[b]=0}function e(a,b,c,d,e){this.static_tree=a,this.extra_bits=b,this.extra_base=c,this.elems=d,this.max_length=e,this.has_stree=a&&a.length}function f(a,b){this.dyn_tree=a,this.max_code=0,this.stat_desc=b}function g(a){return a<256?ia[a]:ia[256+(a>>>7)]}function h(a,b){a.pending_buf[a.pending++]=255&b,a.pending_buf[a.pending++]=b>>>8&255}function i(a,b,c){a.bi_valid>X-c?(a.bi_buf|=b< >X-a.bi_valid,a.bi_valid+=c-X):(a.bi_buf|=b< >>=1,c<<=1;while(--b>0);return c>>>1}function l(a){16===a.bi_valid?(h(a,a.bi_buf),a.bi_buf=0,a.bi_valid=0):a.bi_valid>=8&&(a.pending_buf[a.pending++]=255&a.bi_buf,a.bi_buf>>=8,a.bi_valid-=8)}function m(a,b){var c,d,e,f,g,h,i=b.dyn_tree,j=b.max_code,k=b.stat_desc.static_tree,l=b.stat_desc.has_stree,m=b.stat_desc.extra_bits,n=b.stat_desc.extra_base,o=b.stat_desc.max_length,p=0;for(f=0;f<=W;f++)a.bl_count[f]=0;for(i[2*a.heap[a.heap_max]+1]=0, +c=a.heap_max+1;c o&&(f=o,p++),i[2*d+1]=f,d>j||(a.bl_count[f]++,g=0,d>=n&&(g=m[d-n]),h=i[2*d],a.opt_len+=h*(f+g),l&&(a.static_len+=h*(k[2*d+1]+g)));if(0!==p){do{for(f=o-1;0===a.bl_count[f];)f--;a.bl_count[f]--,a.bl_count[f+1]+=2,a.bl_count[o]--,p-=2}while(p>0);for(f=o;0!==f;f--)for(d=a.bl_count[f];0!==d;)e=a.heap[--c],e>j||(i[2*e+1]!==f&&(a.opt_len+=(f-i[2*e+1])*i[2*e],i[2*e+1]=f),d--)}}function n(a,b,c){var d,e,f=new Array(W+1),g=0;for(d=1;d<=W;d++)f[d]=g=g+c[d-1]<<1;for(e=0;e<=b;e++){var h=a[2*e+1];0!==h&&(a[2*e]=k(f[h]++,h))}}function o(){var a,b,c,d,f,g=new Array(W+1);for(c=0,d=0;d >=7;d 8?h(a,a.bi_buf):a.bi_valid>0&&(a.pending_buf[a.pending++]=a.bi_buf),a.bi_buf=0,a.bi_valid=0}function r(a,b,c,d){q(a),d&&(h(a,c),h(a,~c)),G.arraySet(a.pending_buf,a.window,b,c,a.pending),a.pending+=c}function s(a,b,c,d){var e=2*b,f=2*c;return a[e]>1;c>=1;c--)t(a,f,c);e=i;do c=a.heap[1],a.heap[1]=a.heap[a.heap_len--],t(a,f,1),d=a.heap[1],a.heap[--a.heap_max]=c,a.heap[--a.heap_max]=d,f[2*e]=f[2*c]+f[2*d],a.depth[e]=(a.depth[c]>=a.depth[d]?a.depth[c]:a.depth[d])+1,f[2*c+1]=f[2*d+1]=e,a.heap[1]=e++,t(a,f,1);while(a.heap_len>=2);a.heap[--a.heap_max]=a.heap[1],m(a,b),n(f,j,a.bl_count)}function w(a,b,c){var d,e,f=-1,g=b[1],h=0,i=7,j=4;for(0===g&&(i=138,j=3),b[2*(c+1)+1]=65535,d=0;d<=c;d++)e=g,g=b[2*(d+1)+1],++h=3&&0===a.bl_tree[2*ea[b]+1];b--);return a.opt_len+=3*(b+1)+5+5+4,b}function z(a,b,c,d){var e;for(i(a,b-257,5),i(a,c-1,5),i(a,d-4,4),e=0;e >>=1)if(1&c&&0!==a.dyn_ltree[2*b])return I;if(0!==a.dyn_ltree[18]||0!==a.dyn_ltree[20]||0!==a.dyn_ltree[26])return J;for(b=32;b 0?(a.strm.data_type===K&&(a.strm.data_type=A(a)),v(a,a.l_desc),v(a,a.d_desc),g=y(a),e=a.opt_len+3+7>>>3,f=a.static_len+3+7>>>3,f<=e&&(e=f)):e=f=c+5,c+4<=e&&b!==-1?C(a,b,c,d):a.strategy===H||f===e?(i(a,(M<<1)+(d?1:0),3),u(a,ga,ha)):(i(a,(N<<1)+(d?1:0),3),z(a,a.l_desc.max_code+1,a.d_desc.max_code+1,g+1),u(a,a.dyn_ltree,a.dyn_dtree)),p(a),d&&q(a)}function F(a,b,c){return a.pending_buf[a.d_buf+2*a.last_lit]=b>>>8&255,a.pending_buf[a.d_buf+2*a.last_lit+1]=255&b,a.pending_buf[a.l_buf+a.last_lit]=255&c,a.last_lit++,0===b?a.dyn_ltree[2*c]++:(a.matches++,b--,a.dyn_ltree[2*(ja[c]+R+1)]++,a.dyn_dtree[2*g(b)]++),a.last_lit===a.lit_bufsize-1}var G=a("../utils/common"),H=4,I=0,J=1,K=2,L=0,M=1,N=2,O=3,P=258,Q=29,R=256,S=R+1+Q,T=30,U=19,V=2*S+1,W=15,X=16,Y=7,Z=256,$=16,_=17,aa=18,ba=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],ca=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],da=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],ea=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],fa=512,ga=new Array(2*(S+2));d(ga);var ha=new Array(2*T);d(ha);var ia=new Array(fa);d(ia);var ja=new Array(P-O+1);d(ja);var ka=new Array(Q);d(ka);var la=new Array(T);d(la);var ma,na,oa,pa=!1;c._tr_init=B,c._tr_stored_block=C,c._tr_flush_block=E,c._tr_tally=F,c._tr_align=D},{"../utils/common":62}],74:[function(a,b,c){"use strict";function d(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}b.exports=d},{}]},{},[10])(10)}); \ No newline at end of file diff --git a/vasl_templates/webapp/static/main.js b/vasl_templates/webapp/static/main.js index 869eb35..741d997 100644 --- a/vasl_templates/webapp/static/main.js +++ b/vasl_templates/webapp/static/main.js @@ -1,5 +1,6 @@ var gNationalities = {} ; var gDefaultTemplates = {} ; +var gUserDefinedTemplates = {} ; var _NATIONALITY_SPECIFIC_BUTTONS = { "russian": [ "mol", "mol-p" ], @@ -15,9 +16,11 @@ $(document).ready( function () { // initialize var $menu = $("#menu input") ; $menu.popmenu( { - new: { label: "New scenario", action: on_new_scenario }, - load: { label: "Load scenario", action: on_load_scenario }, - save: { label: "Save scenario", action: on_save_scenario }, + new_scenario: { label: "New scenario", action: on_new_scenario }, + load_scenario: { label: "Load scenario", action: on_load_scenario }, + save_scenario: { label: "Save scenario", action: on_save_scenario }, + separator: { type: "separator" }, + template_pack: { label: "Load template pack", action: on_template_pack }, } ) ; // nb: we only show the popmenu on left click (not the normal right-click) $menu.off( "contextmenu" ) ; @@ -34,6 +37,8 @@ $(document).ready( function () { } ) ; // add a handler for when the "load scenario" file has been selected $("#load-scenario").change( on_load_scenario_file_selected ) ; + // add a handler for when the "load template pack" file has been selected + $("#load-template-pack").change( on_template_pack_file_selected ) ; // all done - we can show the menu now $("#menu").show() ; @@ -115,11 +120,24 @@ $(document).ready( function () { $("select[name='PLAYER_1']").change( function() { on_player_change($(this)) ; } ) ; $("select[name='PLAYER_2']").change( function() { on_player_change($(this)) ; } ) ; - // get the default templates - $.getJSON( gGetTemplatesUrl, function(data) { + // get the templates + $.getJSON( gGetDefaultTemplatesUrl, function(data) { gDefaultTemplates = data ; } ).fail( function( xhr, status, errorMsg ) { - showErrorMsg( "Can't get the default templates: " + escapeHTML(errorMsg) + "" ) ; + showErrorMsg( "Can't get the default templates:" + escapeHTML(errorMsg) + "" ) ; + } ) ; + $.getJSON( gGetAutoloadTemplatesUrl, function(data) { + if ( "error" in data ) + showErrorMsg( "Can't get the autoload templates:" + escapeHTML(data.error) + "" ) ; + else { + if ( "_path_" in data ) { + showInfoMsg( "Auto-loaded template pack:" + escapeHTML(data._path_) + "" ) ; + delete data._path_ ; + } + gUserDefinedTemplates = data ; + } + } ).fail( function( xhr, status, errorMsg ) { + showErrorMsg( "Can't get the autoload templates:" + escapeHTML(errorMsg) + "" ) ; } ) ; var prevHeight = [] ; diff --git a/vasl_templates/webapp/static/utils.js b/vasl_templates/webapp/static/utils.js index 877b9ea..5c16634 100644 --- a/vasl_templates/webapp/static/utils.js +++ b/vasl_templates/webapp/static/utils.js @@ -142,6 +142,11 @@ function escapeHTML( val ) return new Option(val).innerHTML ; } +function pluralString( n, str1, str2 ) +{ + return (n == 1) ? str1 : str2 ; +} + function isIE() { // check if we're running in IE :-/ diff --git a/vasl_templates/webapp/templates/index.html b/vasl_templates/webapp/templates/index.html index 0cb0835..f55a089 100644 --- a/vasl_templates/webapp/templates/index.html +++ b/vasl_templates/webapp/templates/index.html @@ -18,6 +18,8 @@@@ -136,9 +138,11 @@ + diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/american/baz.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/american/baz.j2 new file mode 100644 index 0000000..33cfdff --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/american/baz.j2 @@ -0,0 +1 @@ +Autoload'ed BAZ. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/british/piat.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/british/piat.j2 new file mode 100644 index 0000000..918e997 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/british/piat.j2 @@ -0,0 +1 @@ +Autoload'ed PIAT. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/atmm.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/atmm.j2 new file mode 100644 index 0000000..85a535f --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/atmm.j2 @@ -0,0 +1 @@ +Autoload'ed ATMM. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/pf.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/pf.j2 new file mode 100644 index 0000000..6d32cb8 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/pf.j2 @@ -0,0 +1 @@ +Autoload'ed PF. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/psk.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/psk.j2 new file mode 100644 index 0000000..36a0085 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/psk.j2 @@ -0,0 +1 @@ +Autoload'ed PSK. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/ob_setup.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/ob_setup.j2 new file mode 100644 index 0000000..db46bf4 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/ob_setup.j2 @@ -0,0 +1 @@ +Autoload'ed OB_SETUP. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/players.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/players.j2 new file mode 100644 index 0000000..554eafd --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/players.j2 @@ -0,0 +1 @@ +Autoload'ed PLAYERS. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/russian/mol-p.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/russian/mol-p.j2 new file mode 100644 index 0000000..a781e6d --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/russian/mol-p.j2 @@ -0,0 +1 @@ +Autoload'ed MOL-P. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/russian/mol.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/russian/mol.j2 new file mode 100644 index 0000000..0c34dcc --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/russian/mol.j2 @@ -0,0 +1 @@ +Autoload'ed MOL. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/scenario.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/scenario.j2 new file mode 100644 index 0000000..a3b4926 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/scenario.j2 @@ -0,0 +1 @@ +Autoload'ed SCENARIO. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/ssr.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/ssr.j2 new file mode 100644 index 0000000..efbf32a --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/ssr.j2 @@ -0,0 +1 @@ +Autoload'ed SSR. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/autoload/victory_conditions.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/victory_conditions.j2 new file mode 100644 index 0000000..20616a5 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/autoload/victory_conditions.j2 @@ -0,0 +1 @@ +Autoload'ed VICTORY_CONDITIONS. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/american/baz.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/american/baz.j2 new file mode 100644 index 0000000..b75856f --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/american/baz.j2 @@ -0,0 +1 @@ +Customized BAZ. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/british/piat.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/british/piat.j2 new file mode 100644 index 0000000..bf65165 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/british/piat.j2 @@ -0,0 +1 @@ +Customized PIAT. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/german/atmm.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/german/atmm.j2 new file mode 100644 index 0000000..8dca98b --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/german/atmm.j2 @@ -0,0 +1 @@ +Customized ATMM. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/german/pf.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/german/pf.j2 new file mode 100644 index 0000000..ca41b3e --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/german/pf.j2 @@ -0,0 +1 @@ +Customized PF. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/german/psk.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/german/psk.j2 new file mode 100644 index 0000000..1ae7f56 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/german/psk.j2 @@ -0,0 +1 @@ +Customized PSK. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/ob_setup.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/ob_setup.j2 new file mode 100644 index 0000000..8e3c1ee --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/ob_setup.j2 @@ -0,0 +1 @@ +Customized OB_SETUP. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/players.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/players.j2 new file mode 100644 index 0000000..d0bce59 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/players.j2 @@ -0,0 +1 @@ +Customized PLAYERS. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/russian/mol-p.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/russian/mol-p.j2 new file mode 100644 index 0000000..34574e3 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/russian/mol-p.j2 @@ -0,0 +1 @@ +Customized MOL-P. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/russian/mol.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/russian/mol.j2 new file mode 100644 index 0000000..6958cf0 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/russian/mol.j2 @@ -0,0 +1 @@ +Customized MOL. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/scenario.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/scenario.j2 new file mode 100644 index 0000000..eabca6a --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/scenario.j2 @@ -0,0 +1 @@ +Customized SCENARIO. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/ssr.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/ssr.j2 new file mode 100644 index 0000000..fb4f95e --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/ssr.j2 @@ -0,0 +1 @@ +Customized SSR. diff --git a/vasl_templates/webapp/tests/fixtures/template-packs/full/victory_conditions.j2 b/vasl_templates/webapp/tests/fixtures/template-packs/full/victory_conditions.j2 new file mode 100644 index 0000000..2076a85 --- /dev/null +++ b/vasl_templates/webapp/tests/fixtures/template-packs/full/victory_conditions.j2 @@ -0,0 +1 @@ +Customized VICTORY_CONDITIONS. diff --git a/vasl_templates/webapp/tests/test_generate.py b/vasl_templates/webapp/tests/test_generate.py index ce13fea..d782752 100644 --- a/vasl_templates/webapp/tests/test_generate.py +++ b/vasl_templates/webapp/tests/test_generate.py @@ -11,7 +11,7 @@ def _test_snippet( webdriver, template_id, params, expected, expected2 ): #pylin set_template_params( params ) # generate the snippet - submit = find_child( "input[class='generate'][data-id='{}']".format(template_id) ) + submit = find_child( "input.generate[data-id='{}']".format(template_id) ) submit.click() snippet = get_clipboard() lines = [ l.strip() for l in snippet.split("\n") ] diff --git a/vasl_templates/webapp/tests/test_scenario_persistence.py b/vasl_templates/webapp/tests/test_scenario_persistence.py index 12f23dd..efa7982 100644 --- a/vasl_templates/webapp/tests/test_scenario_persistence.py +++ b/vasl_templates/webapp/tests/test_scenario_persistence.py @@ -4,7 +4,7 @@ import json from selenium.webdriver.support.ui import Select -from vasl_templates.webapp.tests.utils import set_template_params, select_tab +from vasl_templates.webapp.tests.utils import set_template_params, select_tab, select_menu_option from vasl_templates.webapp.tests.utils import get_stored_msg, set_stored_msg, find_child, find_children # --------------------------------------------------------------------- @@ -50,10 +50,7 @@ def test_scenario_persistence( webapp, webdriver ): assert saved_scenario == expected # reset the scenario - elem = find_child( "#menu" ) - elem.click() - elem = find_child( "a.PopMenu-Link[data-name='new']" ) - elem.click() + select_menu_option( "new_scenario" ) # check the save results data = _save_scenario() @@ -114,17 +111,11 @@ def test_loading_ssrs( webapp, webdriver ): def _load_scenario( scenario ): """Load a scenario into the UI.""" set_stored_msg( "scenario_persistence", json.dumps(scenario) ) - elem = find_child( "#menu" ) - elem.click() - elem = find_child( "a.PopMenu-Link[data-name='load']" ) - elem.click() + select_menu_option( "load_scenario" ) def _save_scenario(): """Save the scenario.""" - elem = find_child( "#menu" ) - elem.click() - elem = find_child( "a.PopMenu-Link[data-name='save']" ) - elem.click() + select_menu_option( "save_scenario" ) data = get_stored_msg( "scenario_persistence" ) return json.loads( data ) diff --git a/vasl_templates/webapp/tests/test_ssr.py b/vasl_templates/webapp/tests/test_ssr.py index 47a8a30..ca74341 100644 --- a/vasl_templates/webapp/tests/test_ssr.py +++ b/vasl_templates/webapp/tests/test_ssr.py @@ -1,11 +1,10 @@ """ Test generating SSR snippets. """ import html -import time from selenium.webdriver.common.action_chains import ActionChains -from vasl_templates.webapp.tests.utils import get_clipboard, find_child, find_children +from vasl_templates.webapp.tests.utils import get_clipboard, dismiss_notifications, find_child, find_children # --------------------------------------------------------------------- @@ -33,9 +32,7 @@ def test_ssr( webapp, webdriver ): if width: val += "\nwidth = [{}]".format( width ) assert html.unescape( get_clipboard() ) == val - elem = find_child( ".growl-close" ) - elem.click() - time.sleep( 0.25 ) + dismiss_notifications() # add an SSR and generate the SSR snippet _add_ssr( "This is my first SSR." ) diff --git a/vasl_templates/webapp/tests/test_template_packs.py b/vasl_templates/webapp/tests/test_template_packs.py new file mode 100644 index 0000000..b7167ba --- /dev/null +++ b/vasl_templates/webapp/tests/test_template_packs.py @@ -0,0 +1,201 @@ +"""Test template packs.""" + +import os +import zipfile +import tempfile +import base64 + +from selenium.webdriver.support.ui import Select + +from vasl_templates.webapp.tests.utils import select_tab, select_menu_option, get_clipboard +from vasl_templates.webapp.tests.utils import get_stored_msg, set_stored_msg, dismiss_notifications, find_child + +# standard templates +STD_TEMPLATES = { + "scenario": [ "scenario", "players", "victory_conditions", "ssr" ], + "ob1": [ "ob_setup_1" ], +} + +# nationality-specific templates +NAT_TEMPLATES = { + "german": [ "pf", "psk", "atmm" ], + "russian": [ "mol", "mol-p" ], + "american": [ "baz" ], + "british": [ "piat" ], +} + +# --------------------------------------------------------------------- + +def test_individual_files( webapp, webdriver ): + """Test loading individual template files.""" + + # initialize + webdriver.get( webapp.url_for( "main", store_msgs=1, template_pack_persistence=1 ) ) + + # generate a list of all the templates we need to test + templates_to_test = set() + dname = os.path.join( os.path.split(__file__)[0], "../data/default-templates" ) + for fname in os.listdir(dname): + if os.path.splitext(fname)[1] != ".j2": + continue + templates_to_test.add( fname ) + + # initialize + def test_template( template_id ): + """Test uploading a customized version of the template.""" + # make sure generating a snippet returns something + dismiss_notifications() + elem = find_child( "input.generate[data-id='{}']".format( template_id ) ) + elem.click() + assert get_clipboard() != "" + # upload a new template + fname = ("ob_setup" if template_id.startswith("ob_setup_") else template_id) + ".j2" + set_stored_msg( "template_pack_persistence", + "{} | {}".format( fname, "UPLOADED TEMPLATE" ) + ) + select_menu_option( "template_pack" ) + # make sure generating a snippet returns the new version + dismiss_notifications() + elem = find_child( "input.generate[data-id='{}']".format( template_id ) ) + elem.click() + assert get_clipboard() == "UPLOADED TEMPLATE" + templates_to_test.remove( fname ) + + # try uploading a customized version of each template + for tab_id,template_ids in STD_TEMPLATES.items(): + select_tab( tab_id ) + for template_id in template_ids: + test_template( template_id ) + + # try uploading a customized version of each nationality-specific template + for nat,template_ids in NAT_TEMPLATES.items(): + select_tab( "scenario" ) + sel = Select( find_child( "select[name='PLAYER_1']" ) ) + sel.select_by_value( nat ) + select_tab( "ob1" ) + for template_id in template_ids: + test_template(template_id ) + + # make sure we tested everything + assert not templates_to_test + + # try uploading a template with an incorrect filename extension + set_stored_msg( "template_pack_persistence", + "filename.xyz | UPLOADED TEMPLATE" + ) + select_menu_option( "template_pack" ) + last_error_msg = get_stored_msg("_last-error_" ) + assert "Invalid template extension" in last_error_msg + + # try uploading a template with an unknown filename + set_stored_msg( "template_pack_persistence", + "unknown.j2 | UPLOADED TEMPLATE" + ) + select_menu_option( "template_pack" ) + last_error_msg = get_stored_msg("_last-error_" ) + assert "Invalid template filename" in last_error_msg + +# --------------------------------------------------------------------- + +def test_zip_files( webapp, webdriver ): + """Test loading ZIP'ed template packs.""" + + # initialize + webdriver.get( webapp.url_for( "main", store_msgs=1, template_pack_persistence=1 ) ) + + # upload a template pack that contains a full set of templates + zip_data = _make_zip_from_files( "full" ) + _upload_template_pack( zip_data ) + assert get_stored_msg("_last-error_") is None + + # check that the uploaded templates are being used + _check_snippets( + lambda tid: "Customized {}.".format( + "OB_SETUP" if tid.startswith("ob_setup_") else tid.upper() + ) + ) + + # upload only part of template pack + _upload_template_pack( zip_data[ : int(len(zip_data)/2) ] ) + assert get_stored_msg("_last-error_").startswith( "Can't unpack the ZIP:" ) + + # try uploading an empty template pack + _upload_template_pack( b"" ) + assert get_stored_msg("_last-error_").startswith( "Can't unpack the ZIP:" ) + + # NOTE: We can't test the limit on template pack size, since it happens after the browser's + # "open file" dialog has finished, but before we read the file data (i.e. we don't execute + # that bit of code since we're using the "template_pack_persistence" hack). + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def test_autoload_template_pack( webapp, webdriver ): + """Test auto-loading template packs.""" + + # configure the autoload template pack + dname = os.path.join( os.path.split(__file__)[0], "fixtures/template-packs/autoload/" ) + from vasl_templates.webapp import generate + generate.autoload_template_pack = dname + + # initialize + webdriver.get( webapp.url_for( "main" ) ) + + # check that the autoload'ed templates are being used + _check_snippets( + lambda tid: "Autoload'ed {}.".format( + "OB_SETUP" if tid.startswith("ob_setup_") else tid.upper() + ) + ) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def _check_snippets( func ): + """Check that snippets are being generated as expected.""" + for tab_id,template_ids in STD_TEMPLATES.items(): + select_tab( tab_id ) + for template_id in template_ids: + dismiss_notifications() + elem = find_child( "input.generate[data-id='{}']".format( template_id ) ) + elem.click() + assert get_clipboard() == func(template_id) + for nat,template_ids in NAT_TEMPLATES.items(): + select_tab( "scenario" ) + sel = Select( find_child( "select[name='PLAYER_1']" ) ) + sel.select_by_value( nat ) + select_tab( "ob1" ) + for template_id in template_ids: + dismiss_notifications() + elem = find_child( "input.generate[data-id='{}']".format( template_id ) ) + elem.click() + assert get_clipboard() == func(template_id) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def _make_zip( files ): + """Generate a ZIP file.""" + with tempfile.NamedTemporaryFile() as temp_file: + temp_file.close() + with zipfile.ZipFile( temp_file.name, "w" ) as zip_file: + for fname,fdata in files.items(): + zip_file.writestr( fname, fdata ) + return open( temp_file.name, "rb" ).read() + +def _make_zip_from_files( dname ): + """Generate a ZIP file from files in a directory.""" + files = {} + dname = os.path.join( os.path.split(__file__)[0], "fixtures/template-packs/"+dname ) + for root,_,fnames in os.walk(dname): + for fname in fnames: + fname = os.path.join( root, fname ) + assert fname.startswith( dname ) + fname2 = fname[len(dname)+1:] + with open( fname, "r" ) as fp: + files[fname2] = fp.read() + return _make_zip( files ) + +def _upload_template_pack( zip_data ): + """Upload a template pack.""" + set_stored_msg( "template_pack_persistence", + "{} | {}".format( "test.zip", base64.b64encode(zip_data).decode("ascii") ) + ) + select_menu_option( "template_pack" ) diff --git a/vasl_templates/webapp/tests/utils.py b/vasl_templates/webapp/tests/utils.py index b660569..f08883f 100644 --- a/vasl_templates/webapp/tests/utils.py +++ b/vasl_templates/webapp/tests/utils.py @@ -7,12 +7,27 @@ import time from PyQt5.QtWidgets import QApplication from selenium.webdriver.support.ui import Select from selenium.webdriver.common.keys import Keys -from selenium.common.exceptions import NoSuchElementException +from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException _webdriver = None # --------------------------------------------------------------------- +def select_tab( tab_id ): + """Select a tab in the main page.""" + elem = find_child( "#tabs .ui-tabs-nav a[href='#tabs-{}']".format( tab_id ) ) + elem.click() + +def select_menu_option( menu_id ): + """Select a menu option.""" + elem = find_child( "#menu" ) + elem.click() + elem = find_child( "a.PopMenu-Link[data-name='{}']".format( menu_id ) ) + elem.click() + wait_for( 5, lambda: find_child("#menu .PopMenu-Container") is None ) # nb: wait for the menu to go away + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + def set_template_params( params ): """Set template parameters.""" @@ -45,13 +60,6 @@ def set_template_params( params ): # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def select_tab( tab_id ): - """Select a tab in the main page.""" - elem = find_child( "#tabs .ui-tabs-nav a[href='#tabs-{}']".format( tab_id ) ) - elem.click() - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def get_nationalities( webapp ): """Get the nationalities table.""" url = webapp.url_for( "get_nationalities" ) @@ -91,6 +99,20 @@ def find_children( sel, parent=None ): except NoSuchElementException: return None +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def dismiss_notifications(): + """Dismiss all notifications.""" + while True: + elem = find_child( ".growl-close" ) + if not elem: + break + try: + elem.click() + time.sleep( 0.25 ) + except StaleElementReferenceException: + pass # nb: the notification had already auto-closed + # --------------------------------------------------------------------- def get_clipboard() : @@ -98,3 +120,12 @@ def get_clipboard() : app = QApplication( [] ) #pylint: disable=unused-variable clipboard = QApplication.clipboard() return clipboard.text() + +def wait_for( timeout, func ): + """Wait for a condition to become true.""" + start_time = time.time() + while True: + if func(): + break + assert time.time() - start_time < timeout + time.sleep( 0.1 )