Added the ability to load/save/reset scenarios.

master
Pacman Ghost 6 years ago
parent 2f4f9154b6
commit 09a41da590
  1. 2
      vasl_templates/main.py
  2. 4
      vasl_templates/webapp/main.py
  3. 9
      vasl_templates/webapp/static/css/main.css
  4. 2
      vasl_templates/webapp/static/download/download.min.js
  5. 208
      vasl_templates/webapp/static/generate.js
  6. 42
      vasl_templates/webapp/static/main.js
  7. 46
      vasl_templates/webapp/static/popmenu/jquery.popmenu-1.0.0.min.js
  8. 140
      vasl_templates/webapp/static/popmenu/jquery.popmenu.css
  9. 9
      vasl_templates/webapp/templates/index.html
  10. 20
      vasl_templates/webapp/tests/test_generate.py
  11. 66
      vasl_templates/webapp/tests/test_ob_setup.py
  12. 14
      vasl_templates/webapp/tests/test_players.py
  13. 138
      vasl_templates/webapp/tests/test_scenario_persistence.py
  14. 74
      vasl_templates/webapp/tests/test_ssr.py
  15. 79
      vasl_templates/webapp/tests/utils.py

@ -70,7 +70,7 @@ def main():
# run the application
app = QApplication( sys.argv )
url = "http://localhost:{}/main".format( port )
url = "http://localhost:{}".format( port )
main_window = MainWindow( url )
main_window.show()
ret_code = app.exec_()

@ -6,10 +6,10 @@ from vasl_templates.webapp import app
# ---------------------------------------------------------------------
@app.route( "/main" )
@app.route( "/" )
def main():
"""Return the main page."""
return render_template( "main.html" )
return render_template( "index.html" )
# ---------------------------------------------------------------------

@ -4,6 +4,15 @@ body { height: 100% ; }
/* -------------------------------------------------------------------- */
#menu { position: absolute ; top: 15px ; right: 15px ; z-index: 1 ; }
#menu { height: 30px ; }
.PopMenu-Item { width: 8em }
.PopMenu-Item a { padding: 5px 10px 5px 10px ; }
.PopMenu-Icon { display: none ; }
/* -------------------------------------------------------------------- */
#tabs {
display: none ;
position: absolute ; top: 5px ; bottom: 5px ; left: 5px ; right: 5px ;

@ -0,0 +1,2 @@
//download.js v4.21, by dandavis; 2008-2018. [MIT] see http://danml.com/download.html for tests/usage
;(function(root,factory){typeof define=="function"&&define.amd?define([],factory):typeof exports=="object"?module.exports=factory():root.download=factory()})(this,function(){return function download(data,strFileName,strMimeType){var self=window,defaultMime="application/octet-stream",mimeType=strMimeType||defaultMime,payload=data,url=!strFileName&&!strMimeType&&payload,anchor=document.createElement("a"),toString=function(a){return String(a)},myBlob=self.Blob||self.MozBlob||self.WebKitBlob||toString,fileName=strFileName||"download",blob,reader;myBlob=myBlob.call?myBlob.bind(self):Blob,String(this)==="true"&&(payload=[payload,mimeType],mimeType=payload[0],payload=payload[1]);if(url&&url.length<2048){fileName=url.split("/").pop().split("?")[0],anchor.href=url;if(anchor.href.indexOf(url)!==-1){var ajax=new XMLHttpRequest;return ajax.open("GET",url,!0),ajax.responseType="blob",ajax.onload=function(e){download(e.target.response,fileName,defaultMime)},setTimeout(function(){ajax.send()},0),ajax}}if(/^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(payload)){if(!(payload.length>2096103.424&&myBlob!==toString))return navigator.msSaveBlob?navigator.msSaveBlob(dataUrlToBlob(payload),fileName):saver(payload);payload=dataUrlToBlob(payload),mimeType=payload.type||defaultMime}else if(/([\x80-\xff])/.test(payload)){var i=0,tempUiArr=new Uint8Array(payload.length),mx=tempUiArr.length;for(i;i<mx;++i)tempUiArr[i]=payload.charCodeAt(i);payload=new myBlob([tempUiArr],{type:mimeType})}blob=payload instanceof myBlob?payload:new myBlob([payload],{type:mimeType});function dataUrlToBlob(strUrl){var parts=strUrl.split(/[:;,]/),type=parts[1],indexDecoder=strUrl.indexOf("charset")>0?3:2,decoder=parts[indexDecoder]=="base64"?atob:decodeURIComponent,binData=decoder(parts.pop()),mx=binData.length,i=0,uiArr=new Uint8Array(mx);for(i;i<mx;++i)uiArr[i]=binData.charCodeAt(i);return new myBlob([uiArr],{type:type})}function saver(url,winMode){if("download"in anchor)return anchor.href=url,anchor.setAttribute("download",fileName),anchor.className="download-js-link",anchor.innerHTML="downloading...",anchor.style.display="none",anchor.addEventListener("click",function(e){e.stopPropagation(),this.removeEventListener("click",arguments.callee)}),document.body.appendChild(anchor),setTimeout(function(){anchor.click(),document.body.removeChild(anchor),winMode===!0&&setTimeout(function(){self.URL.revokeObjectURL(anchor.href)},250)},66),!0;if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent))return/^data:/.test(url)&&(url="data:"+url.replace(/^data:([\w\/\-\+]+)/,defaultMime)),window.open(url)||confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")&&(location.href=url),!0;var f=document.createElement("iframe");document.body.appendChild(f),!winMode&&/^data:/.test(url)&&(url="data:"+url.replace(/^data:([\w\/\-\+]+)/,defaultMime)),f.src=url,setTimeout(function(){document.body.removeChild(f)},333)}if(navigator.msSaveBlob)return navigator.msSaveBlob(blob,fileName);if(self.URL)saver(self.URL.createObjectURL(blob),!0);else{if(typeof blob=="string"||blob.constructor===toString)try{return saver("data:"+mimeType+";base64,"+self.btoa(blob))}catch(y){return saver("data:"+mimeType+","+encodeURIComponent(blob))}reader=new FileReader,reader.onload=function(e){saver(this.result)},reader.readAsDataURL(blob)}return!0}});

@ -18,14 +18,8 @@ function generate_snippet( $btn )
storeMsgForTestSuite( "_last-warning_", "" ) ;
storeMsgForTestSuite( "_last-error_", "" ) ;
// collect all the template parameters
var params = {} ;
add_param = function($elem) { params[ $elem.attr("name").toUpperCase() ] = $elem.val() ; } ;
$("input[type='text'].param").each( function() { add_param($(this)) ; } ) ;
$("textarea.param").each( function() { add_param($(this)) ; } ) ;
$("select.param").each( function() { add_param($(this)) ; } ) ;
// figure out which template to use
// unload the template parameters
var params = unload_params() ;
var template_id = $btn.data( "id" ) ;
if ( template_id === "ob_setup_1" ) {
template_id = "ob_setup" ;
@ -41,28 +35,6 @@ function generate_snippet( $btn )
params.OB_SETUP_COLOR_2 = gNationalities[params.PLAYER_2].ob_colors[1] ;
params.OB_SETUP_WIDTH = params.OB_SETUP_WIDTH_2 ;
}
else if ( template_id === "ssr" ) {
params.SSR = [] ;
$("#ssr-sortable li").each( function() {
params.SSR.push( $(this).text() ) ;
} ) ;
}
// check for mandatory parameters
if ( template_id in _MANDATORY_PARAMS ) {
var missing_params = [] ;
for ( var param_id in _MANDATORY_PARAMS[template_id] ) {
if ( ! (param_id in params && params[param_id].length > 0) )
missing_params.push( _MANDATORY_PARAMS[template_id][param_id] ) ;
}
if ( missing_params.length > 0 ) {
var buf = [ "Missing parameters:<ul>" ] ;
for ( var i=0 ; i < missing_params.length ; ++i )
buf.push( "<li>" + escapeHTML(missing_params[i]) ) ;
buf.push( "</ul>" ) ;
showWarningMsg( buf.join("") ) ;
}
}
// extract the scenario date components
var scenario_date = $("input[name='scenario_date']").datepicker( "getDate" ) ;
@ -90,10 +62,6 @@ function generate_snippet( $btn )
params.PF_CHECK_DRM = "" ;
params.PF_CHECK_DR = 3 ;
}
if ( template_id === "pf" ) {
if ( params.SCENARIO_DATE === "" || params.SCENARIO_YEAR <= 1942 || (params.SCENARIO_YEAR == 1943 && params.SCENARIO_MONTH <= 9) )
showWarningMsg( "PF are only available after September 1943." ) ;
}
// generate BAZ parameters
if ( params.SCENARIO_YEAR >= 1945 ) {
@ -113,6 +81,28 @@ function generate_snippet( $btn )
params.BAZ_TOKILL = 13 ;
params.BAZ_RANGE = 4 ;
}
// check for mandatory parameters
if ( template_id in _MANDATORY_PARAMS ) {
var missing_params = [] ;
for ( var param_id in _MANDATORY_PARAMS[template_id] ) {
if ( ! (param_id in params && params[param_id].length > 0) )
missing_params.push( _MANDATORY_PARAMS[template_id][param_id] ) ;
}
if ( missing_params.length > 0 ) {
var buf = [ "Missing parameters:<ul>" ] ;
for ( var i=0 ; i < missing_params.length ; ++i )
buf.push( "<li>" + escapeHTML(missing_params[i]) ) ;
buf.push( "</ul>" ) ;
showWarningMsg( buf.join("") ) ;
}
}
// check for date-specific parameters
if ( template_id === "pf" ) {
if ( params.SCENARIO_DATE === "" || params.SCENARIO_YEAR <= 1942 || (params.SCENARIO_YEAR == 1943 && params.SCENARIO_MONTH <= 9) )
showWarningMsg( "PF are only available after September 1943." ) ;
}
if ( template_id === "baz" ) {
if ( params.SCENARIO_DATE === "" || params.SCENARIO_YEAR <= 1941 || (params.SCENARIO_YEAR == 1942 && params.SCENARIO_MONTH < 11) )
showWarningMsg( "BAZ are only available from November 1942." ) ;
@ -154,3 +144,153 @@ function generate_snippet( $btn )
}
showInfoMsg( "The HTML snippet has been copied to the clipboard." ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function unload_params()
{
// collect all the template parameters
var params = {} ;
add_param = function($elem) { params[ $elem.attr("name").toUpperCase() ] = $elem.val() ; } ;
$("input[type='text'].param").each( function() { add_param($(this)) ; } ) ;
$("textarea.param").each( function() { add_param($(this)) ; } ) ;
$("select.param").each( function() { add_param($(this)) ; } ) ;
// collect the SSR's
params.SSR = [] ;
$("#ssr-sortable li").each( function() {
params.SSR.push( $(this).text() ) ;
} ) ;
return params ;
}
// --------------------------------------------------------------------
function on_load_scenario()
{
// FIXME! confirm this operation if the scenario is dirty
// 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
do_load_scenario( JSON.parse( $elem.val() ) ) ;
return ;
}
// ask the user to upload the scenario file
$("#load-scenario").trigger( "click" ) ;
}
function on_load_scenario_file_selected()
{
// read the selected file
var fileReader = new FileReader() ;
fileReader.onload = function() {
var data ;
try {
data = JSON.parse( fileReader.result ) ;
} catch( ex ) {
showErrorMsg( "Can't load the scenario file:<div>" + escapeHTML(ex) + "</div>" ) ;
return ;
}
do_load_scenario( data ) ;
showInfoMsg( "The scenario was loaded." ) ;
} ;
fileReader.readAsText( $("#load-scenario").prop("files")[0] ) ;
}
function do_load_scenario( params )
{
// load the scenario parameters
on_new_scenario( false ) ;
var params_loaded = {} ;
var set_param = function( $elem, key ) { $elem.val(params[key]) ; params_loaded[key]=true ; return $elem ; } ;
for ( var key in params ) {
if ( key === "SSR" ) {
for ( var i=0 ; i < params[key].length ; ++i ) {
var $ssr = $( "<li></li>" ) ;
$ssr.text( params[key][i] ) ;
$("#ssr-sortable").append( $ssr ) ;
init_ssr( $ssr ) ;
}
update_ssr_hint() ;
params_loaded[key] = true ;
continue ;
}
//jshint loopfunc: true
$elem = $("input[type='text'][name='"+key.toLowerCase()+"'].param").each( function() {
set_param( $(this), key ) ;
} ) ;
$elem = $("textarea[type='text'][name='"+key.toLowerCase()+"'].param").each( function() {
set_param( $(this), key ) ;
} ) ;
$elem = $("select[name='"+key.toLowerCase()+"'].param").each( function() {
set_param( $(this), key ).trigger( "change" ) ;
} ) ;
}
// look for unrecognized keys
var buf = [] ;
for ( key in params ) {
if ( ! (key in params_loaded) )
buf.push( "<li>" + key + " = '" + escapeHTML(params[key]) + "'" ) ;
}
if ( buf.length > 0 )
showWarningMsg( "Unknown keys in the scenario file:<ul>" + buf.join("") + "</ul>" ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function on_save_scenario()
{
// unload the template parameters
var params = unload_params() ;
var data = JSON.stringify( params ) ;
// FOR TESTING PORPOISES! We can't control a file download from Selenium (since
// the browser will use native controls), so we store the result in a <div>).
if ( getUrlParam( "scenario_persistence" ) ) {
var $elem = $( "#scenario_persistence" ) ;
if ( $elem.length === 0 ) {
// NOTE: The <div> we store the message in must be visible, otherwise
// Selenium doesn't return any text for it :-/
$elem = $( "<textarea id='scenario_persistence' style='z-index-999;'></textarea>" ) ;
$("body").append( $elem ) ;
}
$elem.val( data ) ;
return ;
}
// return the parameters to the user as a downloadable file
download( data, "scenario.json", "application/json" ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function on_new_scenario( verbose )
{
// FIXME! confirm this operation if the scenario is dirty
// reset all the template parameters
$("input[type='text'].param").each( function() { $(this).val("") ; } ) ;
$("textarea.param").each( function() { $(this).val("") ; } ) ;
// reset all the template parameters
on_player_change( $("select[name='player_1']").val( "german" ) ) ;
$("select[name='player_1_elr']").val( 5 ) ;
$("select[name='player_1_san']").val( 2 ) ;
on_player_change( $("select[name='player_2']").val( "russian" ) ) ;
$("select[name='player_2_elr']").val( 5 ) ;
$("select[name='player_2_san']").val( 2 ) ;
// reset all the template parameters
$("#ssr-sortable li").each( function() {
$(this).remove() ;
} ) ;
update_ssr_hint() ;
if ( verbose )
showInfoMsg( "The scenario was reset." ) ;
}

@ -12,6 +12,31 @@ var _NATIONALITY_SPECIFIC_BUTTONS = {
$(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 },
} ) ;
// nb: we only show the popmenu on left click (not the normal right-click)
$menu.off( "contextmenu" ) ;
$menu.click( function() {
var pos = $(this).offset() ;
$(this).data( "PopMenu.contextmenu" ).data( "PopMenu.instance" ).show(
pos.left+$(this).width()+4, pos.top+$(this).height()+4, "fade", 200
) ;
} ) ;
// nb: we dismiss the popmenu on ESCAPE
$(document).keydown( function(evt) {
if ( evt.keyCode == 27 )
$menu.popmenu( "hide" ) ;
} ) ;
// add a handler for when the "load scenario" file has been selected
$("#load-scenario").change( on_load_scenario_file_selected ) ;
// all done - we can show the menu now
$("#menu").show() ;
// initialize
$("#tabs").tabs( {
heightStyle: "fill",
@ -45,14 +70,14 @@ $(document).ready( function () {
for ( var i=0 ; i <= 5 ; ++i ) // nb: A19.1: ELR is 0-5
buf.push( "<option value='" + i + "'>" + i + "</option>" ) ;
buf = buf.join( "" ) ;
$("select[name='player_1_elr']").html( buf ).val( 5 ) ;
$("select[name='player_2_elr']").html( buf ).val( 5 ) ;
$("select[name='player_1_elr']").html( buf ) ;
$("select[name='player_2_elr']").html( buf ) ;
buf = [ "<option></option>" ] ; // nb: allow scenarios that have no SAN
for ( i=2 ; i <= 7 ; ++i ) // nb: A14.1: SAN is 2-7
buf.push( "<option value='" + i + "'>" + i + "</option>" ) ;
buf = buf.join( "" ) ;
$("select[name='player_1_san']").html( buf ).val( 2 ) ;
$("select[name='player_2_san']").html( buf ).val( 2 ) ;
$("select[name='player_1_san']").html( buf ) ;
$("select[name='player_2_san']").html( buf ) ;
// load the nationalities
$.getJSON( gGetNationalitiesUrl, function(data) {
@ -60,12 +85,9 @@ $(document).ready( function () {
var buf = [] ;
for ( var id in gNationalities )
buf.push( "<option value='" + id + "'>" + gNationalities[id].display_name + "</option>" ) ;
on_player_change(
$("select[name='player_1']").html( buf ).val( "german" )
) ;
on_player_change(
$("select[name='player_2']").html( buf ).val( "russian" )
) ;
$("select[name='player_1']").html( buf ) ;
$("select[name='player_2']").html( buf ) ;
on_new_scenario( false ) ;
} ).fail( function( xhr, status, errorMsg ) {
showErrorMsg( "Can't get the nationalities:<pre>" + escapeHTML(errorMsg) + "</pre>" ) ;
} ) ;

@ -0,0 +1,46 @@
/**
* jQuery PopMenu
*
* Context menu (popup menu) plugin for web applications.
*
* Copyright © 2014 Fajar Yoseph Chandra. All rights reserved.
*
* @version 1.0.0
* @author Fajar Chandra
* @since 2014.05.29
*/
PopMenu=function(menu,options){this.id="PopMenu_"+Math.round(Math.random()*1E7);this.data=PopMenu.defaults;this.$=$('<div id="'+this.id+'" class="PopMenu-Container" style="display: none;">'+'<ul class="PopMenu-Menu">'+"</ul>"+"</div>");this.$menu=this.$.children(".PopMenu-Menu");this.populateMenu(menu);this.$.on("click",function(){var instance=$(this).data("PopMenu.instance");instance.hide()});this.$.on("contextmenu",function(){return false});this.$.on("mousewheel DOMMouseScroll",function(e){e.stopPropagation();
e.preventDefault();return false});this.$menu.on("mousewheel DOMMouseScroll",function(e){if(!$(this).hasClass("overflows"))return false;var marginTop=parseInt($(this).css("margin-top"));if((e.type="DOMMouseScroll"&&e.originalEvent.detail<0)||typeof WheelEvent!="undefined"&&e.originalEvent instanceof WheelEvent&&e.originalEvent.wheelDeltaY>0||typeof MouseEvent!="undefined"&&e.originalEvent instanceof MouseEvent&&e.originalEvent.detail<0||typeof Event!="undefined"&&e.originalEvent instanceof Event&&
e.originalEvent.wheelDelta>0){marginTop+=72;if(marginTop>0)marginTop=0}else if((e.type="DOMMouseScroll")||typeof WheelEvent!="undefined"&&e.originalEvent instanceof WheelEvent||typeof MouseEvent!="undefined"&&e.originalEvent instanceof MouseEvent||typeof Event!="undefined"&&e.originalEvent instanceof Event){marginTop-=72;if(marginTop+$(this).data("menu-height")<$(window).height())marginTop=$(window).height()-$(this).data("menu-height")}$(this).stop();$(this).animate({"margin-top":marginTop+"px"},
100,"linear")});this.$menu.on("mousemove",function(e){if(!$(this).hasClass("overflows"))return false;var $instance=$(this).parent().data("PopMenu.instance");var marginTop=parseInt($(this).css("margin-top"));var duration=0;if(e.pageY-$(window).scrollTop()<$instance.data.scrollerSize){duration=-marginTop;marginTop=0}else if(e.pageY-$(window).scrollTop()>$(window).height()-$instance.data.scrollerSize){duration=marginTop-($(window).height()-$(this).data("menu-height"));marginTop=$(window).height()-$(this).data("menu-height")}else{if($(this).data("is-scrolling-with-scroller")){$(this).stop();
$(this).data("is-scrolling-with-scroller",false)}return}$(this).stop();$(this).data("is-scrolling-with-scroller",true);$(this).animate({"margin-top":marginTop+"px"},duration*3,"linear",function(){$(this).data("is-scrolling-with-scroller",false)})});this.$.data("PopMenu.instance",this);this.options(options)};PopMenu.direction={RIGHT:2,LEFT:0,TOP:0,BOTTOM:1,HORIZONTAL:2,VERTICAL:1,RIGHT_BOTTOM:3,RIGHT_TOP:2,LEFT_BOTTOM:1,LEFT_TOP:0};
PopMenu.defaults={effect:"fade",duration:200,contextMenu:false,tmpFx:undefined,direction:PopMenu.direction.RIGHT_BOTTOM,scrollerSize:24};PopMenu.prototype.options=function(options){if(arguments.length==0)return this.data;this.data=$.extend(true,{},this.data,options);this.effect(this.data.effect);this.duration(this.data.duration);this.contextMenu(this.data.contextMenu)};
PopMenu.prototype.effect=function(effect){if(arguments.length==0)return this.data.effect;switch(effect){case "fade":case "slide":case "none":this.data.effect=effect;return true;default:return false}};PopMenu.prototype.duration=function(duration){if(arguments.length==0)return this.data.duration;this.data.duration=duration};
PopMenu.prototype.contextMenu=function(value){if(arguments.length==0)return this.data.contextMenu;this.data.contextMenu=value;this.$.removeClass("PopMenu");this.$.removeClass("PopMenu-TopMenu");if(this.data.contextMenu){this.$.addClass("PopMenu");this.$menu.addClass("PopMenu-TopMenu");$("body").append(this.$)}};
PopMenu.prototype.show=function(x,y,effect,duration){if(!this.contextMenu())return false;if(effect==null||effect=="default")effect=this.data.effect;if(duration===undefined)duration=this.data.duration;this.data.tmpFx=effect;this.$.css("visibility","hidden");this.$.show();var menuW=this.$menu.outerWidth();var menuH=this.$menu.outerHeight();this.$.hide();this.$.css("visibility","visible");if(x<0)x=0;if(x+menuW>$(window).width()){x-=menuW;if(x+menuW>$(window).width())x=$(window).width()-menuW}if(y<0)y=
0;if(y+menuH>$(window).height()){y-=menuH;if(y+menuH>$(window).height())y=$(window).height()-menuH}this.$menu.css("left",x+"px");this.$menu.css("top",y+"px");this.updateLabels();switch(effect){default:case "none":this.$.show();break;case "fade":this.$.fadeIn(duration);break;case "slide":this.$.slideDown(duration);break}};
PopMenu.prototype.showAsSubmenu=function(direction,effect,duration){var $parent=this.$.parent(".PopMenu-Item");if($parent.length==0)return false;if(effect==null||effect=="default")effect=this.data.effect;if(duration===undefined)duration=this.data.duration;if(direction===undefined)direction=this.data.direction;this.data.tmpFx=effect;this.$menu.removeClass("overflows");this.$menu.css("margin-top",0);if((direction&PopMenu.direction.HORIZONTAL)==PopMenu.direction.RIGHT){this.$menu.css("left","100%");
this.$menu.css("right","auto")}else{this.$menu.css("left","auto");this.$menu.css("right","100%")}if((direction&PopMenu.direction.VERTICAL)==PopMenu.direction.BOTTOM){this.$menu.css("top","0");this.$menu.css("bottom","auto")}else{this.$menu.css("top","auto");this.$menu.css("bottom","0")}this.$.css("visibility","hidden");this.$.show();var menuW=this.$menu.outerWidth();var menuH=this.$menu.outerHeight();var x=this.$menu.offset().left-$(window).scrollLeft();var y=this.$menu.offset().top-$(window).scrollTop();
this.$menu.data("menu-height",menuH);this.$menu.data("menu-width",menuW);this.$.hide();this.$.css("visibility","visible");var parentH=$parent.outerHeight();if((direction&PopMenu.direction.HORIZONTAL)==PopMenu.direction.RIGHT&&x+menuW>$(window).width()){this.$menu.css("left","auto");this.$menu.css("right","100%");this.data.direction=PopMenu.direction.direction&~PopMenu.direction.HORIZONTAL|PopMenu.direction.LEFT}if((direction&PopMenu.direction.HORIZONTAL)==PopMenu.direction.LEFT&&x<0){this.$menu.css("left",
"100%");this.$menu.css("right","auto");this.data.direction=PopMenu.direction.direction&~PopMenu.direction.HORIZONTAL|PopMenu.direction.RIGHT}if((direction&PopMenu.direction.VERTICAL)==PopMenu.direction.BOTTOM&&y+menuH>$(window).height()){this.$menu.css("top","auto");this.$menu.css("bottom","0");if(y+parentH-menuH<0){this.$menu.css("top",-y+"px");this.$menu.css("bottom","auto");this.$menu.addClass("overflows")}}if((direction&PopMenu.direction.VERTICAL)==PopMenu.direction.TOP&&y<0){this.$menu.css("top",
"0");this.$menu.css("bottom","auto");if(y+parentH>$(window).height()){this.$menu.css("top",-y+"px");this.$menu.css("bottom","auto");this.$menu.addClass("overflows")}}$parent.addClass("selected");switch(effect){default:case "none":this.$.show();break;case "fade":this.$.fadeIn(duration);break;case "slide":this.$.slideDown(duration);break}};
PopMenu.prototype.hide=function(effect,duration){if(effect==null)effect=this.data.tmpFx;if(duration===undefined)duration=this.data.duration;this.hideSubmenus(effect,duration);switch(effect){default:case "none":this.$.hide();break;case "fade":this.$.fadeOut(duration);break;case "slide":this.$.slideUp(duration);break}};
PopMenu.prototype.hideSubmenus=function(effect,duration){this.$.find(".PopMenu-Item.selected").removeClass("selected");switch(effect){default:case "none":this.$.find(".PopMenu-Container").hide();break;case "fade":this.$.find(".PopMenu-Container").fadeOut(duration);break;case "slide":this.$.find(".PopMenu-Container").slideUp(duration);break}};PopMenu.prototype.populateMenu=function(menu){var instance=this;$.each(menu,function(index,item){instance.append(index,item)})};
PopMenu.prototype.find=function(id){var $find=this.$menu.find("a[data-id="+id+"]");if($find.length==0)return null;else return $find.parent().data("PopMenu.instance")};PopMenu.prototype.append=function(id,item){item.id=id;var menuItem=new PopMenuItem(item);this.$menu.append(menuItem.$);return menuItem};PopMenu.prototype.insert=function(id,item,after){item.id=id;var $after=this.find(after).$;var menuItem=new PopMenuItem(item);$after.after(menuItem.$);return menuItem};
PopMenu.prototype.prepend=function(id,item){item.id=id;var menuItem=new PopMenuItem(item);this.$menu.prepend(menuItem.$);return menuItem};PopMenu.prototype.remove=function(id){var item=this.find(id);if(item!=null){item.$.remove();delete item}};PopMenu.prototype.updateLabels=function(force){this.$.find("a.PopMenu-Link").each(function(){var instance=$(this).parent().data("PopMenu.instance");instance.updateLabel(force)})};
PopMenu.prototype.selectedItem=function(){var selected=this.$.find(".PopMenu-Item.selected");if(selected.length>0)return selected.data("PopMenu.instance");else return null};
PopMenuItem=function(options){this.data=PopMenuItem.defaults;this.callbacks={};this.$=$('<li class="PopMenu-Item">'+'<a class="PopMenu-Link" data-name="'+options.id+'">'+"</a>"+'<span class="PopMenu-Icon"></span>'+"</li>");this.$link=this.$.children(".PopMenu-Link");this.$icon=this.$.children(".PopMenu-Icon");this.options(options);this.$link.click(function(e){if($(this).parent().hasClass("disabled"))return false;$(this).trigger("action",e);if($(this).parent().hasClass("PopMenu-HasMenu"))e.stopPropagation()});
this.$.mouseover(function(e){var instance=$(this).data("PopMenu.instance");var $container=$(this).parents(".PopMenu-Container:eq(0)");if($container.length>0){var container=$container.data("PopMenu.instance");if(container.selectedItem()!=instance){container.hideSubmenus();if(instance.submenu!=null)instance.submenu.showAsSubmenu(container.data.direction,container.data.effect,container.data.duration)}}else if(instance.submenu!=null)instance.submenu.showAsSubmenu(container.data.direction,container.data.effect,
container.data.duration);e.stopPropagation()});this.$.data("PopMenu.instance",this)};PopMenuItem.defaults={id:undefined,type:"item",label:undefined,key:undefined,action:undefined,href:undefined,target:undefined,icon:undefined,disabled:false,submenu:undefined,visible:true};
PopMenuItem.prototype.on=function(event,callback){var instance=this;if(typeof this.callbacks[event]!="object"){this.callbacks[event]=[];this.$link.on(event,function(){var args=arguments;$.each(instance.callbacks[event],function(index,item){item.apply(instance,args)})})}this.callbacks[event].push(callback)};
PopMenuItem.prototype.off=function(event){this.$link.off(event);delete this.callbacks[event];if(event=="click")this.$link.click(function(e){if($(this).parent().hasClass("disabled"))return false;$(this).trigger("action",e);if($(this).parent().hasClass("PopMenu-HasMenu"))e.stopPropagation()})};
PopMenuItem.prototype.options=function(options){if(arguments.length==0)return this.data;this.data=$.extend(true,{},this.data,options);this.id(this.data.id);this.key(this.data.key);this.type(this.data.type);this.icon(this.data.icon);this.label(this.data.label);this.disable(this.data.disabled);this.visible(this.data.visible);this.href(this.data.href);this.target(this.data.target);if(typeof this.data.action=="function"){this.off("action");this.on("action",this.data.action)}this.data.action=undefined;
if(typeof this.data.submenu=="object"){if(this.data.submenu instanceof PopMenu==false)this.data.submenu=new PopMenu(this.data.submenu);this.$.append(this.data.submenu.$);this.$.addClass("PopMenu-HasMenu");this.submenu=this.data.submenu}else{this.$.removeClass("PopMenu-HasMenu");delete this.submenu}};
PopMenuItem.prototype.icon=function(icon){if(arguments.length==0)return this.data.icon;this.$icon.css("background-image","none");this.$icon.attr("data-icon",null);if(icon==null)return;if(icon.indexOf(".")==-1)this.$icon.attr("data-icon",icon);else this.$icon.css("background-image",'url("'+icon+'")')};PopMenuItem.prototype.show=function(){this.data.visible=true;this.$.show()};PopMenuItem.prototype.hide=function(){this.data.visible=false;this.$.hide()};
PopMenuItem.prototype.visible=function(value){if(arguments.length==0)return this.data.visible;if(value)return this.show();else return this.hide()};PopMenuItem.prototype.id=function(id){if(arguments.length==0)return this.data.id;this.data.id=id;this.$link.attr("data-id",this.data.id)};PopMenuItem.prototype.key=function(key){if(arguments.length==0)return this.data.key;this.data.key=key;this.$link.attr("data-key",this.data.key)};
PopMenuItem.prototype.href=function(url){if(arguments.length==0)return this.data.href;this.data.href=url;if(this.data.href)this.$link.attr("href",this.data.href);else this.$link.attr("href",null)};PopMenuItem.prototype.target=function(target){if(arguments.length==0)return this.data.target;this.data.target=target;if(this.data.target)this.$link.attr("target",this.data.target);else this.$link.attr("target",null)};
PopMenuItem.prototype.label=function(label){if(arguments.length==0)return this.data.label;this.data.label=label;this.updateLabel(true)};PopMenuItem.prototype.type=function(type){if(arguments.length==0)return this.data.type;var invalid=false;switch(type){case "item":this.$.removeClass();this.$.addClass("PopMenu-Item");break;case "separator":this.$.removeClass();this.$.addClass("PopMenu-Separator");break;default:invalid=true;break}if(!invalid)this.data.type=type};
PopMenuItem.prototype.disable=function(disabled){if(disabled===undefined)return this.$.hasClass("disabled");if(disabled)this.$.addClass("disabled");else this.$.removeClass("disabled")};PopMenuItem.prototype.updateLabel=function(force){if(typeof this.data.label!="function"&&!force)return;if(typeof this.data.label=="function")var label=this.data.label();else var label=this.data.label;if(this.data.key!=null){pattern=new RegExp("("+this.data.key+")","i");label=label.replace(pattern,"<u>$1</u>")}this.$link.html(label)};
(function($){var methods={};var defaults={effect:"default",duration:null};$.fn.popmenu=function(){if(arguments.length==0){if(this.data("PopMenu.contextmenu")==null)methods.init.call(this,{});return this.data("PopMenu.contextmenu").data("PopMenu.instance")}if(typeof arguments[0]==="object")return methods.init.call(this,arguments[0]);else if($.isFunction(methods[arguments[0]])){var shift=[].shift;var firstArg=shift.apply(arguments);return methods[firstArg].apply(this,arguments)}};methods.init=function(menu,
options){var $this=this;if(menu instanceof PopMenu==false)var menu=new PopMenu(menu);menu.contextMenu(true);return $this.each(function(){var $this=$(this);var newOptions=$.extend(true,{},defaults,options);$this.data("PopMenu.contextmenu",menu.$);$this.data("PopMenu.options",newOptions);$this.on("contextmenu",methods.show)})};methods.show=function(e){var $elm=$(this);var $menu=$elm.data("PopMenu.contextmenu");var instance=$menu.data("PopMenu.instance");var options=$elm.data("PopMenu.options");if(e!=
null){var pageX=e.pageX;var pageY=e.pageY}else{var pageX=$elm.offset().left;var pageY=$elm.offset().top}var scrollLeft=$(window).scrollLeft();var scrollTop=$(window).scrollTop();var menuX=pageX-scrollLeft;var menuY=pageY-scrollTop;instance.show(menuX,menuY,options.effect,options.duration);return false};methods.hide=function(e){var $menu=$(this).data("PopMenu.contextmenu");var options=$(this).data("PopMenu.options");var instance=$menu.data("PopMenu.instance");instance.hide(options.effect,options.duration)}})(jQuery);

@ -0,0 +1,140 @@
/**
* jQuery PopMenu
*
* Default stylesheet for jQuery PopMenu.
*
* @version 1.0.0
* @author Fajar Chandra
* @since 2014.05.29
*/
/* CONTAINER */
.PopMenu {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 100000;
}
/* MENU */
.PopMenu-Menu {
box-shadow: 2px 2px 2px #000;
box-shadow: 2px 2px 2px rgba(0,0,0,.5);
background: #eee;
background: linear-gradient(#fff, #e0e0e0);
border: 1px solid #aaa;
color: #333;
list-style: none;
margin: 0;
padding: 0;
font-size: 12px;
font-family: 'Arial', sans-serif;
cursor: default;
line-height: 1.2em;
position: absolute;
top: 0;
left: 100%;
z-index: 100000;
}
/* TOP LEVEL MENU */
.PopMenu-TopMenu {
top: 0;
left: 0;
}
/* MENU ITEM */
.PopMenu-Item {
position: relative;
}
.PopMenu-Item > a {
position: relative;
color: #333;
text-decoration: none;
cursor: default;
display: block;
padding: 5px 15px 5px 30px;
min-width: 100px;
z-index: 1;
white-space: nowrap;
}
.PopMenu-Item.selected,
.PopMenu-Item:hover {
background: #43ace8;
}
.PopMenu-Item.selected > a,
.PopMenu-Item:hover > a {
color: #fff;
text-decoration: none;
}
.PopMenu-Item.disabled {
background: transparent;
}
.PopMenu-Item.disabled > a {
color: #aaa;
}
/* MENU ICON */
.PopMenu-Icon {
position: absolute;
left: 5px;
top: 50%;
margin-top: -8px;
height: 16px;
width: 16px;
font-size: 16px;
line-height: 16px;
color: #333;
display: block;
text-align: center;
z-index: 0;
background-repeat: no-repeat;
background-position: center center;
}
.PopMenu-Item.disabled .PopMenu-Icon {
color: #aaa;
}
/* MENU SEPARATOR */
.PopMenu-Separator {
display: block;
height: 1px;
background: #ccc;
margin: 0 2px;
}
.PopMenu-Separator * {
display: none;
}
/* SUBMENU */
.PopMenu-Item.PopMenu-HasMenu > a:after {
content: '▶';
display: block;
position: absolute;
right: 5px;
top: 4px;
height: 14px;
width: 14px;
text-align: center;
vertical-align: middle;
font-size: 8px;
line-height: 14px;
}

@ -7,12 +7,19 @@
<link rel="shortcut icon" href="{{url_for('static', filename='images/favicon.ico')}}">
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='jquery-ui/jquery-ui.min.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='growl/jquery.growl.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='popmenu/jquery.popmenu.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/main.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/tabs-scenario.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/tabs-ob.css')}}" />
</head>
<body>
<div id="menu" style="display:none;">
<input type="button" value="actions">
<input id="load-scenario" type="file" style="display:none;">
</div>
<div id="tabs">
<ul>
@ -150,6 +157,8 @@
<script src="{{url_for('static',filename='jquery-ui/jquery-ui.min.js')}}"></script>
<script src="{{url_for('static',filename='jinja/jinja.js')}}"></script>
<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>
gImagesBaseUrl = "{{url_for('static',filename='images')}}" ;
gGetTemplatesUrl = "{{url_for('get_templates')}}" ;

@ -1,29 +1,17 @@
""" Test HTML snippet generation. """
from selenium.webdriver.support.ui import Select
from vasl_templates.webapp.tests.utils import get_clipboard, get_stored_msg, find_child
from vasl_templates.webapp.tests.utils import set_template_params, get_clipboard, get_stored_msg, find_child
# ---------------------------------------------------------------------
def _test_snippet( webdriver, template_id, params, expected, expected2 ):
def _test_snippet( webdriver, template_id, params, expected, expected2 ): #pylint: disable=unused-argument
"""Do a single test."""
# set the template parameters
for key,val in params.items():
elem = next( c for c in ( \
find_child( webdriver, "{}[name='{}']".format(elem_type,key) ) \
for elem_type in ["input","textarea","select"]
) if c )
if elem.tag_name == "select":
Select(elem).select_by_value( val )
else:
elem.clear()
if val:
elem.send_keys( val )
set_template_params( params )
# generate the snippet
submit = find_child( webdriver, "input[class='generate'][data-id='{}']".format(template_id) )
submit = find_child( "input[class='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 types
from selenium.webdriver.support.ui import Select
from vasl_templates.webapp.tests.utils import get_nationalities, get_clipboard, get_stored_msg, find_child
from vasl_templates.webapp.tests.utils import select_tab, get_nationalities, get_clipboard, get_stored_msg, find_child
# ---------------------------------------------------------------------
@ -14,55 +14,48 @@ def test_ob_setup( webapp, webdriver ):
# initialize
webdriver.get( webapp.url_for( "main" ) )
# initialize
def select_ob_tab( player_id ):
"""Select the OB tab for the specified player."""
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob{}']".format( player_id ) )
elem.click()
# generate OB SETUP snippets for both players
select_ob_tab( 1 )
textarea1 = find_child( webdriver, "textarea[name='ob_setup_1']" )
select_tab( "ob1" )
textarea1 = find_child( "textarea[name='ob_setup_1']" )
textarea1.clear()
textarea1.send_keys( "setup here." )
btn1 = find_child( webdriver, "input[type='button'][data-id='ob_setup_1']" )
select_ob_tab( 2 )
textarea2 = find_child( webdriver, "textarea[name='ob_setup_2']" )
btn1 = find_child( "input[type='button'][data-id='ob_setup_1']" )
select_tab( "ob2" )
textarea2 = find_child( "textarea[name='ob_setup_2']" )
textarea2.clear()
textarea2.send_keys( "setup there." )
btn2 = find_child( webdriver, "input[type='button'][data-id='ob_setup_2']" )
btn2 = find_child( "input[type='button'][data-id='ob_setup_2']" )
btn2.click()
assert get_clipboard() == "[setup there.] (col=[OBCOL:russian/OBCOL2:russian])"
select_ob_tab( 1 )
select_tab( "ob1" )
btn1.click()
assert get_clipboard() == "[setup here.] (col=[OBCOL:german/OBCOL2:german])"
# change the player nationalities and generate the OB SETUP snippets again
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-scenario']" )
elem.click()
select_tab( "scenario" )
sel = Select(
find_child( webdriver, "select[name='player_1']" )
find_child( "select[name='player_1']" )
)
sel.select_by_value( "british" )
sel = Select(
find_child( webdriver, "select[name='player_2']" )
find_child( "select[name='player_2']" )
)
sel.select_by_value( "french" )
select_ob_tab( 1 )
select_tab( "ob1" )
btn1.click()
assert get_clipboard() == "[setup here.] (col=[OBCOL:british/OBCOL2:british])"
select_ob_tab( 2 )
select_tab( "ob2" )
btn2.click()
assert get_clipboard() == "[setup there.] (col=[OBCOL:french/OBCOL2:french])"
# set the snippet widths and generate the snippets again
select_ob_tab( 1 )
elem = find_child( webdriver, "input[name='ob_setup_width_1']" )
select_tab( "ob1" )
elem = find_child( "input[name='ob_setup_width_1']" )
elem.send_keys( "100px" )
btn1.click()
assert get_clipboard() == "[setup here.] (col=[OBCOL:british/OBCOL2:british]) (width=[100px])"
select_ob_tab( 2 )
elem = find_child( webdriver, "input[name='ob_setup_width_2']" )
select_tab( "ob2" )
elem = find_child( "input[name='ob_setup_width_2']" )
elem.send_keys( "200px" )
btn2.click()
assert get_clipboard() == "[setup there.] (col=[OBCOL:french/OBCOL2:french]) (width=[200px])"
@ -79,9 +72,8 @@ def test_nationality_specific( webapp, webdriver ):
# initialize
def set_scenario_date( date ):
"""Set the scenario date."""
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-scenario']" )
elem.click()
elem = find_child( webdriver, "#panel-scenario input[name='scenario_date']" )
select_tab( "scenario" )
elem = find_child( "#panel-scenario input[name='scenario_date']" )
elem.clear()
elem.send_keys( "{:02}/01/{:04}".format( date[1], date[0] ) )
@ -91,9 +83,8 @@ def test_nationality_specific( webapp, webdriver ):
def do_test( date, expected, warning ): #pylint: disable=missing-docstring
# test snippet generation
set_scenario_date( date )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob1']" )
elem.click()
elem = find_child( webdriver, "input[type='button'][data-id='pf']" )
select_tab( "ob1" )
elem = find_child( "input[type='button'][data-id='pf']" )
elem.click()
assert get_clipboard() == expected
# check if a warning was issued
@ -117,9 +108,8 @@ def test_nationality_specific( webapp, webdriver ):
def do_test( date, expected ): #pylint: disable=missing-docstring
# test snippet generation
set_scenario_date( date )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob1']" )
elem.click()
elem = find_child( webdriver, "input[type='button'][data-id='baz']" )
select_tab( "ob1" )
elem = find_child( "input[type='button'][data-id='baz']" )
elem.click()
assert get_clipboard() == expected
# check if a warning was issued
@ -151,18 +141,16 @@ def test_nationality_specific( webapp, webdriver ):
for nat in nationalities:
# change the nationality for player 1
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-scenario']" )
elem.click()
select_tab( "scenario" )
sel = Select(
find_child( webdriver, "select[name='player_1']" )
find_child( "select[name='player_1']" )
)
sel.select_by_value( nat )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob1']" )
elem.click()
select_tab( "ob1" )
# check the nationality-specific buttons
for button_id,expected in nationality_specific_buttons.items():
elem = find_child( webdriver, "input[type='button'][data-id='{}']".format( button_id ) )
elem = find_child( "input[type='button'][data-id='{}']".format( button_id ) )
if nat == expected[0]:
# the button should be shown for this nationality
assert elem.is_displayed()

@ -6,10 +6,10 @@ from vasl_templates.webapp.tests.utils import get_nationalities, find_child
# ---------------------------------------------------------------------
def _get_player( webdriver, player_id ):
def _get_player( webdriver, player_id ): #pylint: disable=unused-argument
"""Get the nationality of the specified player."""
sel = Select(
find_child( webdriver, "select[name='player_{}']".format( player_id ) )
find_child( "select[name='player_{}']".format( player_id ) )
)
return sel.first_selected_option.get_attribute( "value" )
@ -25,21 +25,21 @@ def test_player_change( webapp, webdriver ):
# make sure that the UI was updated correctly for the initial players
for player_no in [1,2]:
player_id = _get_player( webdriver, player_no )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob{}']".format( player_no ) )
elem = find_child( "#tabs .ui-tabs-nav a[href='#tabs-ob{}']".format( player_no ) )
assert elem.text.strip() == "{} OB".format( nationalities[player_id]["display_name"] )
# change player 1
sel = Select(
find_child( webdriver, "select[name='player_1']" )
find_child( "select[name='player_1']" )
)
sel.select_by_value( "finnish" )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob1']" )
elem = find_child( "#tabs .ui-tabs-nav a[href='#tabs-ob1']" )
assert elem.text.strip() == "{} OB".format( nationalities["finnish"]["display_name"] )
# change player 2
sel = Select(
find_child( webdriver, "select[name='player_2']" )
find_child( "select[name='player_2']" )
)
sel.select_by_value( "japanese" )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob2']" )
elem = find_child( "#tabs .ui-tabs-nav a[href='#tabs-ob2']" )
assert elem.text.strip() == "{} OB".format( nationalities["japanese"]["display_name"] )

@ -0,0 +1,138 @@
""" Test loading/saving scenarios. """
import json
import time
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 get_stored_msg, set_stored_msg, find_child, find_children
# ---------------------------------------------------------------------
def test_scenario_persistence( webapp, webdriver ):
"""Test loading/saving scenarios."""
# initialize
webdriver.get( webapp.url_for( "main", scenario_persistence=1 ) )
# initialize
def load_scenario_fields( fields ):
"""Load the scenario fields."""
for tab_id in fields:
select_tab( tab_id )
set_template_params( fields[tab_id] )
# load the scenario fields
scenario_params = {
"scenario": {
"scenario_name": "my test scenario", "scenario_location": "right here", "scenario_date": "12/31/1945",
"scenario_width": "101",
"player_1": "british", "player_1_elr": "1", "player_1_san": "2",
"player_2": "french", "player_2_elr": "3", "player_2_san": "4",
"victory_conditions": "just do it!", "victory_conditions_width": "102",
"ssr": [ "This is an SSR.", "This is another SSR." ],
"ssr_width": "103",
},
"ob1": {
"ob_setup_1": "Player 1's OB", "ob_setup_width_1": "201",
},
"ob2": {
"ob_setup_2": "Player 2's OB", "ob_setup_width_2": "301",
},
}
load_scenario_fields( scenario_params )
# save the scenario and check the results
saved_scenario = _save_scenario()
expected = {
k.upper(): v for tab in scenario_params.values() for k,v in tab.items()
}
assert saved_scenario == expected
# reset the scenario
elem = find_child( "#menu" )
elem.click()
elem = find_child( "a.PopMenu-Link[data-name='new']" )
elem.click()
elem = find_child( ".growl-close" )
elem.click()
time.sleep( 0.5 )
# check the save results
data = _save_scenario()
data2 = { k: v for k,v in data.items() if v }
assert data2 == {
"PLAYER_1": "german", "PLAYER_1_ELR": "5", "PLAYER_1_SAN": "2",
"PLAYER_2": "russian", "PLAYER_2_ELR": "5", "PLAYER_2_SAN": "2",
}
# load a scenario
_load_scenario( saved_scenario )
# make sure the scenario was loaded into the UI correctly
for tab_id in scenario_params:
select_tab( tab_id )
for field,val in scenario_params[tab_id].items():
if field == "ssr":
continue # nb: this requires special handling, we do it below
elem = next( c for c in ( \
find_child( "{}[name='{}']".format(elem_type,field) ) \
for elem_type in ["input","textarea","select"]
) if c )
if elem.tag_name == "select":
assert Select(elem).first_selected_option.get_attribute("value") == val
else:
assert elem.get_attribute("value") == val
ssrs = _get_ssrs()
assert ssrs == scenario_params["scenario"]["ssr"]
# ---------------------------------------------------------------------
def test_loading_ssrs( webapp, webdriver ):
"""Test loading SSR's."""
# initialize
webdriver.get( webapp.url_for( "main", scenario_persistence=1 ) )
_ = _save_scenario() # nb: force the "scenario-persistence" element to be created
# initialize
def do_test( ssrs ): # pylint: disable=missing-docstring
_load_scenario( { "SSR": ssrs } )
assert _get_ssrs() == ssrs
# load a scenario that has SSR's into a UI with no SSR's
do_test( [ "ssr 1", "ssr 2" ] )
# load a scenario that has more SSR's than are currently in the UI
do_test( [ "ssr 5", "ssr 6", "ssr 7", "ssr 8" ] )
# load a scenario that has fewer SSR's than are currently in the UI
do_test( [ "ssr 10", "ssr 11" ] )
# load a scenario that has no SSR's into a UI that has SSR's
do_test( [] )
# ---------------------------------------------------------------------
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()
def _save_scenario():
"""Save the scenario."""
elem = find_child( "#menu" )
elem.click()
elem = find_child( "a.PopMenu-Link[data-name='save']" )
elem.click()
data = get_stored_msg( "scenario_persistence" )
return json.loads( data )
def _get_ssrs():
"""Get the SSR's from the UI."""
select_tab( "scenario" )
return [ c.text for c in find_children("#ssr-sortable li") ]

@ -16,29 +16,17 @@ def test_ssr( webapp, webdriver ):
# initialize
expected = []
def add_ssr( val ):
"""Add a new SSR, and check that the SSR snippet is generated correctly."""
# add the SSR
def _add_ssr( val ):
expected.append( val )
elem = find_child( webdriver, "#add-ssr" )
elem.click()
edit_ssr( val )
def edit_ssr( val ):
"""Edit an SSR's content, and check that the SSR snippet is generated correctly."""
# edit the SSR content
textarea = find_child( webdriver, "#edit-ssr textarea" )
textarea.clear()
textarea.send_keys( val )
btn = next(
elem for elem in find_children(webdriver,".ui-dialog.edit-ssr button")
if elem.text == "OK"
)
btn.click()
# check the generated snippet
add_ssr( webdriver, val )
check_snippet()
def _edit_ssr( ssr_no, val ):
expected[ssr_no] = val
edit_ssr( webdriver, ssr_no, val )
check_snippet()
def check_snippet( width=None ):
"""Check the generated SSR snippet."""
btn = find_child( webdriver, "input[type='button'][data-id='ssr']" )
btn = find_child( "input[type='button'][data-id='ssr']" )
btn.click()
val = "\n".join( "(*) [{}]".format(e) for e in expected )
if width:
@ -46,32 +34,56 @@ def test_ssr( webapp, webdriver ):
assert html.unescape( get_clipboard() ) == val
# add an SSR and generate the SSR snippet
add_ssr( "This is my first SSR." )
_add_ssr( "This is my first SSR." )
# add an SSR that contains HTML
add_ssr( "This snippet contains <b>bold</b> and <i>italic</i> text." )
_add_ssr( "This snippet contains <b>bold</b> and <i>italic</i> text." )
# add a multi-line SSR
add_ssr( "line 1\nline 2\nline 3" )
_add_ssr( "line 1\nline 2\nline 3" )
# edit one of the SSR's
elems = find_children( webdriver, "#ssr-sortable li" )
assert len(elems) == 3
elem = elems[1]
ActionChains(webdriver).double_click( elem ).perform()
expected[1] = "This SSR was <i>modified</i>."
edit_ssr( expected[1] )
_edit_ssr( 1, "This SSR was <i>modified</i>." )
# delete one of the SSR's
elems = find_children( webdriver, "#ssr-sortable li" )
elems = find_children( "#ssr-sortable li" )
assert len(elems) == 3
elem = elems[1]
trash = find_child( webdriver, "#ssr-trash" )
trash = find_child( "#ssr-trash" )
ActionChains(webdriver).drag_and_drop( elem, trash ).perform()
del expected[1]
check_snippet()
# set the snippet width
elem = find_child( webdriver, "input[name='ssr_width']" )
elem = find_child( "input[name='ssr_width']" )
elem.send_keys( "300px" )
check_snippet( "300px" )
# ---------------------------------------------------------------------
def add_ssr( webdriver, val ):
"""Add a new SSR."""
elem = find_child( "#add-ssr" )
elem.click()
edit_ssr( webdriver, None, val )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def edit_ssr( webdriver, ssr_no, val ):
"""Edit an SSR's content."""
# locate the requested SSR and start editing it
if ssr_no is not None:
elems = find_children( "#ssr-sortable li" )
elem = elems[ ssr_no ]
ActionChains(webdriver).double_click( elem ).perform()
# edit the SSR
textarea = find_child( "#edit-ssr textarea" )
textarea.clear()
textarea.send_keys( val )
btn = next(
elem for elem in find_children(".ui-dialog.edit-ssr button")
if elem.text == "OK"
)
btn.click()

@ -2,14 +2,56 @@
import urllib.request
import json
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
_webdriver = None
# ---------------------------------------------------------------------
def set_template_params( params ):
"""Set template parameters."""
for key,val in params.items():
# check for SSR's (these require special handling)
if key == "ssr":
# add them in (nb: we don't consider any existing SSR's)
from vasl_templates.webapp.tests.test_ssr import add_ssr #pylint: disable=cyclic-import
for ssr in val:
add_ssr( _webdriver, ssr )
continue
# locate the next parameter
elem = next( c for c in ( \
find_child( "{}[name='{}']".format(elem_type,key) ) \
for elem_type in ["input","textarea","select"]
) if c )
# set the parameter value
if elem.tag_name == "select":
Select(elem).select_by_value( val )
else:
elem.clear()
if val:
elem.send_keys( val )
if key == "scenario_date":
elem.send_keys( Keys.ESCAPE ) # nb: force the calendar popup to close :-/
time.sleep( 0.25 )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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" )
@ -19,31 +61,40 @@ def get_nationalities( webapp ):
def get_stored_msg( msg_id ):
"""Get a message stored for us by the front-end."""
elem = find_child( _webdriver, "#"+msg_id )
elem = find_child( "#"+msg_id )
if not elem:
return None
return elem.text
# ---------------------------------------------------------------------
if elem.tag_name == "div":
return elem.text
assert elem.tag_name == "textarea"
return elem.get_attribute( "value" )
def get_clipboard() :
"""Get the contents of the clipboard."""
app = QApplication( [] ) #pylint: disable=unused-variable
clipboard = QApplication.clipboard()
return clipboard.text()
def set_stored_msg( msg_id, val ):
"""Set a message for the front-end."""
elem = find_child( "#"+msg_id )
assert elem.tag_name == "textarea"
_webdriver.execute_script( "arguments[0].value = arguments[1]", elem, val )
# ---------------------------------------------------------------------
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def find_child( elem, sel ):
def find_child( sel, parent=None ):
"""Find a single child element."""
try:
return elem.find_element_by_css_selector( sel )
return (parent if parent else _webdriver).find_element_by_css_selector( sel )
except NoSuchElementException:
return None
def find_children( elem, sel ):
def find_children( sel, parent=None ):
"""Find child elements."""
try:
return elem.find_elements_by_css_selector( sel )
return (parent if parent else _webdriver).find_elements_by_css_selector( sel )
except NoSuchElementException:
return None
# ---------------------------------------------------------------------
def get_clipboard() :
"""Get the contents of the clipboard."""
app = QApplication( [] ) #pylint: disable=unused-variable
clipboard = QApplication.clipboard()
return clipboard.text()

Loading…
Cancel
Save