Sunday, November 29, 2009

Sunday, November 22, 2009

Blogspot vs Wikidot

I always thought it would be fun and easy to write posts here on blogspot. What can be easier, you can even use google doc to prepare your posts and than publish it directly to blogspot. This may be true, but not for posts where you have source code. I know many bloggers use blogspot and can manage to write posts with source code examples. But for me it turns out I spend more time editing and adjusting formatting the post than writing it.

From now I will place posts with source code to http://it-bubbles.wikidot.com/blog, which seems more programmers friendly. Wikidot is Wiki based and even though it does not have such popularity as blogspot, I find it much easier to use. I'll try to keep references to posts here at blogspot as well.

Tuesday, May 26, 2009

Test different language locale and browser type with Grails WebTest plugin.


Grails
WebTest plugin allows you to integrate Canoo WebTest into your Grails project and create tests for automatic testing. In this post we take a look at possibility to run your tests with simulation of different locales and browser types. You can use described approach for other WebTest configuration parameters.

For examples I have used grails-1.1.1, Webtest plugin 1.1.4.2 and WebTest 3.0.

Create test suite

Make sure the WebTest plugin is installed and available in your project, create some test:

grails install-plugin webtest
grails create-webtest

These commands will install WebTest plugin, make WebTest directory within your project, create TestSuite class and Test class for your domain class.
In example project (download below) my domain class is called Car, so calling grails create-webtest Car creates TestSuite.groovy and CarTest.groovy files in webtest/tests directory. The test class already contains generated tests for your domain class. You can use following command to run them:
grails run-webtest

When inspecting test results, you can see tests are run with your default language locale.

Implement static method that runs before each test.

The browser type or language locale in WebTest is set in config (configuration option browser) and header (parameter Accept-Language) elements. Before each test is run, we will explicitly set these parameters to make sure the test is run with appropriate locale and browser emulation. For this we need a way to call a (static) method before each test is run.

Let us put this static method into TestSuite class and call it globalSetUp:

public static lang = 'en'
public static browser = 'FF3'
public static Properties i18nProperties = new Properties();

def static globalSetUp(def ant) {
ant.config(summary: true, browser: browser) {
header name: 'Accept-Language', value: lang
}
}

You can see the globalSetUp method takes WebTest ant builder as a parameter and prepares config and header elements before test is created and run. It sets browser type and locale according to the values in lang and browser static variables. The static variable i18nProperties will be discussed later.

Each test class inherits WebTest class. In case setUp method exists in the test class, it is called before any test method in test class is run (see WebTest.webtestMethod in WebTest.groovy).

We have two options to call our static method before any test from test class is invoked:

  • add setUp method to each test class, which in turns calls our static method
  • make new class derived from WebTest class and implement setUp method in this class calling globalSetUp; change each test class in a way that it is inherited from this new class instead of WebTest class

(Note:In one project I have encountered path problems with second option - new class couldn not be found - but in other project there was no problem; do not know why yet.)

Let's choose first option and add to each test class following setUp method:

def setUp() {
TestSuite.globalSetUp(ant)
}

Run suite with different browser emulation

As described in config WebTest (version 3.0) can emulate following browsers:

  • Firefox3 (FF3)
  • Firefox2 (FF2)
  • InternetExplorer6 (IE6)
  • InternetExplorer7 (IE7)

To set browser emulation, change value of static variable browser:

e.g.:

public static browser = 'IE6'


and run tests:


grails run-webtest

In output you should see text:

Surfing with browser IE6

Run suite with different locales.

To run TestSuite with different locale, change value of lang static variable.
E.g.:

public static lang = 'de'

and run tests:

grails run-webtest

In output you should see text:

Configured header "Accept-Language": de


The web pages in your text should reflect selected locale.

verifyText for given locale

verifyText command makes sure there is a given text on the web page. In different locale the text will be different and we want to make sure our test suite takes this into consideration.

The text in browser is (usually) rendered with g:message tag to resolve text from message_.properties file. In example project the the Car list view has header "CAR LIST" in English version and "AUTO-LISTE" in German version. This is achieved by line in views/car/list.gsp file. This line gets the text from corresponding i18n properties file.

If we want to test this text, we can add following line to CarTest.groovy:

verifyText 'CAR LIST'

This line passes with English locale, but fails with German locale. To fix it, we will read message properties for given language into
i18nProperties static variable (introduced earlier) before the tests are run.

In TestSuite.groovy file find


for (file in scanner) {
def test = getClass().classLoader.parseClass(file).newInstance()
test.ant = ant
test.suite()
}
and change it to:
TestSuite.i18nProperties.load(new FileInputStream("grails-app/i18n/messages_${lang}.properties"));
for (file in scanner) {
def test = getClass().classLoader.parseClass(file).newInstance()
test.ant = ant
test.suite()
}

Modify previous verifyText (so it uses text from properties file):

verifyText TestSuite.i18nProperties.getProperty("webtest-multi.carlist")

Now, the test will pass for both, English and German locale.

Multiple locales and browser emulations in one test suite

If you want to change locale or browser type, you have to change static variable lang or browser before each run. Another option is to modify test suite method to iterate through browsers and/or locales and run test suite several times.
e.g.:

TestSuite.i18nProperties.load(new FileInputStream("grails-app/i18n/messages_${lang}.properties"));
['IE6', 'IE7', 'FF2', 'FF3'].each { br ->
browser = br
for (file in scanner) {
def test = getClass().classLoader.parseClass(file).newInstance()
test.ant = ant
test.suite()
}
}

or:

['de', 'en'].each { lo ->
lang = lo
TestSuite.i18nProperties.load(new FileInputStream("grails-app/i18n/messages_${lang}.properties"));
for (file in scanner) {
def test = getClass().classLoader.parseClass(file).newInstance()
test.ant = ant
test.suite()
}
}


or even:

['de', 'en'].each { lo ->
lang = lo
TestSuite.i18nProperties.load(new FileInputStream("grails-app/i18n/messages_${lang}.properties"));
['IE6', 'IE7', 'FF2', 'FF3'].each { br ->
browser = br
for (file in scanner) {
def test = getClass().classLoader.parseClass(file).newInstance()
test.ant = ant
test.suite()
}
}
}

At first glance, this approach seems obvious. Unfortunately it is only useful for test suites which do not change the context (database) after they pass or fail or for test suites that do not depend on this context (for example no dependency on number of rows in the table). If you do not have such tests, you have to reset the context after each run of suite (after call of test.suite() method) in some way. Currently I do not know any proper way how to do it.

Conclusion

WebTest is testing framework which is well integrated into grails. In this post you have seen how to modify attributes of config and header elements for your tests and how to use this modification to emulate different browser types and change language locales. This is can be even more useful, if you can iterate trough the array of possible values directly in TestSuite script. Unfortunately this is only possible for tests that do not change database context.

External links:

Friday, March 27, 2009

Enhance Grails g:datePicker with jscalendar

Introduction


(Note 2009-05-17: Calendar plugin 1.2.0-SNAPSHOT broke interface of jscalendar by adding custom code to calendar.js file. You either have to use earlier calendar plugin, replace calendar.js file with original or change the implementation to adapt to Calendar plugin 1.2.0., if possible. Another option is to use original jscalendar without support of calendar plugin at all. Personally I do not like the original - non plugin - calendar.js is modified by plugin developer. Plugin should be wrapper around calendar.js.)



Grails has g:datePicker gsp tag to create editable date and time field.
With g:datePicker you can choose current date and time through combo boxes (selects).



There is also calendar plugin which brings jscalendar into grails. You can open jscalendar (e.g. clicking on calendar picture) and choose date and time. It is not possible to edit date in associated text field (there is JIRA issue for this).



Very often, you would like to have both functionality combined. I have created javascript function that links g:datePicker with jscalendar via onclick action.

The result looks like this (by clicking on image, cale
ndar pop-ups):



I have tried to meet following requirements:
  • reuse g:datePicker configuration in associated jscalendar as much as possible (no need to repeat configuration parameters)
  • automatic synchronization between value in g:datePicker and jscalendar
  • support for internationalization - add possibility to use time format from calendar lang file, use first day of week from calendar lang file
  • simple to use

When user clicks on calendar image/button associated with g:datePicker, jscalendar widget is created and displayed. It already has date (and time in case g:datePicker precision parameter is hour or minute) filled with date corresponding to values in g:datePicker combo boxes. By changing the date (and time) in jscalendar the g:datePicker values change as well. Single click on the date closes calendar.



If javascript is disabled (e.g. with NoScript Firefox add on), user can still select date and time through g:datePicker.

Implementation

Javascript function is called showCalendar and it links jscalendar
with g:datePicker.

g:datePicker accepts following arguments (see documentation):

name, value, precision, years and noSelection

When examining the source code for g:datePicker, you can see it creates several combo box elements with following ids:

${name}_year
${name}_month
${name}_date
${name}_hour
${name}_minute


According to precision parameter, some combo boxes may not be created . Year combo box is always created.

In gsp page you link
showCalendar function with g:datePicker in following way:
<g:datePicker name="reminderAt" value="${eventInstance?.reminderAt}" ></g:datePicker>
<img src="${createLinkTo(
dir: org.codehaus.groovy.grails.plugins.PluginManagerHolder.currentPluginManager().getGrailsPlugin("calendar").getPluginPath(),
file: "/images/skin/calendar.png")}"
id="reminderAt-trigger" alt="Date"
onclick="showCalendar('reminderAt');"/>
(Note: In this example I have tried to re-use calendar image directly from the calendar plugin. That is why the dir path is so cumbersome. If you know of better way how to get plugin's dir, please let me know. You can use path to any other image or use button instead.)

The parameters of showCalendar function are:
  • name of g:datePicker to link function with
  • boolean value indicating if time is displayed in 24h format - this parameter is optional and can be omitted (default is true = 24 hour format - this is in line with g:datePicker format). Preferred way to set time format should be through calendar-{lang}.js (see below).
From calendar-{lang}.js following parameters are used:
  • Calendar._FD - first day of week (if not specified, default value is 0 = Sunday)
  • Calendar._TIME24 - if true, 24h time format is used (if specified it overwrites second parameter of showCalendar function). This parameter is newly introduced by my implementation (as I thing this is lang specific like Calendar._FD) and you have to modify calendar-{lang).js if you want to use it. Otherwise pass time format as second parameter when calling showCalendar.

Calendar creation

Upon creation of jscalendar the fu
nction tries to find out most of the parameters from associated g:datePicker.

Following jscalendar parameters are configured at that time:
  • date corresponding to the date in g:datePicker or today's date if year, month or day in the g:datePicker corresponds to noSelection value
  • allowed year range corresponding to possible values in ${name}_year combo box element
  • presence of time setup - time setup is part of jscalendar only if ${name}_hour element exists
  • time format is set to 24h format by default; it can be changed to 12h format by passing false as second (optional) parameter to the function; if time format is specified in calendar-{lang}.js (Calendar._TIME24), it is used instead, with higher priority.
Calendar is displayed next to ${name}_year combo box element (this element is always present regardless of precision attribute).

Note: Since jscalednar is linked to g:datePicker, there is no need to configure date format in any way, which makes configuration easy.

Selection - event handling:

When date is changed in
jscalendar, event function selected is called. It extracts selected Date and updates g:datePicker combo boxes accordingly.

Source code:

/*
File: calendar-gdatepicker.js
Grails g:datePicker binding to the
DHTML Calendar www.dynarch.com/projects/calendar
version: 1.1
history:
1.1 2009-03-29 firstDayOfWeek is retrieved from calendar-{lang}.js file Calendar._FD
time24 format is retrieved from calendar-{lang}.js file Calendar._TIME24
(Michal Novak)
1.0 2009-03-28 Initial version (Michal Novak)

Copyright 2009, Michal Novak, bubbles.way@gmail.com
http://it-bubbles.blogspot.com/

This script is distributed under the GNU Lesser General Public License.
Read the entire license text here: http://www.gnu.org/licenses/lgpl.html

This script is base on examples on following page:
http://www.dynarch.com/static/jscalendar-1.0/index.html
*/

// return value given by index of combo box element or null if combo box element is null
function comboGetValueAt(comboBoxElem, index) {
if (comboBoxElem != null) {
if (comboBoxElem.options.length > index) {
return comboBoxElem.options[index].value;
}
}
return null;
}

// return selected value of combo box element or null if combo box element is null
function comboSelectedValue(comboBoxElem) {
if (comboBoxElem != null) {
return comboGetValueAt(comboBoxElem, comboBoxElem.selectedIndex)
}
return null;
}

// select value in combo box element (if combo box element is not null)
function selectCombo(comboBoxElem, value) {
if (comboBoxElem != null) {
for (var i = 0; i < comboBoxElem.options.length; i++) {
if (comboBoxElem.options[i].value == value &&
comboBoxElem.options[i].value != "") { //empty string is for "noSelection handling as "" == 0 in js
comboBoxElem.options[i].selected = true;
break
}
}
}
}

// This function gets called when the end-user clicks on some date.
function selected(cal) {
selectCombo(cal.g_year, cal.date.getFullYear())
selectCombo(cal.g_month, cal.date.getMonth() + 1)
selectCombo(cal.g_day, cal.date.getDate())
selectCombo(cal.g_hour, cal.date.getHours())
selectCombo(cal.g_minute, cal.date.getMinutes())
if (cal.dateClicked)
//close the calendar on single-click.
cal.callCloseHandler();
}

// This function gets called when the end-user clicks on the _selected_ date,
// or clicks on the "Close" button. It just hides the calendar without destroying it.
function closeHandler(cal) {
cal.hide() // hide the calendar
_dynarch_popupCalendar = null
}

// This function shows the calendar under the year element of the g:datePicker
// first parameter is id (name) of g:datePicker element
// second parameter (optional) specifies if time should be displayed in 24h format (default is true)
// other necessary configuration is extracted directly from g:datePicker
function showCalendar(datePickerId,time24) {
if (_dynarch_popupCalendar != null) { // we already have some calendar created
_dynarch_popupCalendar.hide() // so we hide it first.
}
// create the calendar
var firstDayOfWeek = Calendar._FD == null?0:Calendar._FS // default is 0 (Sun) if not specified in lang file
time24 = time24 == null?true:false // default time format is true (24h)
time24 = Calendar._TIME24 == null?time24:Calendar._TIME24 // time format can be overwritten from lang file
_dynarch_popupCalendar = new Calendar(firstDayOfWeek, null, selected, closeHandler)
// remember grails date picker elements
_dynarch_popupCalendar.g_year = document.getElementById(datePickerId + '_year')
_dynarch_popupCalendar.g_month = document.getElementById(datePickerId + '_month')
_dynarch_popupCalendar.g_day = document.getElementById(datePickerId + '_day')
_dynarch_popupCalendar.g_hour = document.getElementById(datePickerId + '_hour')
_dynarch_popupCalendar.g_minute = document.getElementById(datePickerId + '_minute')
_dynarch_popupCalendar.showsTime = _dynarch_popupCalendar.g_hour != null // display time only if grails has hour element
_dynarch_popupCalendar.time24 = time24
// find out year range from grails date picker
var minYear = comboGetValueAt(_dynarch_popupCalendar.g_year, 0) // get first item
var maxYear = comboGetValueAt(_dynarch_popupCalendar.g_year, _dynarch_popupCalendar.g_year.options.length - 1) // get first item
_dynarch_popupCalendar.setRange(parseInt(minYear), parseInt(maxYear)) // min/max year allowed.
_dynarch_popupCalendar.create()
var yearValue = comboSelectedValue(_dynarch_popupCalendar.g_year)
var monthValue = comboSelectedValue(_dynarch_popupCalendar.g_month)
var dayValue = comboSelectedValue(_dynarch_popupCalendar.g_day)
var hourValue = comboSelectedValue(_dynarch_popupCalendar.g_hour)
var minuteValue = comboSelectedValue(_dynarch_popupCalendar.g_minute)
// day and month may not be present in grails date picker and if set to null, it makes calendar to break
monthValue = monthValue == null || monthValue == "" ? 0 : parseInt(comboSelectedValue(_dynarch_popupCalendar.g_month)) - 1
dayValue = dayValue == null ? 1 : comboSelectedValue(_dynarch_popupCalendar.g_day)
var ourDate = new Date(yearValue, monthValue, dayValue, hourValue, minuteValue)
// handle grails date picker "noSelection: in year, month or day combo box, in this case set value to today
if (ourDate.getDate() != dayValue || ourDate.getMonth() != monthValue || ourDate.getFullYear() != yearValue) {
ourDate = new Date()
}
_dynarch_popupCalendar.setDate(ourDate)
_dynarch_popupCalendar.showAtElement(_dynarch_popupCalendar.g_year) // show the calendar
}


Download link

Integration with grails


Integration with grails is simple. Copy calendar-gdatepicker.js source file to web-app/js directory of your grails project.
Import jscalendar and calendar-gdatepicker.js scripts on your page.

Since Grails already have calendar plugin, I think the best way is to use this plugin to import jscalendar and jscalendar localization scripts. Then you can choose which localization and theme to use (this requires that calendar plugin is installed in grails).

E.g:
<calendar:resources lang="en" theme="system"/>
(Of course another way is to import jscalendar scripts directly).

Import calendar-gdatepicker.js script:
<script type="text/javascript" src="${createLinkTo(dir:'js',file:'calendar-gdatepicker.js')}"></script>
Now, you are ready to use jscalendar linked with g:datePicker. You only need to declare element (usually after g:datePicker tag) with onclick method set to showCalendar with first parameter specifying the name of g:datePicker. The most appropriate element is usually image or button.

E.g:
<g:datePicker name="reminderAt" value="${eventInstance?.reminderAt}" ></g:datePicker>
<img src="${createLinkTo(
dir: org.codehaus.groovy.grails.plugins.PluginManagerHolder.currentPluginManager().getGrailsPlugin("calendar").getPluginPath(),
file: "/images/skin/calendar.png")}"
id="reminderAt-trigger" alt="Date"
onclick="showCalendar('reminderAt');"/>

In InternetExplorer the jscalendar plugin (ver. 1.1.1) provides styling than makes calendar to stretch. This should be fixed in 1.2.0 according to this issue.

Sample demo project

I have created demo project that displays calendar from the plugin, calendar linked to the g:datePicker with full precision and calendar linked to the g:datePicker with day precision.
Look for calendar-gdatepicker.js and create.gsp files in the project for example code.
If you change lang parameter from "en" to "cs" (in create.gsp) you can see how calendar adjusts for Czech locale (calendar-cs.js is included).




Conclusion

By extending g:datePicker with jscalendar support you can simplify date and time selection. Selection can be done directly in jscalendar, there is no need to go through all combo box elements of g:datePicker (on the other hand, if javascript is disabled, there is still a way to select date and time just through g:datePicker
without jscalendar)

I believe this implementation (or similar) should become part of calendar plugin. New custom tag for linking g:datePicker with jscalendar would simplify usage in gsp files. This tag can have parameter specifying to show button or calendar image. Another benefit would be that there will be no need to import
calendar-gdatepicker.js as this could be handled by plugin's resource tag. I think such integration will resolve this issue as well.

Feel free to use the script if you like it for any purpose. If you modify it, please keep original header information and add description of your modification to the header.

This is my first javascript related work, so please be patient with possible errors or parts that does not conform to best practice rules.

External links:

Thursday, February 12, 2009

StringToMap

First version of StringToMap has been just released.

This small utility class/library can convert string with key:value items separated by separator character to Java Map. This is helpful when dealing with resource bundles for Swing/SWT UI, as you can put more values to one resource bundle property.

E.g.:

gui.action.file.opendb = lb:Open database...|mn:O|ic:disk.png|tp:Open existing database from specific directory.


Will result in following Map (keys and values are separated by ':'):


lb:Open database...,
mn:O|ic:disk.png,
tp:Open existing database from specific directory.


Now it is easy to get value for label with get("lb"), mnemonic get("mn") or tooltip get("tp").

Saturday, January 10, 2009

TestNG dependency observation

I have observed behavior in TestNG dependency functionality that I do not fully understand. (In example below TestNG version 5.8 was used.)

Let us have following test class:

   1 public class NGTest {
2
3 @Test(groups = {"initA"}, enabled = true)
4 public void initA() {
5 System.out.println("Init A called.");
6 }
7
8 @Test(groups = {"A"}, dependsOnGroups = {"initA"})
9 public void testA() {
10 System.out.println("Test A called.");
11 }
12 }

In class, there are two test methods:

  1. initA - test that is supposed to make initialization of A (e.g. resource A).

  2. testA - test that is supposed to perform test scenario on A; testA depends on initA through groups dependency.

Let us use following testng.xml:
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd">
<suite name="NGTest">
<test name="NGTest">
<groups>
<run>
<include name="initA"/>
<include name="A"/>
</run>
</groups>
<classes>
<class name="testngtest.NGTest"/>
</classes>
</test>
</suite>

When tests are run, the output is, as expected:

Init A called.
Test A called.

===============================================
NGTest
Total tests run: 2, Failures: 0, Skips: 0
===============================================
Let us change line in testng.xml to exclude initA group (<exclude name="initA"/>):
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd">
<suite name="NGTest">
<test name="NGTest">
<groups>
<run>
<exclude name="initA"/>
<include name="A"/>
</run>
</groups>
<classes>
<class name="testngtest.NGTest"/>
</classes>
</test>
</suite>

When tests are run, the output is same like in previous example:

Init A called.
Test A called.

===============================================
NGTest
Total tests run: 2, Failures: 0, Skips: 0
===============================================

From documentation to TestNG I would expect that initA is not called (as group is excluded) and testA is skipped, since it depends on successful run of initA. Instead, it looks like when testA is invoked, it forces initA to be called first.

Let us now disable initA test in source code:

   1         @Test(groups = {"initA"}, enabled = false)
2 public void initA() {
3 System.out.println("Init A called.");
4 }

When tests are run, the output is like expected:

===============================================
NGTest
Total tests run: 1, Failures: 0, Skips: 1
===============================================

This means disabling test method in source code has "higher priority" than excluding group in testng.xml.

Let us now add @BeforeClass method to the NGTest class and enable initA in source code again.

   1         @BeforeClass
2 public void init() {
3 System.out.println("Init called.");
4 }
5
6 @Test(groups = {"initA"}, enabled = true)
7 public void initA() {
8 System.out.println("Init A called.");
9 }

When tests are run, the output is again like expected:

===============================================
NGTest
Total tests run: 1, Failures: 0, Skips: 1
===============================================
Only testA is included into tests, but since it depends on initA, it is skipped. Same is archived when adding @BeforeTest or @BeforeSuite. This means that exclude behavior is somehow controlled by presence of @Before... method(s).