September 13, 2008

RJS testing

Last week I spent two very full days in creating an RJS-enhanced admin GUI on Rails. As RJS is extremely brittle ( it takes some time to tie the Ajax and css together and still it's easy to break an aspect of functionality without realizing it ), testing is important to later add new functionality, and a necessity for another developer to write new code without regressions. In the GUI there is a list of users and a list of groups. On the page there is an XHR call to the AjaxController, which responds by updating a div on the page.
def select_group
raise unless request.xhr?
render :update do |page|
  page.replace_html :zcore_inspector,   :partial => 'admin/ajax/group'
  page.replace_html :zcore_group_users, :partial => 'admin/ajax/groups_users'
end
end
Kevin Clark has written an RJS testing plugin (ARTS) and the guide gives a good head start. As RJS returns a chunk of JSON-encoded JavaScript, it's simple to regexp for an item that is expected to be rendered from the partial. As can be seen below, this is a powerful testing utility.
xhr :get, :select_group, :groupid => group.id, :uid => @admin.id
  assert_response :success
  assert_not_nil assigns(:admin)
  assert_not_nil assigns(:company)
  assert_equal assigns(:company),@company
  assert_not_nil assigns(:group)
  assert_equal assigns(:group),group

  # group.name should be in the returned JavaScript
  assert_rjs :replace_html, 'zcore_group_users', /#{to_json(group.name)}/

  # all groups users should be listed, as they are draggable the id is fixed.
  group.users.each do |u|
    assert_rjs :replace_html, 'zcore_group_users', /id=\\\"uid_#{u.id}\\\"/
  end

  # closer inspection of the group
  assert_rjs :replace_html, 'zcore_inspector', /span id=\\\"groupid.*>#{group.id}<\/span>/

  # if the group does not have forums, offer a link to create them
  unless group.has_zcategories?
    assert_rjs :replace_html, 'zcore_inspector', /create_message_board/

  else
    root = group.private_councelling_category
    assert_rjs :replace_html, 'zcore_inspector', /#{to_json(root.description)}/

    # groups members have their private category, assert their names are mentioned
    root.subcategories.each do |c|
      assert_rjs :replace_html, 'zcore_inspector', /#{to_json(c.user.fullname)}/
    end
  end
The assertion doesn't match if the string has non-ascii characters that are encoded by the String.to_json() method. I got around this by creating a json wrapper method to_json(), with the 'falling matchsticks' and whatnot so the string properly evalutes in the regular expression.
def to_json(s)
s.to_json.gsub(/^\"|\"$/,'').gsub(/\\/,'\\\\\\\\').gsub(/\(/,'\(').gsub(/\)/,'\)')
end