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 # run the application
app = QApplication( sys.argv ) app = QApplication( sys.argv )
url = "http://localhost:{}/main".format( port ) url = "http://localhost:{}".format( port )
main_window = MainWindow( url ) main_window = MainWindow( url )
main_window.show() main_window.show()
ret_code = app.exec_() ret_code = app.exec_()

@ -6,10 +6,10 @@ from vasl_templates.webapp import app
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
@app.route( "/main" ) @app.route( "/" )
def main(): def main():
"""Return the main page.""" """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 { #tabs {
display: none ; display: none ;
position: absolute ; top: 5px ; bottom: 5px ; left: 5px ; right: 5px ; 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-warning_", "" ) ;
storeMsgForTestSuite( "_last-error_", "" ) ; storeMsgForTestSuite( "_last-error_", "" ) ;
// collect all the template parameters // unload the template parameters
var params = {} ; var params = unload_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
var template_id = $btn.data( "id" ) ; var template_id = $btn.data( "id" ) ;
if ( template_id === "ob_setup_1" ) { if ( template_id === "ob_setup_1" ) {
template_id = "ob_setup" ; 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_COLOR_2 = gNationalities[params.PLAYER_2].ob_colors[1] ;
params.OB_SETUP_WIDTH = params.OB_SETUP_WIDTH_2 ; 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 // extract the scenario date components
var scenario_date = $("input[name='scenario_date']").datepicker( "getDate" ) ; var scenario_date = $("input[name='scenario_date']").datepicker( "getDate" ) ;
@ -90,10 +62,6 @@ function generate_snippet( $btn )
params.PF_CHECK_DRM = "" ; params.PF_CHECK_DRM = "" ;
params.PF_CHECK_DR = 3 ; 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 // generate BAZ parameters
if ( params.SCENARIO_YEAR >= 1945 ) { if ( params.SCENARIO_YEAR >= 1945 ) {
@ -113,6 +81,28 @@ function generate_snippet( $btn )
params.BAZ_TOKILL = 13 ; params.BAZ_TOKILL = 13 ;
params.BAZ_RANGE = 4 ; 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 ( template_id === "baz" ) {
if ( params.SCENARIO_DATE === "" || params.SCENARIO_YEAR <= 1941 || (params.SCENARIO_YEAR == 1942 && params.SCENARIO_MONTH < 11) ) if ( params.SCENARIO_DATE === "" || params.SCENARIO_YEAR <= 1941 || (params.SCENARIO_YEAR == 1942 && params.SCENARIO_MONTH < 11) )
showWarningMsg( "BAZ are only available from November 1942." ) ; 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." ) ; 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 () { $(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 // initialize
$("#tabs").tabs( { $("#tabs").tabs( {
heightStyle: "fill", heightStyle: "fill",
@ -45,14 +70,14 @@ $(document).ready( function () {
for ( var i=0 ; i <= 5 ; ++i ) // nb: A19.1: ELR is 0-5 for ( var i=0 ; i <= 5 ; ++i ) // nb: A19.1: ELR is 0-5
buf.push( "<option value='" + i + "'>" + i + "</option>" ) ; buf.push( "<option value='" + i + "'>" + i + "</option>" ) ;
buf = buf.join( "" ) ; buf = buf.join( "" ) ;
$("select[name='player_1_elr']").html( buf ).val( 5 ) ; $("select[name='player_1_elr']").html( buf ) ;
$("select[name='player_2_elr']").html( buf ).val( 5 ) ; $("select[name='player_2_elr']").html( buf ) ;
buf = [ "<option></option>" ] ; // nb: allow scenarios that have no SAN buf = [ "<option></option>" ] ; // nb: allow scenarios that have no SAN
for ( i=2 ; i <= 7 ; ++i ) // nb: A14.1: SAN is 2-7 for ( i=2 ; i <= 7 ; ++i ) // nb: A14.1: SAN is 2-7
buf.push( "<option value='" + i + "'>" + i + "</option>" ) ; buf.push( "<option value='" + i + "'>" + i + "</option>" ) ;
buf = buf.join( "" ) ; buf = buf.join( "" ) ;
$("select[name='player_1_san']").html( buf ).val( 2 ) ; $("select[name='player_1_san']").html( buf ) ;
$("select[name='player_2_san']").html( buf ).val( 2 ) ; $("select[name='player_2_san']").html( buf ) ;
// load the nationalities // load the nationalities
$.getJSON( gGetNationalitiesUrl, function(data) { $.getJSON( gGetNationalitiesUrl, function(data) {
@ -60,12 +85,9 @@ $(document).ready( function () {
var buf = [] ; var buf = [] ;
for ( var id in gNationalities ) for ( var id in gNationalities )
buf.push( "<option value='" + id + "'>" + gNationalities[id].display_name + "</option>" ) ; buf.push( "<option value='" + id + "'>" + gNationalities[id].display_name + "</option>" ) ;
on_player_change( $("select[name='player_1']").html( buf ) ;
$("select[name='player_1']").html( buf ).val( "german" ) $("select[name='player_2']").html( buf ) ;
) ; on_new_scenario( false ) ;
on_player_change(
$("select[name='player_2']").html( buf ).val( "russian" )
) ;
} ).fail( function( xhr, status, errorMsg ) { } ).fail( function( xhr, status, errorMsg ) {
showErrorMsg( "Can't get the nationalities:<pre>" + escapeHTML(errorMsg) + "</pre>" ) ; 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="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='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='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/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-scenario.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/tabs-ob.css')}}" /> <link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/tabs-ob.css')}}" />
</head> </head>
<body> <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"> <div id="tabs">
<ul> <ul>
@ -150,6 +157,8 @@
<script src="{{url_for('static',filename='jquery-ui/jquery-ui.min.js')}}"></script> <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='jinja/jinja.js')}}"></script>
<script src="{{url_for('static',filename='growl/jquery.growl.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> <script>
gImagesBaseUrl = "{{url_for('static',filename='images')}}" ; gImagesBaseUrl = "{{url_for('static',filename='images')}}" ;
gGetTemplatesUrl = "{{url_for('get_templates')}}" ; gGetTemplatesUrl = "{{url_for('get_templates')}}" ;

@ -1,29 +1,17 @@
""" Test HTML snippet generation. """ """ Test HTML snippet generation. """
from selenium.webdriver.support.ui import Select from vasl_templates.webapp.tests.utils import set_template_params, get_clipboard, get_stored_msg, find_child
from vasl_templates.webapp.tests.utils import 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.""" """Do a single test."""
# set the template parameters # set the template parameters
for key,val in params.items(): set_template_params( params )
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 )
# generate the snippet # 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() submit.click()
snippet = get_clipboard() snippet = get_clipboard()
lines = [ l.strip() for l in snippet.split("\n") ] lines = [ l.strip() for l in snippet.split("\n") ]

@ -4,7 +4,7 @@ import types
from selenium.webdriver.support.ui import Select 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 # initialize
webdriver.get( webapp.url_for( "main" ) ) 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 # generate OB SETUP snippets for both players
select_ob_tab( 1 ) select_tab( "ob1" )
textarea1 = find_child( webdriver, "textarea[name='ob_setup_1']" ) textarea1 = find_child( "textarea[name='ob_setup_1']" )
textarea1.clear() textarea1.clear()
textarea1.send_keys( "setup here." ) textarea1.send_keys( "setup here." )
btn1 = find_child( webdriver, "input[type='button'][data-id='ob_setup_1']" ) btn1 = find_child( "input[type='button'][data-id='ob_setup_1']" )
select_ob_tab( 2 ) select_tab( "ob2" )
textarea2 = find_child( webdriver, "textarea[name='ob_setup_2']" ) textarea2 = find_child( "textarea[name='ob_setup_2']" )
textarea2.clear() textarea2.clear()
textarea2.send_keys( "setup there." ) 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() btn2.click()
assert get_clipboard() == "[setup there.] (col=[OBCOL:russian/OBCOL2:russian])" assert get_clipboard() == "[setup there.] (col=[OBCOL:russian/OBCOL2:russian])"
select_ob_tab( 1 ) select_tab( "ob1" )
btn1.click() btn1.click()
assert get_clipboard() == "[setup here.] (col=[OBCOL:german/OBCOL2:german])" assert get_clipboard() == "[setup here.] (col=[OBCOL:german/OBCOL2:german])"
# change the player nationalities and generate the OB SETUP snippets again # change the player nationalities and generate the OB SETUP snippets again
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-scenario']" ) select_tab( "scenario" )
elem.click()
sel = Select( sel = Select(
find_child( webdriver, "select[name='player_1']" ) find_child( "select[name='player_1']" )
) )
sel.select_by_value( "british" ) sel.select_by_value( "british" )
sel = Select( sel = Select(
find_child( webdriver, "select[name='player_2']" ) find_child( "select[name='player_2']" )
) )
sel.select_by_value( "french" ) sel.select_by_value( "french" )
select_ob_tab( 1 ) select_tab( "ob1" )
btn1.click() btn1.click()
assert get_clipboard() == "[setup here.] (col=[OBCOL:british/OBCOL2:british])" assert get_clipboard() == "[setup here.] (col=[OBCOL:british/OBCOL2:british])"
select_ob_tab( 2 ) select_tab( "ob2" )
btn2.click() btn2.click()
assert get_clipboard() == "[setup there.] (col=[OBCOL:french/OBCOL2:french])" assert get_clipboard() == "[setup there.] (col=[OBCOL:french/OBCOL2:french])"
# set the snippet widths and generate the snippets again # set the snippet widths and generate the snippets again
select_ob_tab( 1 ) select_tab( "ob1" )
elem = find_child( webdriver, "input[name='ob_setup_width_1']" ) elem = find_child( "input[name='ob_setup_width_1']" )
elem.send_keys( "100px" ) elem.send_keys( "100px" )
btn1.click() btn1.click()
assert get_clipboard() == "[setup here.] (col=[OBCOL:british/OBCOL2:british]) (width=[100px])" assert get_clipboard() == "[setup here.] (col=[OBCOL:british/OBCOL2:british]) (width=[100px])"
select_ob_tab( 2 ) select_tab( "ob2" )
elem = find_child( webdriver, "input[name='ob_setup_width_2']" ) elem = find_child( "input[name='ob_setup_width_2']" )
elem.send_keys( "200px" ) elem.send_keys( "200px" )
btn2.click() btn2.click()
assert get_clipboard() == "[setup there.] (col=[OBCOL:french/OBCOL2:french]) (width=[200px])" assert get_clipboard() == "[setup there.] (col=[OBCOL:french/OBCOL2:french]) (width=[200px])"
@ -79,9 +72,8 @@ def test_nationality_specific( webapp, webdriver ):
# initialize # initialize
def set_scenario_date( date ): def set_scenario_date( date ):
"""Set the scenario date.""" """Set the scenario date."""
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-scenario']" ) select_tab( "scenario" )
elem.click() elem = find_child( "#panel-scenario input[name='scenario_date']" )
elem = find_child( webdriver, "#panel-scenario input[name='scenario_date']" )
elem.clear() elem.clear()
elem.send_keys( "{:02}/01/{:04}".format( date[1], date[0] ) ) 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 def do_test( date, expected, warning ): #pylint: disable=missing-docstring
# test snippet generation # test snippet generation
set_scenario_date( date ) set_scenario_date( date )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob1']" ) select_tab( "ob1" )
elem.click() elem = find_child( "input[type='button'][data-id='pf']" )
elem = find_child( webdriver, "input[type='button'][data-id='pf']" )
elem.click() elem.click()
assert get_clipboard() == expected assert get_clipboard() == expected
# check if a warning was issued # 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 def do_test( date, expected ): #pylint: disable=missing-docstring
# test snippet generation # test snippet generation
set_scenario_date( date ) set_scenario_date( date )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob1']" ) select_tab( "ob1" )
elem.click() elem = find_child( "input[type='button'][data-id='baz']" )
elem = find_child( webdriver, "input[type='button'][data-id='baz']" )
elem.click() elem.click()
assert get_clipboard() == expected assert get_clipboard() == expected
# check if a warning was issued # check if a warning was issued
@ -151,18 +141,16 @@ def test_nationality_specific( webapp, webdriver ):
for nat in nationalities: for nat in nationalities:
# change the nationality for player 1 # change the nationality for player 1
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-scenario']" ) select_tab( "scenario" )
elem.click()
sel = Select( sel = Select(
find_child( webdriver, "select[name='player_1']" ) find_child( "select[name='player_1']" )
) )
sel.select_by_value( nat ) sel.select_by_value( nat )
elem = find_child( webdriver, "#tabs .ui-tabs-nav a[href='#tabs-ob1']" ) select_tab( "ob1" )
elem.click()
# check the nationality-specific buttons # check the nationality-specific buttons
for button_id,expected in nationality_specific_buttons.items(): 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]: if nat == expected[0]:
# the button should be shown for this nationality # the button should be shown for this nationality
assert elem.is_displayed() 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.""" """Get the nationality of the specified player."""
sel = Select( 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" ) 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 # make sure that the UI was updated correctly for the initial players
for player_no in [1,2]: for player_no in [1,2]:
player_id = _get_player( webdriver, player_no ) 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"] ) assert elem.text.strip() == "{} OB".format( nationalities[player_id]["display_name"] )
# change player 1 # change player 1
sel = Select( sel = Select(
find_child( webdriver, "select[name='player_1']" ) find_child( "select[name='player_1']" )
) )
sel.select_by_value( "finnish" ) 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"] ) assert elem.text.strip() == "{} OB".format( nationalities["finnish"]["display_name"] )
# change player 2 # change player 2
sel = Select( sel = Select(
find_child( webdriver, "select[name='player_2']" ) find_child( "select[name='player_2']" )
) )
sel.select_by_value( "japanese" ) 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"] ) 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 # initialize
expected = [] expected = []
def add_ssr( val ): def _add_ssr( val ):
"""Add a new SSR, and check that the SSR snippet is generated correctly."""
# add the SSR
expected.append( val ) expected.append( val )
elem = find_child( webdriver, "#add-ssr" ) add_ssr( webdriver, val )
elem.click() check_snippet()
edit_ssr( val ) def _edit_ssr( ssr_no, val ):
def edit_ssr( val ): expected[ssr_no] = val
"""Edit an SSR's content, and check that the SSR snippet is generated correctly.""" edit_ssr( webdriver, ssr_no, val )
# 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
check_snippet() check_snippet()
def check_snippet( width=None ): def check_snippet( width=None ):
"""Check the generated SSR snippet.""" """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() btn.click()
val = "\n".join( "(*) [{}]".format(e) for e in expected ) val = "\n".join( "(*) [{}]".format(e) for e in expected )
if width: if width:
@ -46,32 +34,56 @@ def test_ssr( webapp, webdriver ):
assert html.unescape( get_clipboard() ) == val assert html.unescape( get_clipboard() ) == val
# add an SSR and generate the SSR snippet # 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 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 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 # edit one of the SSR's
elems = find_children( webdriver, "#ssr-sortable li" ) _edit_ssr( 1, "This SSR was <i>modified</i>." )
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] )
# delete one of the SSR's # delete one of the SSR's
elems = find_children( webdriver, "#ssr-sortable li" ) elems = find_children( "#ssr-sortable li" )
assert len(elems) == 3 assert len(elems) == 3
elem = elems[1] elem = elems[1]
trash = find_child( webdriver, "#ssr-trash" ) trash = find_child( "#ssr-trash" )
ActionChains(webdriver).drag_and_drop( elem, trash ).perform() ActionChains(webdriver).drag_and_drop( elem, trash ).perform()
del expected[1] del expected[1]
check_snippet() check_snippet()
# set the snippet width # set the snippet width
elem = find_child( webdriver, "input[name='ssr_width']" ) elem = find_child( "input[name='ssr_width']" )
elem.send_keys( "300px" ) elem.send_keys( "300px" )
check_snippet( "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 urllib.request
import json import json
import time
from PyQt5.QtWidgets import QApplication 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
_webdriver = None _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 ): def get_nationalities( webapp ):
"""Get the nationalities table.""" """Get the nationalities table."""
url = webapp.url_for( "get_nationalities" ) url = webapp.url_for( "get_nationalities" )
@ -19,31 +61,40 @@ def get_nationalities( webapp ):
def get_stored_msg( msg_id ): def get_stored_msg( msg_id ):
"""Get a message stored for us by the front-end.""" """Get a message stored for us by the front-end."""
elem = find_child( _webdriver, "#"+msg_id ) elem = find_child( "#"+msg_id )
if not elem: if not elem:
return None 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() : def set_stored_msg( msg_id, val ):
"""Get the contents of the clipboard.""" """Set a message for the front-end."""
app = QApplication( [] ) #pylint: disable=unused-variable elem = find_child( "#"+msg_id )
clipboard = QApplication.clipboard() assert elem.tag_name == "textarea"
return clipboard.text() _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.""" """Find a single child element."""
try: try:
return elem.find_element_by_css_selector( sel ) return (parent if parent else _webdriver).find_element_by_css_selector( sel )
except NoSuchElementException: except NoSuchElementException:
return None return None
def find_children( elem, sel ): def find_children( sel, parent=None ):
"""Find child elements.""" """Find child elements."""
try: try:
return elem.find_elements_by_css_selector( sel ) return (parent if parent else _webdriver).find_elements_by_css_selector( sel )
except NoSuchElementException: except NoSuchElementException:
return None 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