Added template packs.

master
Pacman Ghost 6 years ago
parent 40d676194b
commit 04d60d9887
  1. 2
      MANIFEST.in
  2. 1
      setup.py
  3. 21
      vasl_templates/main.py
  4. 75
      vasl_templates/webapp/generate.py
  5. 5
      vasl_templates/webapp/static/css/main.css
  6. 159
      vasl_templates/webapp/static/generate.js
  7. 15
      vasl_templates/webapp/static/jszip/jszip.min.js
  8. 30
      vasl_templates/webapp/static/main.js
  9. 5
      vasl_templates/webapp/static/utils.js
  10. 6
      vasl_templates/webapp/templates/index.html
  11. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/american/baz.j2
  12. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/british/piat.j2
  13. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/atmm.j2
  14. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/pf.j2
  15. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/german/psk.j2
  16. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/ob_setup.j2
  17. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/players.j2
  18. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/russian/mol-p.j2
  19. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/russian/mol.j2
  20. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/scenario.j2
  21. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/ssr.j2
  22. 1
      vasl_templates/webapp/tests/fixtures/template-packs/autoload/victory_conditions.j2
  23. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/american/baz.j2
  24. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/british/piat.j2
  25. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/german/atmm.j2
  26. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/german/pf.j2
  27. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/german/psk.j2
  28. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/ob_setup.j2
  29. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/players.j2
  30. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/russian/mol-p.j2
  31. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/russian/mol.j2
  32. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/scenario.j2
  33. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/ssr.j2
  34. 1
      vasl_templates/webapp/tests/fixtures/template-packs/full/victory_conditions.j2
  35. 2
      vasl_templates/webapp/tests/test_generate.py
  36. 17
      vasl_templates/webapp/tests/test_scenario_persistence.py
  37. 7
      vasl_templates/webapp/tests/test_ssr.py
  38. 201
      vasl_templates/webapp/tests/test_template_packs.py
  39. 47
      vasl_templates/webapp/tests/utils.py

@ -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 *.*

@ -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": [

@ -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

@ -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" )

@ -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 ; }

@ -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:<pre>" + escapeHTML(ex) + "</pre>" ) ;
@ -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 <div>).
var $elem ;
if ( getUrlParam( "scenario_persistence" ) ) {
$elem = $( "#scenario_persistence" ) ; // nb: must have already been created
var $elem = $( "#scenario_persistence" ) ; // nb: must have already been created
do_load_scenario( JSON.parse( $elem.val() ) ) ;
return ;
}
@ -221,7 +225,7 @@ function do_load_scenario( params )
continue ;
}
//jshint loopfunc: true
$elem = $("input[type='text'][name='"+key+"'].param").each( function() {
var $elem = $("input[type='text'][name='"+key+"'].param").each( function() {
set_param( $(this), key ) ;
} ) ;
$elem = $("textarea[type='text'][name='"+key+"'].param").each( function() {
@ -294,3 +298,148 @@ function on_new_scenario( verbose )
if ( verbose )
showInfoMsg( "The scenario was reset." ) ;
}
// --------------------------------------------------------------------
function on_template_pack()
{
// 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 <div>).
if ( getUrlParam( "template_pack_persistence" ) ) {
var data = $( "#template_pack_persistence" ).val() ; // nb: must have already been created
var pos = data.indexOf( "|" ) ;
var fname = data.substring( 0, pos ).trim() ;
data = data.substring( pos+1 ).trim() ;
if ( fname.substring(fname.length-4) == ".zip" )
data = atob( data ) ;
do_load_template_pack( fname, data ) ;
return ;
}
// ask the user to upload the template pack
$("#load-template-pack").trigger( "click" ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function on_template_pack_file_selected()
{
// read the selected file
var MAX_FILE_SIZE = 2 ; // nb: MB
var file = $("#load-template-pack").prop("files")[0] ;
if ( file.size > 1024*1024*MAX_FILE_SIZE ) {
showErrorMsg( "Template pack is too large (must be no larger than " + MAX_FILE_SIZE + "MB)." ) ;
return ;
}
var fileReader = new FileReader() ;
fileReader.onload = function() {
var data = fileReader.result ;
do_load_template_pack( file.name, data ) ;
} ;
fileReader.readAsArrayBuffer( file ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function do_load_template_pack( fname, data )
{
// initialize
var invalid_filename_extns = [] ;
var unknown_template_ids = [] ;
var new_templates = {} ;
// initialize
function on_new_template( fname, data ) {
// make sure the filename is valid
if ( fname.substring(fname.length-3) != ".j2" ) {
invalid_filename_extns.push( fname ) ;
return ;
}
var template_id = fname.substring( 0, fname.length-3 ).toLowerCase() ;
if ( ! (template_id in gDefaultTemplates) ) {
unknown_template_ids.push( fname ) ;
return ;
}
// save the new template file
new_templates[template_id] = data ;
}
// initialize
function install_new_templates( success_msg ) {
// check if there were any errors
var ok = true ;
var buf, tid, i ;
if ( invalid_filename_extns.length > 0 ) {
buf = [] ;
buf.push(
"Invalid template ",
pluralString( invalid_filename_extns.length, "extension:", "extensions:" ),
"<div class='pre'>"
) ;
for ( i=0 ; i < invalid_filename_extns.length ; ++i )
buf.push( escapeHTML(invalid_filename_extns[i]) + "<br>" ) ;
buf.push( "</div>" ) ;
buf.push( 'Must be <span class="pre">".zip"</span> or <span class="pre">".j2"</span>.' ) ;
showErrorMsg( buf.join("") ) ;
ok = false ;
}
if ( unknown_template_ids.length > 0 ) {
buf = [] ;
buf.push(
"Invalid template ",
pluralString( unknown_template_ids.length, "filename:", "filenames:" ),
"<div class='pre'>"
) ;
for ( i=0 ; i < unknown_template_ids.length ; ++i )
buf.push( escapeHTML(unknown_template_ids[i]) + "<br>" ) ;
buf.push( "</div>" ) ;
buf.push( "Must be one of:<div class='pre'><ul>" ) ;
for ( tid in gDefaultTemplates )
buf.push( "<li>" + escapeHTML(tid) + ".j2" ) ;
buf.push( "</ul></div>" ) ;
showErrorMsg( buf.join("") ) ;
ok = false ;
}
if ( ! ok )
return ;
// all good - install the new templates
for ( tid in new_templates ){
gUserDefinedTemplates[tid] = new_templates[tid] ;
}
showInfoMsg( success_msg ) ;
}
// check if we have a ZIP file
fname = fname.toLowerCase() ;
if ( fname.substring(fname.length-4) == ".zip" ) {
// yup - process each file in the ZIP
var nFiles = 0 ;
JSZip.loadAsync( data ).then( function( zip ) {
zip.forEach( function( relPath, zipEntry ) {
++ nFiles ;
zipEntry.async( "string" ).then( function( data ) {
// extract the filename (i.e. we ignore sub-directories)
fname = zipEntry.name ;
var pos = Math.max( fname.lastIndexOf("/"), fname.lastIndexOf("\\") ) ;
if ( pos === fname.length-1 )
return ; // nb: ignore directory entries
if ( pos !== -1 )
fname = fname.substring( pos+1 ) ;
on_new_template( fname, data ) ;
} ).then( function() {
if ( --nFiles === 0 ) {
install_new_templates( "The template pack was loaded." ) ;
}
} ) ;
} ) ;
} ).catch( function(ex) {
showErrorMsg( "Can't unpack the ZIP:<div class='pre'>" + escapeHTML(ex) + "</div>" ) ;
} ) ;
}
else {
// nope - assume an individual template file
on_new_template( fname, data ) ;
install_new_templates( "The template file was loaded." ) ;
}
}

File diff suppressed because one or more lines are too long

@ -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:<pre>" + escapeHTML(errorMsg) + "</pre>" ) ;
showErrorMsg( "Can't get the default templates:<div class='pre'>" + escapeHTML(errorMsg) + "</div>" ) ;
} ) ;
$.getJSON( gGetAutoloadTemplatesUrl, function(data) {
if ( "error" in data )
showErrorMsg( "Can't get the autoload templates:<div class='pre'>" + escapeHTML(data.error) + "</div>" ) ;
else {
if ( "_path_" in data ) {
showInfoMsg( "Auto-loaded template pack:<div class='pre'>" + escapeHTML(data._path_) + "</div>" ) ;
delete data._path_ ;
}
gUserDefinedTemplates = data ;
}
} ).fail( function( xhr, status, errorMsg ) {
showErrorMsg( "Can't get the autoload templates:<div class='pre'>" + escapeHTML(errorMsg) + "</div>" ) ;
} ) ;
var prevHeight = [] ;

@ -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 :-/

@ -18,6 +18,8 @@
<div id="menu" style="display:none;">
<input type="button" value="actions">
<input id="load-scenario" type="file" style="display:none;">
<input id="load-template-pack" type="file" style="display:none;">
<textarea id="template_pack_persistence" style="display:none;"></textarea>
</div>
<div id="tabs">
@ -136,9 +138,11 @@
<script src="{{url_for('static',filename='growl/jquery.growl.js')}}"></script>
<script src="{{url_for('static',filename='popmenu/jquery.popmenu-1.0.0.min.js')}}"></script>
<script src="{{url_for('static',filename='download/download.min.js')}}"></script>
<script src="{{url_for('static',filename='jszip/jszip.min.js')}}"></script>
<script>
gImagesBaseUrl = "{{url_for('static',filename='images')}}" ;
gGetTemplatesUrl = "{{url_for('get_templates')}}" ;
gGetDefaultTemplatesUrl = "{{url_for('get_default_templates')}}" ;
gGetAutoloadTemplatesUrl = "{{url_for('get_autoload_templates')}}" ;
gGetNationalitiesUrl = "{{url_for('get_nationalities')}}" ;
</script>
<script src="{{url_for('static',filename='main.js')}}"></script>

@ -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") ]

@ -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 )

@ -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." )

@ -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" )

@ -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 )

Loading…
Cancel
Save