zena 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (392) hide show
  1. data/History.txt +23 -1
  2. data/MIT-LICENSE +2 -2
  3. data/README.rdoc +2 -15
  4. data/app/controllers/acls_controller.rb +1 -1
  5. data/app/controllers/documents_controller.rb +12 -3
  6. data/app/controllers/nodes_controller.rb +1 -3
  7. data/app/controllers/sites_controller.rb +7 -0
  8. data/app/controllers/versions_controller.rb +1 -1
  9. data/app/controllers/virtual_classes_controller.rb +7 -8
  10. data/app/models/acl.rb +3 -2
  11. data/app/models/relation_proxy.rb +2 -1
  12. data/app/models/role.rb +16 -0
  13. data/app/models/site.rb +48 -0
  14. data/app/models/text_document.rb +2 -1
  15. data/app/models/user.rb +2 -1
  16. data/app/models/zip.rb +2 -1
  17. data/app/views/acls/index.rhtml +11 -1
  18. data/app/views/documents/show.rhtml +1 -1
  19. data/app/views/relations/_add.erb +2 -2
  20. data/app/views/relations/_form.erb +5 -2
  21. data/app/views/relations/_li.erb +4 -1
  22. data/app/views/relations/index.erb +3 -3
  23. data/app/views/sites/_form.erb +1 -1
  24. data/app/views/sites/_job.erb +4 -0
  25. data/app/views/sites/index.erb +1 -1
  26. data/app/views/sites/jobs.erb +10 -0
  27. data/app/views/templates/document_create_tabs/_file.rhtml +3 -0
  28. data/app/views/zafu/default/Node-+search.zafu +2 -2
  29. data/bricks/acls/zena/init.rb +2 -1
  30. data/bricks/acls/zena/test/integration/acl_integration_test.rb +5 -5
  31. data/bricks/acls/zena/test/sites/erebus/acls.yml +2 -2
  32. data/bricks/acls/zena/test/unit/acl_test.rb +2 -2
  33. data/bricks/grid/lib/bricks/grid.rb +110 -0
  34. data/bricks/grid/zena/init.rb +4 -0
  35. data/bricks/math/lib/bricks/math.rb +1 -0
  36. data/bricks/pdf/lib/bricks/pdf.rb +10 -1
  37. data/bricks/sphinx/zena/tasks.rb +8 -40
  38. data/bricks/spreadsheet/README +31 -0
  39. data/bricks/spreadsheet/lib/bricks/spreadsheet.rb +293 -0
  40. data/bricks/spreadsheet/zena/init.rb +3 -0
  41. data/bricks/spreadsheet/zena/test/sites/zena/links.yml +11 -0
  42. data/bricks/spreadsheet/zena/test/unit/xlsx_test.rb +5 -0
  43. data/bricks/spreadsheet/zena/test/zafu/README +27 -0
  44. data/bricks/spreadsheet/zena/test/zafu/spreadsheet.yml +19 -0
  45. data/bricks/worker/lib/bricks/worker.rb +11 -5
  46. data/bricks/worker/zena/init.rb +8 -0
  47. data/bricks/worker/zena/migrate/20120501091514_add_site_id_to_jobs.rb +9 -0
  48. data/bricks/zena/zena/migrate/01_base.rb +0 -7
  49. data/bricks/zena/zena/migrate/20111214112233_add_comment_to_relations.rb +9 -0
  50. data/config/bricks.yml +15 -1
  51. data/config/environments/production.rb +2 -2
  52. data/config/gems.yml +6 -1
  53. data/doc/zafu_changes.yml +1 -1
  54. data/lib/tasks/zena.rake +6 -2
  55. data/lib/zena/app.rb +1 -1
  56. data/lib/zena/code/default_syntax.rb +5 -1
  57. data/lib/zena/db_helper/mysql.rb +5 -1
  58. data/lib/zena/deploy/app_init.rhtml +11 -7
  59. data/lib/zena/deploy/awstats.conf.rhtml +1 -1
  60. data/lib/zena/deploy/start_stop.rhtml +5 -5
  61. data/lib/zena/deploy/vhost.rhtml +16 -2
  62. data/lib/zena/deploy.rb +5 -3
  63. data/lib/zena/info.rb +1 -1
  64. data/lib/zena/parser/zazen_rules.rb +7 -7
  65. data/lib/zena/remote/node.rb +7 -0
  66. data/lib/zena/routes.rb +4 -5
  67. data/lib/zena/test_controller.rb +18 -1
  68. data/lib/zena/use/action.rb +13 -4
  69. data/lib/zena/use/ajax.rb +10 -4
  70. data/lib/zena/use/authlogic.rb +1 -1
  71. data/lib/zena/use/calendar.rb +4 -0
  72. data/lib/zena/use/conditional.rb +3 -0
  73. data/lib/zena/use/dates.rb +44 -4
  74. data/lib/zena/use/display.rb +45 -15
  75. data/lib/zena/use/forms.rb +59 -30
  76. data/lib/zena/use/query_builder.rb +12 -12
  77. data/lib/zena/use/recursion.rb +1 -1
  78. data/lib/zena/use/rendering.rb +18 -17
  79. data/lib/zena/use/urls.rb +2 -1
  80. data/lib/zena/use/zafu_safe_definitions.rb +0 -9
  81. data/lib/zena/use/zafu_templates.rb +2 -2
  82. data/lib/zena/use/zazen.rb +1 -3
  83. data/lib/zena/use.rb +13 -2
  84. data/lib/zena.rb +8 -2
  85. data/public/images/bullet_back.png +0 -0
  86. data/public/javascripts/grid.js +535 -0
  87. data/public/javascripts/tablekit.js +0 -0
  88. data/public/javascripts/window.js +0 -0
  89. data/public/javascripts/zena.js +17 -7
  90. data/public/stylesheets/admin.css +5 -1
  91. data/public/stylesheets/grid.css +19 -0
  92. data/test/fixtures/files/TestNode.zafu +123 -311
  93. data/test/fixtures/files/translations_fr.yml +6 -1
  94. data/test/functional/acls_controller_test.rb +2 -2
  95. data/test/functional/nodes_controller_test.rb +17 -11
  96. data/test/functional/virtual_classes_controller_test.rb +1 -1
  97. data/test/integration/query_node/basic.yml +9 -0
  98. data/test/integration/query_node/dates.yml +1 -1
  99. data/test/integration/zafu_compiler/ajax.yml +9 -9
  100. data/test/integration/zafu_compiler/basic.yml +0 -24
  101. data/test/integration/zafu_compiler/dates.yml +24 -7
  102. data/test/integration/zafu_compiler/display.yml +45 -2
  103. data/test/integration/zafu_compiler/errors.yml +0 -6
  104. data/test/integration/zafu_compiler/forms.yml +41 -3
  105. data/test/integration/zafu_compiler/query.yml +29 -3
  106. data/test/integration/zafu_compiler/roles.yml +0 -4
  107. data/test/integration/zafu_compiler/safe_definitions.yml +1 -1
  108. data/test/integration/zafu_compiler_test.rb +11 -3
  109. data/test/selenium/Add/add3.rsel +8 -8
  110. data/test/selenium/Drop/drop1.rsel +12 -12
  111. data/test/selenium/Drop/drop2.rsel +14 -14
  112. data/test/selenium/Drop/drop3.rsel +21 -21
  113. data/test/selenium/Drop/drop4.rsel +1 -1
  114. data/test/selenium/Edit/edit2.rsel +9 -9
  115. data/test/selenium/Edit/edit4.rsel +55 -0
  116. data/test/selenium/Edit/edit5.rsel +41 -0
  117. data/test/selenium/Edit/edit6.rsel +53 -0
  118. data/test/selenium/Filter/0setup.rsel +12 -0
  119. data/test/selenium/Filter/filter1.rsel +9 -0
  120. data/test/selenium/Filter/filter2.rsel +9 -0
  121. data/test/selenium/Form/form1.rsel +1 -1
  122. data/test/selenium/Form/form2.rsel +1 -1
  123. data/test/selenium/Form/form3.rsel +15 -0
  124. data/test/selenium/Grid/0setup.rsel +13 -0
  125. data/test/selenium/Grid/grid1.rsel +11 -0
  126. data/test/selenium/Swap/0setup.rsel +12 -0
  127. data/test/selenium/Swap/swap1.rsel +13 -0
  128. data/test/selenium/Swap/swap2.rsel +11 -0
  129. data/test/selenium/Toggle/toggle1.rsel +6 -6
  130. data/test/sites/zena/versions.yml +1 -1
  131. data/test/unit/relation_proxy_test.rb +36 -0
  132. data/test/unit/site_test.rb +10 -2
  133. data/test/unit/zena/use/rendering_test.rb +7 -6
  134. data/test/unit/zena/use/zazen_test.rb +1 -2
  135. data/vendor/TextMate/Ruby Shoulda.tmbundle/Commands/Run 2.tmCommand +24 -0
  136. data/vendor/TextMate/Ruby Shoulda.tmbundle/Commands/Run Context.tmCommand +58 -0
  137. data/vendor/TextMate/Ruby Shoulda.tmbundle/Commands/Run Focused Should.tmCommand +88 -0
  138. data/vendor/TextMate/Ruby Shoulda.tmbundle/Commands/Run.tmCommand +27 -0
  139. data/vendor/TextMate/Ruby Shoulda.tmbundle/Commands/YAML to Shoulda.tmCommand +23 -0
  140. data/vendor/TextMate/Ruby Shoulda.tmbundle/Preferences/Symbol List: Context.tmPreferences +19 -0
  141. data/vendor/TextMate/Ruby Shoulda.tmbundle/Preferences/Symbol List: Should.tmPreferences +19 -0
  142. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/Factory attributes for.tmSnippet +16 -0
  143. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/Factory build.tmSnippet +16 -0
  144. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/Factory.tmSnippet +16 -0
  145. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/Factory_define with class.tmSnippet +18 -0
  146. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/Factory_define.tmSnippet +18 -0
  147. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/Factory_next.tmSnippet +16 -0
  148. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/Factory_sequence.tmSnippet +18 -0
  149. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/assert_bad_value.tmSnippet +16 -0
  150. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/assert_contains.tmSnippet +16 -0
  151. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/assert_does_not_contain.tmSnippet +16 -0
  152. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/assert_good_value.tmSnippet +16 -0
  153. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/assert_same_elements.tmSnippet +16 -0
  154. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/assert_save.tmSnippet +16 -0
  155. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/assert_sent_email.tmSnippet +18 -0
  156. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/assert_valid.tmSnippet +16 -0
  157. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/association.tmSnippet +17 -0
  158. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/attribute.tmSnippet +17 -0
  159. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/before_should block.tmSnippet +18 -0
  160. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/context block get.tmSnippet +22 -0
  161. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/context block post.tmSnippet +23 -0
  162. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/context block with setup.tmSnippet +25 -0
  163. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/setup.tmSnippet +18 -0
  164. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should block with before proc.tmSnippet +18 -0
  165. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should block.tmSnippet +18 -0
  166. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_allow_values_for.tmSnippet +16 -0
  167. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_assign_to.tmSnippet +16 -0
  168. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_be_restful denied.tmSnippet +20 -0
  169. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_be_restful.tmSnippet +20 -0
  170. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_belong_to.tmSnippet +16 -0
  171. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_change by.tmSnippet +16 -0
  172. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_change from to.tmSnippet +16 -0
  173. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_ensure_length_at_least.tmSnippet +16 -0
  174. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_ensure_length_in_range.tmSnippet +16 -0
  175. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_ensure_length_is.tmSnippet +16 -0
  176. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_ensure_value_in_range.tmSnippet +16 -0
  177. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_eventually.tmSnippet +18 -0
  178. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_filter_params.tmSnippet +16 -0
  179. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_and_belong_to_many.tmSnippet +16 -0
  180. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_class_methods.tmSnippet +16 -0
  181. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_db_column.tmSnippet +16 -0
  182. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_db_columns.tmSnippet +16 -0
  183. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_index.tmSnippet +16 -0
  184. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_indices.tmSnippet +16 -0
  185. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_instance_methods.tmSnippet +16 -0
  186. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_many.tmSnippet +16 -0
  187. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_named_scope.tmSnippet +16 -0
  188. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_one.tmSnippet +16 -0
  189. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_have_readonly_attributes.tmSnippet +16 -0
  190. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_not_allow_mass_assignment_of.tmSnippet +16 -0
  191. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_not_allow_values_for.tmSnippet +16 -0
  192. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_not_assign_to.tmSnippet +16 -0
  193. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_not_change.tmSnippet +16 -0
  194. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_not_set_the_flash.tmSnippet +16 -0
  195. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_redirect_to.tmSnippet +16 -0
  196. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_render_a_form.tmSnippet +16 -0
  197. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_render_template.tmSnippet +16 -0
  198. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_render_with_layout.tmSnippet +16 -0
  199. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_render_without_layout.tmSnippet +16 -0
  200. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_respond_with.tmSnippet +16 -0
  201. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_respond_with_content_type.tmSnippet +16 -0
  202. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_return_from_session.tmSnippet +16 -0
  203. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_route.tmSnippet +16 -0
  204. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_set_the_flash_to.tmSnippet +16 -0
  205. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_validate_acceptance_of.tmSnippet +16 -0
  206. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_validate_numericality_of.tmSnippet +16 -0
  207. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_validate_presence_of.tmSnippet +16 -0
  208. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_validate_uniqueness_of with scope.tmSnippet +16 -0
  209. data/vendor/TextMate/Ruby Shoulda.tmbundle/Snippets/should_validate_uniqueness_of.tmSnippet +16 -0
  210. data/vendor/TextMate/Ruby Shoulda.tmbundle/Support/RubyMate/catch_exception.rb +39 -0
  211. data/vendor/TextMate/Ruby Shoulda.tmbundle/Support/RubyMate/run_script.rb +104 -0
  212. data/vendor/TextMate/Ruby Shoulda.tmbundle/Support/RubyMate/stdin_dialog.rb +14 -0
  213. data/vendor/TextMate/Ruby Shoulda.tmbundle/Support/RubyMate/test.rb +17 -0
  214. data/vendor/TextMate/Ruby Shoulda.tmbundle/Support/RubyMate/todo.txt +13 -0
  215. data/vendor/TextMate/Ruby Shoulda.tmbundle/Support/bin/yaml_to_shoulda.rb +25 -0
  216. data/vendor/TextMate/Ruby Shoulda.tmbundle/Syntaxes/Ruby on Rails (Shoulda).tmLanguage +166 -0
  217. data/vendor/TextMate/Ruby Shoulda.tmbundle/info.plist +304 -0
  218. data/vendor/TextMate/Zena.tmbundle/Commands/Run all yaml tests.tmCommand +37 -0
  219. data/vendor/TextMate/Zena.tmbundle/Commands/Run focused yaml test.tmCommand +52 -0
  220. data/vendor/TextMate/Zena.tmbundle/Support/RubyMate/catch_exception.rb +39 -0
  221. data/vendor/TextMate/Zena.tmbundle/Support/RubyMate/run_script.rb +118 -0
  222. data/vendor/TextMate/Zena.tmbundle/Support/RubyMate/stdin_dialog.rb +14 -0
  223. data/vendor/TextMate/Zena.tmbundle/info.plist +17 -0
  224. data/vendor/plugins/selenium-on-rails/CHANGELOG +125 -0
  225. data/vendor/plugins/selenium-on-rails/LICENSE-2.0.txt +202 -0
  226. data/vendor/plugins/selenium-on-rails/README.md +202 -0
  227. data/vendor/plugins/selenium-on-rails/Rakefile +38 -0
  228. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumController.html +265 -0
  229. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumHelper.html +148 -0
  230. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/FixtureLoader.html +231 -0
  231. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/PartialsSupport.html +195 -0
  232. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/Paths.html +295 -0
  233. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/RSelenese.html +219 -0
  234. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/Renderer.html +156 -0
  235. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/Selenese.html +179 -0
  236. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/SuiteRenderer.html +223 -0
  237. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/TestBuilder.html +441 -0
  238. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/TestBuilderAccessors.html +3098 -0
  239. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/TestBuilderActions.html +2080 -0
  240. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/TestBuilderUserAccessors.html +116 -0
  241. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails/TestBuilderUserActions.html +116 -0
  242. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRails.html +126 -0
  243. data/vendor/plugins/selenium-on-rails/doc/classes/SeleniumOnRailsConfig.html +150 -0
  244. data/vendor/plugins/selenium-on-rails/doc/files/CHANGELOG.html +422 -0
  245. data/vendor/plugins/selenium-on-rails/doc/files/README.html +321 -0
  246. data/vendor/plugins/selenium-on-rails/doc/files/lib/controllers/selenium_controller_rb.html +108 -0
  247. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_helper_rb.html +101 -0
  248. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/acceptance_test_runner_rb.html +222 -0
  249. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/fixture_loader_rb.html +109 -0
  250. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/partials_support_rb.html +111 -0
  251. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/paths_rb.html +101 -0
  252. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/renderer_rb.html +101 -0
  253. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/rselenese_rb.html +118 -0
  254. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/selenese_rb.html +101 -0
  255. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/suite_renderer_rb.html +101 -0
  256. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/test_builder_accessors_rb.html +114 -0
  257. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/test_builder_actions_rb.html +113 -0
  258. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails/test_builder_rb.html +120 -0
  259. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails_config_rb.html +108 -0
  260. data/vendor/plugins/selenium-on-rails/doc/files/lib/selenium_on_rails_rb.html +115 -0
  261. data/vendor/plugins/selenium-on-rails/doc/fr_class_index.html +42 -0
  262. data/vendor/plugins/selenium-on-rails/doc/fr_file_index.html +43 -0
  263. data/vendor/plugins/selenium-on-rails/doc/fr_method_index.html +182 -0
  264. data/vendor/plugins/selenium-on-rails/doc/index.html +24 -0
  265. data/vendor/plugins/selenium-on-rails/doc/rdoc-style.css +208 -0
  266. data/vendor/plugins/selenium-on-rails/generators/selenium/USAGE +19 -0
  267. data/vendor/plugins/selenium-on-rails/generators/selenium/selenium_generator.rb +50 -0
  268. data/vendor/plugins/selenium-on-rails/generators/selenium/templates/rhtml.rhtml +16 -0
  269. data/vendor/plugins/selenium-on-rails/generators/selenium/templates/rselenese.rhtml +14 -0
  270. data/vendor/plugins/selenium-on-rails/generators/selenium/templates/selenese.rhtml +11 -0
  271. data/vendor/plugins/selenium-on-rails/init.rb +15 -0
  272. data/vendor/plugins/selenium-on-rails/lib/controllers/selenium_controller.rb +122 -0
  273. data/vendor/plugins/selenium-on-rails/lib/controllers/switch_environment_controller.rb +16 -0
  274. data/vendor/plugins/selenium-on-rails/lib/selenium_helper.rb +8 -0
  275. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/acceptance_test_runner.rb +215 -0
  276. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/fixture_loader.rb +57 -0
  277. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/partials_support.rb +36 -0
  278. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/paths.rb +61 -0
  279. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/renderer.rb +20 -0
  280. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/rselenese.rb +44 -0
  281. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/selenese.rb +87 -0
  282. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/suite_renderer.rb +56 -0
  283. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/test_builder.rb +116 -0
  284. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/test_builder_accessors.rb +1002 -0
  285. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/test_builder_actions.rb +514 -0
  286. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/test_builder_user_accessors.rb.example +91 -0
  287. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/test_builder_user_actions.rb.example +24 -0
  288. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails.rb +11 -0
  289. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails_config.rb +30 -0
  290. data/vendor/plugins/selenium-on-rails/lib/views/layouts/layout.rhtml +18 -0
  291. data/vendor/plugins/selenium-on-rails/lib/views/record.rhtml +5 -0
  292. data/vendor/plugins/selenium-on-rails/lib/views/selenium_helper.rb +9 -0
  293. data/vendor/plugins/selenium-on-rails/lib/views/setup.rhtml +67 -0
  294. data/vendor/plugins/selenium-on-rails/lib/views/test_suite.rhtml +26 -0
  295. data/vendor/plugins/selenium-on-rails/routes.rb +24 -0
  296. data/vendor/plugins/selenium-on-rails/selenium-core/Blank.html +7 -0
  297. data/vendor/plugins/selenium-on-rails/selenium-core/InjectedRemoteRunner.html +8 -0
  298. data/vendor/plugins/selenium-on-rails/selenium-core/RemoteRunner.html +110 -0
  299. data/vendor/plugins/selenium-on-rails/selenium-core/SeleniumLog.html +109 -0
  300. data/vendor/plugins/selenium-on-rails/selenium-core/TestPrompt.html +145 -0
  301. data/vendor/plugins/selenium-on-rails/selenium-core/TestRunner-splash.html +55 -0
  302. data/vendor/plugins/selenium-on-rails/selenium-core/TestRunner.hta +177 -0
  303. data/vendor/plugins/selenium-on-rails/selenium-core/TestRunner.html +177 -0
  304. data/vendor/plugins/selenium-on-rails/selenium-core/domviewer/butmin.gif +0 -0
  305. data/vendor/plugins/selenium-on-rails/selenium-core/domviewer/butplus.gif +0 -0
  306. data/vendor/plugins/selenium-on-rails/selenium-core/domviewer/domviewer.css +298 -0
  307. data/vendor/plugins/selenium-on-rails/selenium-core/domviewer/domviewer.html +16 -0
  308. data/vendor/plugins/selenium-on-rails/selenium-core/domviewer/selenium-domviewer.js +205 -0
  309. data/vendor/plugins/selenium-on-rails/selenium-core/icons/all.png +0 -0
  310. data/vendor/plugins/selenium-on-rails/selenium-core/icons/continue.png +0 -0
  311. data/vendor/plugins/selenium-on-rails/selenium-core/icons/continue_disabled.png +0 -0
  312. data/vendor/plugins/selenium-on-rails/selenium-core/icons/pause.png +0 -0
  313. data/vendor/plugins/selenium-on-rails/selenium-core/icons/pause_disabled.png +0 -0
  314. data/vendor/plugins/selenium-on-rails/selenium-core/icons/selected.png +0 -0
  315. data/vendor/plugins/selenium-on-rails/selenium-core/icons/step.png +0 -0
  316. data/vendor/plugins/selenium-on-rails/selenium-core/icons/step_disabled.png +0 -0
  317. data/vendor/plugins/selenium-on-rails/selenium-core/iedoc-core.xml +1759 -0
  318. data/vendor/plugins/selenium-on-rails/selenium-core/iedoc.xml +1800 -0
  319. data/vendor/plugins/selenium-on-rails/selenium-core/lib/cssQuery/cssQuery-p.js +6 -0
  320. data/vendor/plugins/selenium-on-rails/selenium-core/lib/cssQuery/src/cssQuery-level2.js +142 -0
  321. data/vendor/plugins/selenium-on-rails/selenium-core/lib/cssQuery/src/cssQuery-level3.js +150 -0
  322. data/vendor/plugins/selenium-on-rails/selenium-core/lib/cssQuery/src/cssQuery-standard.js +53 -0
  323. data/vendor/plugins/selenium-on-rails/selenium-core/lib/cssQuery/src/cssQuery.js +356 -0
  324. data/vendor/plugins/selenium-on-rails/selenium-core/lib/prototype.js +2006 -0
  325. data/vendor/plugins/selenium-on-rails/selenium-core/lib/scriptaculous/builder.js +101 -0
  326. data/vendor/plugins/selenium-on-rails/selenium-core/lib/scriptaculous/controls.js +815 -0
  327. data/vendor/plugins/selenium-on-rails/selenium-core/lib/scriptaculous/dragdrop.js +915 -0
  328. data/vendor/plugins/selenium-on-rails/selenium-core/lib/scriptaculous/effects.js +958 -0
  329. data/vendor/plugins/selenium-on-rails/selenium-core/lib/scriptaculous/scriptaculous.js +47 -0
  330. data/vendor/plugins/selenium-on-rails/selenium-core/lib/scriptaculous/slider.js +283 -0
  331. data/vendor/plugins/selenium-on-rails/selenium-core/lib/scriptaculous/unittest.js +383 -0
  332. data/vendor/plugins/selenium-on-rails/selenium-core/lib/snapsie.js +91 -0
  333. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/find_matching_child.js +69 -0
  334. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/htmlutils.js +1616 -0
  335. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/injection.html +72 -0
  336. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/selenium-api.js +3184 -0
  337. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/selenium-browserbot.js +2300 -0
  338. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/selenium-browserdetect.js +153 -0
  339. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/selenium-commandhandlers.js +377 -0
  340. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/selenium-executionloop.js +175 -0
  341. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/selenium-logging.js +148 -0
  342. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/selenium-remoterunner.js +695 -0
  343. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/selenium-testrunner.js +1362 -0
  344. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/selenium-version.js +5 -0
  345. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/ui-doc.html +803 -0
  346. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/ui-element.js +1537 -0
  347. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/ui-map-sample.js +979 -0
  348. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/user-extensions.js +3 -0
  349. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/user-extensions.js.sample +75 -0
  350. data/vendor/plugins/selenium-on-rails/selenium-core/scripts/xmlextras.js +153 -0
  351. data/vendor/plugins/selenium-on-rails/selenium-core/selenium-logo.png +0 -0
  352. data/vendor/plugins/selenium-on-rails/selenium-core/selenium-test.css +43 -0
  353. data/vendor/plugins/selenium-on-rails/selenium-core/selenium.css +316 -0
  354. data/vendor/plugins/selenium-on-rails/selenium-core/xpath/dom.js +566 -0
  355. data/vendor/plugins/selenium-on-rails/selenium-core/xpath/javascript-xpath-0.1.11.js +2816 -0
  356. data/vendor/plugins/selenium-on-rails/selenium-core/xpath/util.js +549 -0
  357. data/vendor/plugins/selenium-on-rails/selenium-core/xpath/xmltoken.js +149 -0
  358. data/vendor/plugins/selenium-on-rails/selenium-core/xpath/xpath.js +2450 -0
  359. data/vendor/plugins/selenium-on-rails/tasks/test_acceptance.rake +8 -0
  360. data/vendor/plugins/selenium-on-rails/test/fixtures/config.yml +37 -0
  361. data/vendor/plugins/selenium-on-rails/test/fixtures/selenium.yml +27 -0
  362. data/vendor/plugins/selenium-on-rails/test/paths_test.rb +72 -0
  363. data/vendor/plugins/selenium-on-rails/test/renderer_test.rb +157 -0
  364. data/vendor/plugins/selenium-on-rails/test/rselenese_test.rb +708 -0
  365. data/vendor/plugins/selenium-on-rails/test/selenese_test.rb +242 -0
  366. data/vendor/plugins/selenium-on-rails/test/selenium_controller_test.rb +67 -0
  367. data/vendor/plugins/selenium-on-rails/test/selenium_on_rails_config_test.rb +43 -0
  368. data/vendor/plugins/selenium-on-rails/test/selenium_support_test.rb +35 -0
  369. data/vendor/plugins/selenium-on-rails/test/setup_test.rb +31 -0
  370. data/vendor/plugins/selenium-on-rails/test/suite_renderer_test.rb +109 -0
  371. data/vendor/plugins/selenium-on-rails/test/switch_environment_controller_test.rb +17 -0
  372. data/vendor/plugins/selenium-on-rails/test/test_builder_functions_authortest.rb +51 -0
  373. data/vendor/plugins/selenium-on-rails/test/test_helper.rb +101 -0
  374. data/vendor/plugins/selenium-on-rails/test_data/_partial.rsel +1 -0
  375. data/vendor/plugins/selenium-on-rails/test_data/own_layout.html +12 -0
  376. data/vendor/plugins/selenium-on-rails/test_data/partials/_html.html +6 -0
  377. data/vendor/plugins/selenium-on-rails/test_data/partials/_nesting.rsel +2 -0
  378. data/vendor/plugins/selenium-on-rails/test_data/partials/_rhtml.rhtml +6 -0
  379. data/vendor/plugins/selenium-on-rails/test_data/partials/_rsel.rsel +1 -0
  380. data/vendor/plugins/selenium-on-rails/test_data/partials/_sel.sel +5 -0
  381. data/vendor/plugins/selenium-on-rails/test_data/partials/all_partials.rsel +5 -0
  382. data/vendor/plugins/selenium-on-rails/test_data/rhtml.rhtml +7 -0
  383. data/vendor/plugins/selenium-on-rails/test_data/rselenese.rsel +8 -0
  384. data/vendor/plugins/selenium-on-rails/test_data/selenese.sel +7 -0
  385. data/vendor/plugins/selenium-on-rails/test_data/suite_one/subsuite/suite_one_subsuite_testcase.sel +1 -0
  386. data/vendor/plugins/selenium-on-rails/test_data/suite_one/suite_one_testcase1.sel +1 -0
  387. data/vendor/plugins/selenium-on-rails/test_data/suite_one/suite_one_testcase2.sel +1 -0
  388. data/vendor/plugins/selenium-on-rails/test_data/suite_two/suite_two_testcase.sel +1 -0
  389. data/zena.gemspec +290 -7
  390. metadata +346 -52
  391. data/app/views/nodes/_table.rhtml +0 -14
  392. data/lib/zena/use/grid.rb +0 -154
@@ -0,0 +1,1537 @@
1
+ //******************************************************************************
2
+ // Globals, including constants
3
+
4
+ var UI_GLOBAL = {
5
+ UI_PREFIX: 'ui'
6
+ , XHTML_DOCTYPE: '<!DOCTYPE html PUBLIC '
7
+ + '"-//W3C//DTD XHTML 1.0 Strict//EN" '
8
+ + '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
9
+ , XHTML_XMLNS: 'http://www.w3.org/1999/xhtml'
10
+ };
11
+
12
+ //*****************************************************************************
13
+ // Exceptions
14
+
15
+ function UIElementException(message)
16
+ {
17
+ this.message = message;
18
+ this.name = 'UIElementException';
19
+ }
20
+
21
+ function UIArgumentException(message)
22
+ {
23
+ this.message = message;
24
+ this.name = 'UIArgumentException';
25
+ }
26
+
27
+ function PagesetException(message)
28
+ {
29
+ this.message = message;
30
+ this.name = 'PagesetException';
31
+ }
32
+
33
+ function UISpecifierException(message)
34
+ {
35
+ this.message = message;
36
+ this.name = 'UISpecifierException';
37
+ }
38
+
39
+ function CommandMatcherException(message)
40
+ {
41
+ this.message = message;
42
+ this.name = 'CommandMatcherException';
43
+ }
44
+
45
+ //*****************************************************************************
46
+ // UI-Element core
47
+
48
+ /**
49
+ * The UIElement object. This has been crafted along with UIMap to make
50
+ * specifying UI elements using JSON as simple as possible. Object construction
51
+ * will fail if 1) a proper name isn't provided, 2) a faulty args argument is
52
+ * given, or 3) getLocator() returns undefined for a valid permutation of
53
+ * default argument values. See ui-doc.html for the documentation on the
54
+ * builder syntax.
55
+ *
56
+ * @param uiElementShorthand an object whose contents conform to the
57
+ * UI-Element builder syntax.
58
+ *
59
+ * @return a new UIElement object
60
+ * @throws UIElementException
61
+ */
62
+ function UIElement(uiElementShorthand)
63
+ {
64
+ // a shorthand object might look like:
65
+ //
66
+ // {
67
+ // name: 'topic'
68
+ // , description: 'sidebar links to topic categories'
69
+ // , args: [
70
+ // {
71
+ // name: 'name'
72
+ // , description: 'the name of the topic'
73
+ // , defaultValues: topLevelTopics
74
+ // }
75
+ // ]
76
+ // , getLocator: function(args) {
77
+ // return this._listXPath +
78
+ // "/a[text()=" + args.name.quoteForXPath() + "]";
79
+ // }
80
+ // , getGenericLocator: function() {
81
+ // return this._listXPath + '/a';
82
+ // }
83
+ // // maintain testcases for getLocator()
84
+ // , testcase1: {
85
+ // // defaultValues used if args not specified
86
+ // args: { name: 'foo' }
87
+ // , xhtml: '<div id="topiclist">'
88
+ // + '<ul><li><a expected-result="1">foo</a></li></ul>'
89
+ // + '</div>'
90
+ // }
91
+ // // set a local element variable
92
+ // , _listXPath: "//div[@id='topiclist']/ul/li"
93
+ // }
94
+ //
95
+ // name cannot be null or an empty string. Enforce the same requirement for
96
+ // the description.
97
+
98
+ /**
99
+ * Recursively returns all permutations of argument-value pairs, given
100
+ * a list of argument definitions. Each argument definition will have
101
+ * a set of default values to use in generating said pairs. If an argument
102
+ * has no default values defined, it will not be included among the
103
+ * permutations.
104
+ *
105
+ * @param args a list of UIArguments
106
+ * @param opt_inDocument (optional)
107
+ * @return a list of associative arrays containing key value pairs
108
+ */
109
+ this.permuteArgs = function(args, opt_inDocument) {
110
+ var permutations = [];
111
+ for (var i = 0; i < args.length; ++i) {
112
+ var arg = args[i];
113
+ var defaultValues = (arguments.length > 1)
114
+ ? arg.getDefaultValues(opt_inDocument)
115
+ : arg.getDefaultValues();
116
+
117
+ // skip arguments for which no default values are defined
118
+ if (defaultValues.length == 0) {
119
+ continue;
120
+ }
121
+ for (var j = 0; j < defaultValues.length; ++j) {
122
+ var value = defaultValues[j];
123
+ var nextPermutations = this.permuteArgs(args.slice(i+1));
124
+ if (nextPermutations.length == 0) {
125
+ var permutation = {};
126
+ permutation[arg.name] = value + ''; // make into string
127
+ permutations.push(permutation);
128
+ }
129
+ else {
130
+ for (var k = 0; k < nextPermutations.length; ++k) {
131
+ nextPermutations[k][arg.name] = value + '';
132
+ permutations.push(nextPermutations[k]);
133
+ }
134
+ }
135
+ }
136
+ break;
137
+ }
138
+ return permutations;
139
+ }
140
+
141
+
142
+
143
+ /**
144
+ * Returns a list of all testcases for this UIElement.
145
+ */
146
+ this.getTestcases = function()
147
+ {
148
+ return this.testcases;
149
+ }
150
+
151
+
152
+
153
+ /**
154
+ * Run all unit tests, stopping at the first failure, if any. Return true
155
+ * if no failures encountered, false otherwise. See the following thread
156
+ * regarding use of getElementById() on XML documents created by parsing
157
+ * text via the DOMParser:
158
+ *
159
+ * http://groups.google.com/group/comp.lang.javascript/browse_thread/thread/2b1b82b3c53a1282/
160
+ */
161
+ this.test = function()
162
+ {
163
+ var parser = new DOMParser();
164
+ var testcases = this.getTestcases();
165
+ testcaseLoop: for (var i = 0; i < testcases.length; ++i) {
166
+ var testcase = testcases[i];
167
+ var xhtml = UI_GLOBAL.XHTML_DOCTYPE + '<html xmlns="'
168
+ + UI_GLOBAL.XHTML_XMLNS + '">' + testcase.xhtml + '</html>';
169
+ var doc = parser.parseFromString(xhtml, "text/xml");
170
+ if (doc.firstChild.nodeName == 'parsererror') {
171
+ safe_alert('Error parsing XHTML in testcase "' + testcase.name
172
+ + '" for UI element "' + this.name + '": ' + "\n"
173
+ + doc.firstChild.firstChild.nodeValue);
174
+ }
175
+
176
+ // we're no longer using the default locators when testing, because
177
+ // args is now required
178
+ var locator = parse_locator(this.getLocator(testcase.args));
179
+ var results;
180
+ if (locator.type == 'xpath' || (locator.type == 'implicit' &&
181
+ locator.string.substring(0, 2) == '//')) {
182
+ // try using the javascript xpath engine to avoid namespace
183
+ // issues. The xpath does have to be lowercase however, it
184
+ // seems.
185
+ results = eval_xpath(locator.string, doc,
186
+ { allowNativeXpath: false, returnOnFirstMatch: true });
187
+ }
188
+ else {
189
+ // piece the locator back together
190
+ locator = (locator.type == 'implicit')
191
+ ? locator.string
192
+ : locator.type + '=' + locator.string;
193
+ results = eval_locator(locator, doc);
194
+ }
195
+ if (results.length && results[0].hasAttribute('expected-result')) {
196
+ continue testcaseLoop;
197
+ }
198
+
199
+ // testcase failed
200
+ if (is_IDE()) {
201
+ var msg = 'Testcase "' + testcase.name
202
+ + '" failed for UI element "' + this.name + '":';
203
+ if (!results.length) {
204
+ msg += '\n"' + locator + '" did not match any elements!';
205
+ }
206
+ else {
207
+ msg += '\n' + results[0] + ' was not the expected result!';
208
+ }
209
+ safe_alert(msg);
210
+ }
211
+ return false;
212
+ }
213
+ return true;
214
+ };
215
+
216
+
217
+
218
+ /**
219
+ * Creates a set of locators using permutations of default values for
220
+ * arguments used in the locator construction. The set is returned as an
221
+ * object mapping locators to key-value arguments objects containing the
222
+ * values passed to getLocator() to create the locator.
223
+ *
224
+ * @param opt_inDocument (optional) the document object of the "current"
225
+ * page when this method is invoked. Some arguments
226
+ * may have default value lists that are calculated
227
+ * based on the contents of the page.
228
+ *
229
+ * @return a list of locator strings
230
+ * @throws UIElementException
231
+ */
232
+ this.getDefaultLocators = function(opt_inDocument) {
233
+ var defaultLocators = {};
234
+ if (this.args.length == 0) {
235
+ defaultLocators[this.getLocator({})] = {};
236
+ }
237
+ else {
238
+ var permutations = this.permuteArgs(this.args, opt_inDocument);
239
+ if (permutations.length != 0) {
240
+ for (var i = 0; i < permutations.length; ++i) {
241
+ var args = permutations[i];
242
+ var locator = this.getLocator(args);
243
+ if (!locator) {
244
+ throw new UIElementException('Error in UIElement(): '
245
+ + 'no getLocator return value for element "' + name
246
+ + '"');
247
+ }
248
+ defaultLocators[locator] = args;
249
+ }
250
+ }
251
+ else {
252
+ // try using no arguments. If it doesn't work, fine.
253
+ try {
254
+ var locator = this.getLocator();
255
+ defaultLocators[locator] = {};
256
+ }
257
+ catch (e) {
258
+ safe_log('debug', e.message);
259
+ }
260
+ }
261
+ }
262
+ return defaultLocators;
263
+ };
264
+
265
+
266
+
267
+ /**
268
+ * Validate the structure of the shorthand notation this object is being
269
+ * initialized with. Throws an exception if there's a validation error.
270
+ *
271
+ * @param uiElementShorthand
272
+ *
273
+ * @throws UIElementException
274
+ */
275
+ this.validate = function(uiElementShorthand)
276
+ {
277
+ var msg = "UIElement validation error:\n" + print_r(uiElementShorthand);
278
+ if (!uiElementShorthand.name) {
279
+ throw new UIElementException(msg + 'no name specified!');
280
+ }
281
+ if (!uiElementShorthand.description) {
282
+ throw new UIElementException(msg + 'no description specified!');
283
+ }
284
+ if (!uiElementShorthand.locator
285
+ && !uiElementShorthand.getLocator
286
+ && !uiElementShorthand.xpath
287
+ && !uiElementShorthand.getXPath) {
288
+ throw new UIElementException(msg + 'no locator specified!');
289
+ }
290
+ };
291
+
292
+
293
+
294
+ this.init = function(uiElementShorthand)
295
+ {
296
+ this.validate(uiElementShorthand);
297
+
298
+ this.name = uiElementShorthand.name;
299
+ this.description = uiElementShorthand.description;
300
+
301
+ // construct a new getLocator() method based on the locator property,
302
+ // or use the provided function. We're deprecating the xpath property
303
+ // and getXPath() function, but still allow for them for backwards
304
+ // compatability.
305
+ if (uiElementShorthand.locator) {
306
+ this.getLocator = function(args) {
307
+ return uiElementShorthand.locator;
308
+ };
309
+ }
310
+ else if (uiElementShorthand.getLocator) {
311
+ this.getLocator = uiElementShorthand.getLocator;
312
+ }
313
+ else if (uiElementShorthand.xpath) {
314
+ this.getLocator = function(args) {
315
+ return uiElementShorthand.xpath;
316
+ };
317
+ }
318
+ else {
319
+ this.getLocator = uiElementShorthand.getXPath;
320
+ }
321
+
322
+ if (uiElementShorthand.genericLocator) {
323
+ this.getGenericLocator = function() {
324
+ return uiElementShorthand.genericLocator;
325
+ };
326
+ }
327
+ else if (uiElementShorthand.getGenericLocator) {
328
+ this.getGenericLocator = uiElementShorthand.getGenericLocator;
329
+ }
330
+
331
+ if (uiElementShorthand.getOffsetLocator) {
332
+ this.getOffsetLocator = uiElementShorthand.getOffsetLocator;
333
+ }
334
+
335
+ // get the testcases and local variables
336
+ this.testcases = [];
337
+ var localVars = {};
338
+ for (var attr in uiElementShorthand) {
339
+ if (attr.match(/^testcase/)) {
340
+ var testcase = uiElementShorthand[attr];
341
+ if (uiElementShorthand.args &&
342
+ uiElementShorthand.args.length && !testcase.args) {
343
+ safe_alert('No args defined in ' + attr + ' for UI element '
344
+ + this.name + '! Skipping testcase.');
345
+ continue;
346
+ }
347
+ testcase.name = attr;
348
+ this.testcases.push(testcase);
349
+ }
350
+ else if (attr.match(/^_/)) {
351
+ this[attr] = uiElementShorthand[attr];
352
+ localVars[attr] = uiElementShorthand[attr];
353
+ }
354
+ }
355
+
356
+ // create the arguments
357
+ this.args = []
358
+ this.argsOrder = [];
359
+ if (uiElementShorthand.args) {
360
+ for (var i = 0; i < uiElementShorthand.args.length; ++i) {
361
+ var arg = new UIArgument(uiElementShorthand.args[i], localVars);
362
+ this.args.push(arg);
363
+ this.argsOrder.push(arg.name);
364
+
365
+ // if an exception is thrown when invoking getDefaultValues()
366
+ // with no parameters passed in, assume the method requires an
367
+ // inDocument parameter, and thus may only be invoked at run
368
+ // time. Mark the UI element object accordingly.
369
+ try {
370
+ arg.getDefaultValues();
371
+ }
372
+ catch (e) {
373
+ this.isDefaultLocatorConstructionDeferred = true;
374
+ }
375
+ }
376
+
377
+ }
378
+
379
+ if (!this.isDefaultLocatorConstructionDeferred) {
380
+ this.defaultLocators = this.getDefaultLocators();
381
+ }
382
+ };
383
+
384
+
385
+
386
+ this.init(uiElementShorthand);
387
+ }
388
+
389
+ // hang this off the UIElement "namespace"
390
+ UIElement.defaultOffsetLocatorStrategy = function(locatedElement, pageElement) {
391
+ if (is_ancestor(locatedElement, pageElement)) {
392
+ var offsetLocator;
393
+ var recorder = Recorder.get(locatedElement.ownerDocument.defaultView);
394
+ var builderNames = [
395
+ 'xpath:link'
396
+ , 'xpath:img'
397
+ , 'xpath:attributes'
398
+ , 'xpath:idRelative'
399
+ , 'xpath:href'
400
+ , 'xpath:position'
401
+ ];
402
+ for (var i = 0; i < builderNames.length; ++i) {
403
+ offsetLocator = recorder.locatorBuilders
404
+ .buildWith(builderNames[i], pageElement, locatedElement);
405
+ if (offsetLocator) {
406
+ return offsetLocator;
407
+ }
408
+ }
409
+ }
410
+ return null;
411
+ };
412
+
413
+
414
+
415
+ /**
416
+ * Constructs a UIArgument. This is mostly for checking that the values are
417
+ * valid.
418
+ *
419
+ * @param uiArgumentShorthand
420
+ * @param localVars
421
+ *
422
+ * @throws UIArgumentException
423
+ */
424
+ function UIArgument(uiArgumentShorthand, localVars)
425
+ {
426
+ /**
427
+ * @param uiArgumentShorthand
428
+ *
429
+ * @throws UIArgumentException
430
+ */
431
+ this.validate = function(uiArgumentShorthand)
432
+ {
433
+ var msg = "UIArgument validation error:\n"
434
+ + print_r(uiArgumentShorthand);
435
+
436
+ // try really hard to throw an exception!
437
+ if (!uiArgumentShorthand.name) {
438
+ throw new UIArgumentException(msg + 'no name specified!');
439
+ }
440
+ if (!uiArgumentShorthand.description) {
441
+ throw new UIArgumentException(msg + 'no description specified!');
442
+ }
443
+ if (!uiArgumentShorthand.defaultValues &&
444
+ !uiArgumentShorthand.getDefaultValues) {
445
+ throw new UIArgumentException(msg + 'no default values specified!');
446
+ }
447
+ };
448
+
449
+
450
+
451
+ /**
452
+ * @param uiArgumentShorthand
453
+ * @param localVars a list of local variables
454
+ */
455
+ this.init = function(uiArgumentShorthand, localVars)
456
+ {
457
+ this.validate(uiArgumentShorthand);
458
+
459
+ this.name = uiArgumentShorthand.name;
460
+ this.description = uiArgumentShorthand.description;
461
+
462
+ if (uiArgumentShorthand.defaultValues) {
463
+ var defaultValues = uiArgumentShorthand.defaultValues;
464
+ this.getDefaultValues =
465
+ function() { return defaultValues; }
466
+ }
467
+ else {
468
+ this.getDefaultValues = uiArgumentShorthand.getDefaultValues;
469
+ }
470
+
471
+ for (var name in localVars) {
472
+ this[name] = localVars[name];
473
+ }
474
+ }
475
+
476
+
477
+
478
+ this.init(uiArgumentShorthand, localVars);
479
+ }
480
+
481
+
482
+
483
+ /**
484
+ * The UISpecifier constructor is overloaded. If less than three arguments are
485
+ * provided, the first argument will be considered a UI specifier string, and
486
+ * will be split out accordingly. Otherwise, the first argument will be
487
+ * considered the path.
488
+ *
489
+ * @param uiSpecifierStringOrPagesetName a UI specifier string, or the pageset
490
+ * name of the UI specifier
491
+ * @param elementName the name of the element
492
+ * @param args an object associating keys to values
493
+ *
494
+ * @return new UISpecifier object
495
+ */
496
+ function UISpecifier(uiSpecifierStringOrPagesetName, elementName, args)
497
+ {
498
+ /**
499
+ * Initializes this object from a UI specifier string of the form:
500
+ *
501
+ * pagesetName::elementName(arg1=value1, arg2=value2, ...)
502
+ *
503
+ * into its component parts, and returns them as an object.
504
+ *
505
+ * @return an object containing the components of the UI specifier
506
+ * @throws UISpecifierException
507
+ */
508
+ this._initFromUISpecifierString = function(uiSpecifierString) {
509
+ var matches = /^(.*)::([^\(]+)\((.*)\)$/.exec(uiSpecifierString);
510
+ if (matches == null) {
511
+ throw new UISpecifierException('Error in '
512
+ + 'UISpecifier._initFromUISpecifierString(): "'
513
+ + this.string + '" is not a valid UI specifier string');
514
+ }
515
+ this.pagesetName = matches[1];
516
+ this.elementName = matches[2];
517
+ this.args = (matches[3]) ? parse_kwargs(matches[3]) : {};
518
+ };
519
+
520
+
521
+
522
+ /**
523
+ * Override the toString() method to return the UI specifier string when
524
+ * evaluated in a string context. Combines the UI specifier components into
525
+ * a canonical UI specifier string and returns it.
526
+ *
527
+ * @return a UI specifier string
528
+ */
529
+ this.toString = function() {
530
+ // empty string is acceptable for the path, but it must be defined
531
+ if (this.pagesetName == undefined) {
532
+ throw new UISpecifierException('Error in UISpecifier.toString(): "'
533
+ + this.pagesetName + '" is not a valid UI specifier pageset '
534
+ + 'name');
535
+ }
536
+ if (!this.elementName) {
537
+ throw new UISpecifierException('Error in UISpecifier.unparse(): "'
538
+ + this.elementName + '" is not a valid UI specifier element '
539
+ + 'name');
540
+ }
541
+ if (!this.args) {
542
+ throw new UISpecifierException('Error in UISpecifier.unparse(): "'
543
+ + this.args + '" are not valid UI specifier args');
544
+ }
545
+
546
+ uiElement = UIMap.getInstance()
547
+ .getUIElement(this.pagesetName, this.elementName);
548
+ if (uiElement != null) {
549
+ var kwargs = to_kwargs(this.args, uiElement.argsOrder);
550
+ }
551
+ else {
552
+ // probably under unit test
553
+ var kwargs = to_kwargs(this.args);
554
+ }
555
+ return this.pagesetName + '::' + this.elementName + '(' + kwargs + ')';
556
+ };
557
+
558
+
559
+
560
+ // construct the object
561
+ if (arguments.length < 2) {
562
+ this._initFromUISpecifierString(uiSpecifierStringOrPagesetName);
563
+ }
564
+ else {
565
+ this.pagesetName = uiSpecifierStringOrPagesetName;
566
+ this.elementName = elementName;
567
+ this.args = (args) ? clone(args) : {};
568
+ }
569
+ }
570
+
571
+
572
+
573
+ function Pageset(pagesetShorthand)
574
+ {
575
+ /**
576
+ * Returns true if the page is included in this pageset, false otherwise.
577
+ * The page is specified by a document object.
578
+ *
579
+ * @param inDocument the document object representing the page
580
+ */
581
+ this.contains = function(inDocument)
582
+ {
583
+ var urlParts = parseUri(unescape(inDocument.location.href));
584
+ var path = urlParts.path
585
+ .replace(/^\//, "")
586
+ .replace(/\/$/, "");
587
+ if (!this.pathRegexp.test(path)) {
588
+ return false;
589
+ }
590
+ for (var paramName in this.paramRegexps) {
591
+ var paramRegexp = this.paramRegexps[paramName];
592
+ if (!paramRegexp.test(urlParts.queryKey[paramName])) {
593
+ return false;
594
+ }
595
+ }
596
+ if (!this.pageContent(inDocument)) {
597
+ return false;
598
+ }
599
+
600
+ return true;
601
+ }
602
+
603
+
604
+
605
+ this.getUIElements = function()
606
+ {
607
+ var uiElements = [];
608
+ for (var uiElementName in this.uiElements) {
609
+ uiElements.push(this.uiElements[uiElementName]);
610
+ }
611
+ return uiElements;
612
+ };
613
+
614
+
615
+
616
+ /**
617
+ * Returns a list of UI specifier string stubs representing all UI elements
618
+ * for this pageset. Stubs contain all required arguments, but leave
619
+ * argument values blank. Each element stub is paired with the element's
620
+ * description.
621
+ *
622
+ * @return a list of UI specifier string stubs
623
+ */
624
+ this.getUISpecifierStringStubs = function()
625
+ {
626
+ var stubs = [];
627
+ for (var name in this.uiElements) {
628
+ var uiElement = this.uiElements[name];
629
+ var args = {};
630
+ for (var i = 0; i < uiElement.args.length; ++i) {
631
+ args[uiElement.args[i].name] = '';
632
+ }
633
+ var uiSpecifier = new UISpecifier(this.name, uiElement.name, args);
634
+ stubs.push([
635
+ UI_GLOBAL.UI_PREFIX + '=' + uiSpecifier.toString()
636
+ , uiElement.description
637
+ ]);
638
+ }
639
+ return stubs;
640
+ }
641
+
642
+
643
+
644
+ /**
645
+ * Throws an exception on validation failure.
646
+ */
647
+ this._validate = function(pagesetShorthand)
648
+ {
649
+ var msg = "Pageset validation error:\n"
650
+ + print_r(pagesetShorthand);
651
+ if (!pagesetShorthand.name) {
652
+ throw new PagesetException(msg + 'no name specified!');
653
+ }
654
+ if (!pagesetShorthand.description) {
655
+ throw new PagesetException(msg + 'no description specified!');
656
+ }
657
+ if (!pagesetShorthand.paths &&
658
+ !pagesetShorthand.pathRegexp &&
659
+ !pagesetShorthand.pageContent) {
660
+ throw new PagesetException(msg
661
+ + 'no path, pathRegexp, or pageContent specified!');
662
+ }
663
+ };
664
+
665
+
666
+
667
+ this.init = function(pagesetShorthand)
668
+ {
669
+ this._validate(pagesetShorthand);
670
+
671
+ this.name = pagesetShorthand.name;
672
+ this.description = pagesetShorthand.description;
673
+
674
+ var pathPrefixRegexp = pagesetShorthand.pathPrefix
675
+ ? RegExp.escape(pagesetShorthand.pathPrefix) : "";
676
+ var pathRegexp = '^' + pathPrefixRegexp;
677
+
678
+ if (pagesetShorthand.paths != undefined) {
679
+ pathRegexp += '(?:';
680
+ for (var i = 0; i < pagesetShorthand.paths.length; ++i) {
681
+ if (i > 0) {
682
+ pathRegexp += '|';
683
+ }
684
+ pathRegexp += RegExp.escape(pagesetShorthand.paths[i]);
685
+ }
686
+ pathRegexp += ')$';
687
+ }
688
+ else if (pagesetShorthand.pathRegexp) {
689
+ pathRegexp += '(?:' + pagesetShorthand.pathRegexp + ')$';
690
+ }
691
+
692
+ this.pathRegexp = new RegExp(pathRegexp);
693
+ this.paramRegexps = {};
694
+ for (var paramName in pagesetShorthand.paramRegexps) {
695
+ this.paramRegexps[paramName] =
696
+ new RegExp(pagesetShorthand.paramRegexps[paramName]);
697
+ }
698
+ this.pageContent = pagesetShorthand.pageContent ||
699
+ function() { return true; };
700
+ this.uiElements = {};
701
+ };
702
+
703
+
704
+
705
+ this.init(pagesetShorthand);
706
+ }
707
+
708
+
709
+
710
+ /**
711
+ * Construct the UI map object, and return it. Once the object is instantiated,
712
+ * it binds to a global variable and will not leave scope.
713
+ *
714
+ * @return new UIMap object
715
+ */
716
+ function UIMap()
717
+ {
718
+ // the singleton pattern, split into two parts so that "new" can still
719
+ // be used, in addition to "getInstance()"
720
+ UIMap.self = this;
721
+
722
+ // need to attach variables directly to the Editor object in order for them
723
+ // to be in scope for Editor methods
724
+ if (is_IDE()) {
725
+ Editor.uiMap = this;
726
+ Editor.UI_PREFIX = UI_GLOBAL.UI_PREFIX;
727
+ }
728
+
729
+ this.pagesets = new Object();
730
+
731
+
732
+
733
+ /**
734
+ * pageset[pagesetName]
735
+ * regexp
736
+ * elements[elementName]
737
+ * UIElement
738
+ */
739
+ this.addPageset = function(pagesetShorthand)
740
+ {
741
+ try {
742
+ var pageset = new Pageset(pagesetShorthand);
743
+ }
744
+ catch (e) {
745
+ safe_alert("Could not create pageset from shorthand:\n"
746
+ + print_r(pagesetShorthand) + "\n" + e.message);
747
+ return false;
748
+ }
749
+
750
+ if (this.pagesets[pageset.name]) {
751
+ safe_alert('Could not add pageset "' + pageset.name
752
+ + '": a pageset with that name already exists!');
753
+ return false;
754
+ }
755
+
756
+ this.pagesets[pageset.name] = pageset;
757
+ return true;
758
+ };
759
+
760
+
761
+
762
+ /**
763
+ * @param pagesetName
764
+ * @param uiElementShorthand a representation of a UIElement object in
765
+ * shorthand JSON.
766
+ */
767
+ this.addElement = function(pagesetName, uiElementShorthand)
768
+ {
769
+ try {
770
+ var uiElement = new UIElement(uiElementShorthand);
771
+ }
772
+ catch (e) {
773
+ safe_alert("Could not create UI element from shorthand:\n"
774
+ + print_r(uiElementShorthand) + "\n" + e.message);
775
+ return false;
776
+ }
777
+
778
+ // run the element's unit tests only for the IDE, and only when the
779
+ // IDE is starting. Make a rough guess as to the latter condition.
780
+ if (is_IDE() && !editor.selDebugger && !uiElement.test()) {
781
+ safe_alert('Could not add UI element "' + uiElement.name
782
+ + '": failed testcases!');
783
+ return false;
784
+ }
785
+
786
+ try {
787
+ this.pagesets[pagesetName].uiElements[uiElement.name] = uiElement;
788
+ }
789
+ catch (e) {
790
+ safe_alert("Could not add UI element '" + uiElement.name
791
+ + "' to pageset '" + pagesetName + "':\n" + e.message);
792
+ return false;
793
+ }
794
+
795
+ return true;
796
+ };
797
+
798
+
799
+
800
+ /**
801
+ * Returns the pageset for a given UI specifier string.
802
+ *
803
+ * @param uiSpecifierString
804
+ * @return a pageset object
805
+ */
806
+ this.getPageset = function(uiSpecifierString)
807
+ {
808
+ try {
809
+ var uiSpecifier = new UISpecifier(uiSpecifierString);
810
+ return this.pagesets[uiSpecifier.pagesetName];
811
+ }
812
+ catch (e) {
813
+ return null;
814
+ }
815
+ }
816
+
817
+
818
+
819
+ /**
820
+ * Returns the UIElement that a UISpecifierString or pageset and element
821
+ * pair refer to.
822
+ *
823
+ * @param pagesetNameOrUISpecifierString
824
+ * @return a UIElement, or null if none is found associated with
825
+ * uiSpecifierString
826
+ */
827
+ this.getUIElement = function(pagesetNameOrUISpecifierString, uiElementName)
828
+ {
829
+ var pagesetName = pagesetNameOrUISpecifierString;
830
+ if (arguments.length == 1) {
831
+ var uiSpecifierString = pagesetNameOrUISpecifierString;
832
+ try {
833
+ var uiSpecifier = new UISpecifier(uiSpecifierString);
834
+ pagesetName = uiSpecifier.pagesetName;
835
+ var uiElementName = uiSpecifier.elementName;
836
+ }
837
+ catch (e) {
838
+ return null;
839
+ }
840
+ }
841
+ try {
842
+ return this.pagesets[pagesetName].uiElements[uiElementName];
843
+ }
844
+ catch (e) {
845
+ return null;
846
+ }
847
+ };
848
+
849
+
850
+
851
+ /**
852
+ * Returns a list of pagesets that "contains" the provided page,
853
+ * represented as a document object. Containership is defined by the
854
+ * Pageset object's contain() method.
855
+ *
856
+ * @param inDocument the page to get pagesets for
857
+ * @return a list of pagesets
858
+ */
859
+ this.getPagesetsForPage = function(inDocument)
860
+ {
861
+ var pagesets = [];
862
+ for (var pagesetName in this.pagesets) {
863
+ var pageset = this.pagesets[pagesetName];
864
+ if (pageset.contains(inDocument)) {
865
+ pagesets.push(pageset);
866
+ }
867
+ }
868
+ return pagesets;
869
+ };
870
+
871
+
872
+
873
+ /**
874
+ * Returns a list of all pagesets.
875
+ *
876
+ * @return a list of pagesets
877
+ */
878
+ this.getPagesets = function()
879
+ {
880
+ var pagesets = [];
881
+ for (var pagesetName in this.pagesets) {
882
+ pagesets.push(this.pagesets[pagesetName]);
883
+ }
884
+ return pagesets;
885
+ };
886
+
887
+
888
+
889
+ /**
890
+ * Returns a list of elements on a page that a given UI specifier string,
891
+ * maps to. If no elements are mapped to, returns an empty list..
892
+ *
893
+ * @param uiSpecifierString a String that specifies a UI element with
894
+ * attendant argument values
895
+ * @param inDocument the document object the specified UI element
896
+ * appears in
897
+ * @return a potentially-empty list of elements
898
+ * specified by uiSpecifierString
899
+ */
900
+ this.getPageElements = function(uiSpecifierString, inDocument)
901
+ {
902
+ var locator = this.getLocator(uiSpecifierString);
903
+ var results = locator ? eval_locator(locator, inDocument) : [];
904
+ return results;
905
+ };
906
+
907
+
908
+
909
+ /**
910
+ * Returns the locator string that a given UI specifier string maps to, or
911
+ * null if it cannot be mapped.
912
+ *
913
+ * @param uiSpecifierString
914
+ */
915
+ this.getLocator = function(uiSpecifierString)
916
+ {
917
+ try {
918
+ var uiSpecifier = new UISpecifier(uiSpecifierString);
919
+ }
920
+ catch (e) {
921
+ safe_alert('Could not create UISpecifier for string "'
922
+ + uiSpecifierString + '": ' + e.message);
923
+ return null;
924
+ }
925
+
926
+ var uiElement = this.getUIElement(uiSpecifier.pagesetName,
927
+ uiSpecifier.elementName);
928
+ try {
929
+ return uiElement.getLocator(uiSpecifier.args);
930
+ }
931
+ catch (e) {
932
+ return null;
933
+ }
934
+ }
935
+
936
+
937
+
938
+ /**
939
+ * Finds and returns a UI specifier string given an element and the page
940
+ * that it appears on.
941
+ *
942
+ * @param pageElement the document element to map to a UI specifier
943
+ * @param inDocument the document the element appears in
944
+ * @return a UI specifier string, or false if one cannot be
945
+ * constructed
946
+ */
947
+ this.getUISpecifierString = function(pageElement, inDocument)
948
+ {
949
+ var is_fuzzy_match =
950
+ BrowserBot.prototype.locateElementByUIElement.is_fuzzy_match;
951
+ var pagesets = this.getPagesetsForPage(inDocument);
952
+ for (var i = 0; i < pagesets.length; ++i) {
953
+ var pageset = pagesets[i];
954
+ var uiElements = pageset.getUIElements();
955
+ for (var j = 0; j < uiElements.length; ++j) {
956
+ var uiElement = uiElements[j];
957
+
958
+ // first test against the generic locator, if there is one.
959
+ // This should net some performance benefit when recording on
960
+ // more complicated pages.
961
+ if (uiElement.getGenericLocator) {
962
+ var passedTest = false;
963
+ var results =
964
+ eval_locator(uiElement.getGenericLocator(), inDocument);
965
+ for (var i = 0; i < results.length; ++i) {
966
+ if (results[i] == pageElement) {
967
+ passedTest = true;
968
+ break;
969
+ }
970
+ }
971
+ if (!passedTest) {
972
+ continue;
973
+ }
974
+ }
975
+
976
+ var defaultLocators;
977
+ if (uiElement.isDefaultLocatorConstructionDeferred) {
978
+ defaultLocators = uiElement.getDefaultLocators(inDocument);
979
+ }
980
+ else {
981
+ defaultLocators = uiElement.defaultLocators;
982
+ }
983
+
984
+ //safe_alert(print_r(uiElement.defaultLocators));
985
+ for (var locator in defaultLocators) {
986
+ var locatedElements = eval_locator(locator, inDocument);
987
+ if (locatedElements.length) {
988
+ var locatedElement = locatedElements[0];
989
+ }
990
+ else {
991
+ continue;
992
+ }
993
+
994
+ // use a heuristic to determine whether the element
995
+ // specified is the "same" as the element we're matching
996
+ if (is_fuzzy_match) {
997
+ if (is_fuzzy_match(locatedElement, pageElement)) {
998
+ return UI_GLOBAL.UI_PREFIX + '=' +
999
+ new UISpecifier(pageset.name, uiElement.name,
1000
+ defaultLocators[locator]);
1001
+ }
1002
+ }
1003
+ else {
1004
+ if (locatedElement == pageElement) {
1005
+ return UI_GLOBAL.UI_PREFIX + '=' +
1006
+ new UISpecifier(pageset.name, uiElement.name,
1007
+ defaultLocators[locator]);
1008
+ }
1009
+ }
1010
+ // ok, matching the element failed. See if an offset
1011
+ // locator can complete the match.
1012
+ if (uiElement.getOffsetLocator) {
1013
+ for (var i = 0; i < locatedElements.length; ++i) {
1014
+ var offsetLocator = uiElement
1015
+ .getOffsetLocator(locatedElement, pageElement);
1016
+ if (offsetLocator) {
1017
+ return UI_GLOBAL.UI_PREFIX + '=' +
1018
+ new UISpecifier(pageset.name,
1019
+ uiElement.name,
1020
+ defaultLocators[locator])
1021
+ + '->' + offsetLocator;
1022
+ }
1023
+ }
1024
+ }
1025
+ }
1026
+ }
1027
+ }
1028
+ return false;
1029
+ };
1030
+
1031
+
1032
+
1033
+ /**
1034
+ * Returns a sorted list of UI specifier string stubs representing possible
1035
+ * UI elements for all pagesets, paired the their descriptions. Stubs
1036
+ * contain all required arguments, but leave argument values blank.
1037
+ *
1038
+ * @return a list of UI specifier string stubs
1039
+ */
1040
+ this.getUISpecifierStringStubs = function() {
1041
+ var stubs = [];
1042
+ var pagesets = this.getPagesets();
1043
+ for (var i = 0; i < pagesets.length; ++i) {
1044
+ stubs = stubs.concat(pagesets[i].getUISpecifierStringStubs());
1045
+ }
1046
+ stubs.sort(function(a, b) {
1047
+ if (a[0] < b[0]) {
1048
+ return -1;
1049
+ }
1050
+ return a[0] == b[0] ? 0 : 1;
1051
+ });
1052
+ return stubs;
1053
+ }
1054
+ }
1055
+
1056
+ UIMap.getInstance = function() {
1057
+ return (UIMap.self == null) ? new UIMap() : UIMap.self;
1058
+ }
1059
+
1060
+ //******************************************************************************
1061
+ // Rollups
1062
+
1063
+ /**
1064
+ * The Command object isn't available in the Selenium RC. We introduce an
1065
+ * object with the identical constructor. In the IDE, this will be redefined,
1066
+ * which is just fine.
1067
+ *
1068
+ * @param command
1069
+ * @param target
1070
+ * @param value
1071
+ */
1072
+ if (typeof(Command) == 'undefined') {
1073
+ function Command(command, target, value) {
1074
+ this.command = command != null ? command : '';
1075
+ this.target = target != null ? target : '';
1076
+ this.value = value != null ? value : '';
1077
+ }
1078
+ }
1079
+
1080
+
1081
+
1082
+ /**
1083
+ * A CommandMatcher object matches commands during the application of a
1084
+ * RollupRule. It's specified with a shorthand format, for example:
1085
+ *
1086
+ * new CommandMatcher({
1087
+ * command: 'click'
1088
+ * , target: 'ui=allPages::.+'
1089
+ * })
1090
+ *
1091
+ * which is intended to match click commands whose target is an element in the
1092
+ * allPages PageSet. The matching expressions are given as regular expressions;
1093
+ * in the example above, the command must be "click"; "clickAndWait" would be
1094
+ * acceptable if 'click.*' were used. Here's a more complete example:
1095
+ *
1096
+ * new CommandMatcher({
1097
+ * command: 'type'
1098
+ * , target: 'ui=loginPages::username()'
1099
+ * , value: '.+_test'
1100
+ * , updateArgs: function(command, args) {
1101
+ * args.username = command.value;
1102
+ * }
1103
+ * })
1104
+ *
1105
+ * Here, the command and target are fixed, but there is variability in the
1106
+ * value of the command. When a command matches, the username is saved to the
1107
+ * arguments object.
1108
+ */
1109
+ function CommandMatcher(commandMatcherShorthand)
1110
+ {
1111
+ /**
1112
+ * Ensure the shorthand notation used to initialize the CommandMatcher has
1113
+ * all required values.
1114
+ *
1115
+ * @param commandMatcherShorthand an object containing information about
1116
+ * the CommandMatcher
1117
+ */
1118
+ this.validate = function(commandMatcherShorthand) {
1119
+ var msg = "CommandMatcher validation error:\n"
1120
+ + print_r(commandMatcherShorthand);
1121
+ if (!commandMatcherShorthand.command) {
1122
+ throw new CommandMatcherException(msg + 'no command specified!');
1123
+ }
1124
+ if (!commandMatcherShorthand.target) {
1125
+ throw new CommandMatcherException(msg + 'no target specified!');
1126
+ }
1127
+ if (commandMatcherShorthand.minMatches &&
1128
+ commandMatcherShorthand.maxMatches &&
1129
+ commandMatcherShorthand.minMatches >
1130
+ commandMatcherShorthand.maxMatches) {
1131
+ throw new CommandMatcherException(msg + 'minMatches > maxMatches!');
1132
+ }
1133
+ };
1134
+
1135
+ /**
1136
+ * Initialize this object.
1137
+ *
1138
+ * @param commandMatcherShorthand an object containing information used to
1139
+ * initialize the CommandMatcher
1140
+ */
1141
+ this.init = function(commandMatcherShorthand) {
1142
+ this.validate(commandMatcherShorthand);
1143
+
1144
+ this.command = commandMatcherShorthand.command;
1145
+ this.target = commandMatcherShorthand.target;
1146
+ this.value = commandMatcherShorthand.value || null;
1147
+ this.minMatches = commandMatcherShorthand.minMatches || 1;
1148
+ this.maxMatches = commandMatcherShorthand.maxMatches || 1;
1149
+ this.updateArgs = commandMatcherShorthand.updateArgs ||
1150
+ function(command, args) { return args; };
1151
+ };
1152
+
1153
+ /**
1154
+ * Determines whether a given command matches. Updates args by "reference"
1155
+ * and returns true if it does; return false otherwise.
1156
+ *
1157
+ * @param command the command to attempt to match
1158
+ */
1159
+ this.isMatch = function(command) {
1160
+ var re = new RegExp('^' + this.command + '$');
1161
+ if (! re.test(command.command)) {
1162
+ return false;
1163
+ }
1164
+ re = new RegExp('^' + this.target + '$');
1165
+ if (! re.test(command.target)) {
1166
+ return false;
1167
+ }
1168
+ if (this.value != null) {
1169
+ re = new RegExp('^' + this.value + '$');
1170
+ if (! re.test(command.value)) {
1171
+ return false;
1172
+ }
1173
+ }
1174
+
1175
+ // okay, the command matches
1176
+ return true;
1177
+ };
1178
+
1179
+ // initialization
1180
+ this.init(commandMatcherShorthand);
1181
+ }
1182
+
1183
+
1184
+
1185
+ function RollupRuleException(message)
1186
+ {
1187
+ this.message = message;
1188
+ this.name = 'RollupRuleException';
1189
+ }
1190
+
1191
+ function RollupRule(rollupRuleShorthand)
1192
+ {
1193
+ /**
1194
+ * Ensure the shorthand notation used to initialize the RollupRule has all
1195
+ * required values.
1196
+ *
1197
+ * @param rollupRuleShorthand an object containing information about the
1198
+ * RollupRule
1199
+ */
1200
+ this.validate = function(rollupRuleShorthand) {
1201
+ var msg = "RollupRule validation error:\n"
1202
+ + print_r(rollupRuleShorthand);
1203
+ if (!rollupRuleShorthand.name) {
1204
+ throw new RollupRuleException(msg + 'no name specified!');
1205
+ }
1206
+ if (!rollupRuleShorthand.description) {
1207
+ throw new RollupRuleException(msg + 'no description specified!');
1208
+ }
1209
+ // rollupRuleShorthand.args is optional
1210
+ if (!rollupRuleShorthand.commandMatchers &&
1211
+ !rollupRuleShorthand.getRollup) {
1212
+ throw new RollupRuleException(msg
1213
+ + 'no command matchers specified!');
1214
+ }
1215
+ if (!rollupRuleShorthand.expandedCommands &&
1216
+ !rollupRuleShorthand.getExpandedCommands) {
1217
+ throw new RollupRuleException(msg
1218
+ + 'no expanded commands specified!');
1219
+ }
1220
+
1221
+ return true;
1222
+ };
1223
+
1224
+ /**
1225
+ * Initialize this object.
1226
+ *
1227
+ * @param rollupRuleShorthand an object containing information used to
1228
+ * initialize the RollupRule
1229
+ */
1230
+ this.init = function(rollupRuleShorthand) {
1231
+ this.validate(rollupRuleShorthand);
1232
+
1233
+ this.name = rollupRuleShorthand.name;
1234
+ this.description = rollupRuleShorthand.description;
1235
+ this.pre = rollupRuleShorthand.pre || '';
1236
+ this.post = rollupRuleShorthand.post || '';
1237
+ this.alternateCommand = rollupRuleShorthand.alternateCommand;
1238
+ this.args = rollupRuleShorthand.args || [];
1239
+
1240
+ if (rollupRuleShorthand.commandMatchers) {
1241
+ // construct the rule from the list of CommandMatchers
1242
+ this.commandMatchers = [];
1243
+ var matchers = rollupRuleShorthand.commandMatchers;
1244
+ for (var i = 0; i < matchers.length; ++i) {
1245
+ if (matchers[i].updateArgs && this.args.length == 0) {
1246
+ // enforce metadata for arguments
1247
+ var msg = "RollupRule validation error:\n"
1248
+ + print_r(rollupRuleShorthand)
1249
+ + 'no argument metadata provided!';
1250
+ throw new RollupRuleException(msg);
1251
+ }
1252
+ this.commandMatchers.push(new CommandMatcher(matchers[i]));
1253
+ }
1254
+
1255
+ // returns false if the rollup doesn't match, or a rollup command
1256
+ // if it does. If returned, the command contains the
1257
+ // replacementIndexes property, which indicates which commands it
1258
+ // substitutes for.
1259
+ this.getRollup = function(commands) {
1260
+ // this is a greedy matching algorithm
1261
+ var replacementIndexes = [];
1262
+ var commandMatcherQueue = this.commandMatchers;
1263
+ var matchCount = 0;
1264
+ var args = {};
1265
+ for (var i = 0, j = 0; i < commandMatcherQueue.length;) {
1266
+ var matcher = commandMatcherQueue[i];
1267
+ if (j >= commands.length) {
1268
+ // we've run out of commands! If the remaining matchers
1269
+ // do not have minMatches requirements, this is a
1270
+ // match. Otherwise, it's not.
1271
+ if (matcher.minMatches > 0) {
1272
+ return false;
1273
+ }
1274
+ ++i;
1275
+ matchCount = 0; // unnecessary, but let's be consistent
1276
+ }
1277
+ else {
1278
+ if (matcher.isMatch(commands[j])) {
1279
+ ++matchCount;
1280
+ if (matchCount == matcher.maxMatches) {
1281
+ // exhausted this matcher's matches ... move on
1282
+ // to next matcher
1283
+ ++i;
1284
+ matchCount = 0;
1285
+ }
1286
+ args = matcher.updateArgs(commands[j], args);
1287
+ replacementIndexes.push(j);
1288
+ ++j; // move on to next command
1289
+ }
1290
+ else {
1291
+ //alert(matchCount + ', ' + matcher.minMatches);
1292
+ if (matchCount < matcher.minMatches) {
1293
+ return false;
1294
+ }
1295
+ // didn't match this time, but we've satisfied the
1296
+ // requirements already ... move on to next matcher
1297
+ ++i;
1298
+ matchCount = 0;
1299
+ // still gonna look at same command
1300
+ }
1301
+ }
1302
+ }
1303
+
1304
+ var rollup;
1305
+ if (this.alternateCommand) {
1306
+ rollup = new Command(this.alternateCommand,
1307
+ commands[0].target, commands[0].value);
1308
+ }
1309
+ else {
1310
+ rollup = new Command('rollup', this.name);
1311
+ rollup.value = to_kwargs(args);
1312
+ }
1313
+ rollup.replacementIndexes = replacementIndexes;
1314
+ return rollup;
1315
+ };
1316
+ }
1317
+ else {
1318
+ this.getRollup = function(commands) {
1319
+ var result = rollupRuleShorthand.getRollup(commands);
1320
+ if (result) {
1321
+ var rollup = new Command(
1322
+ result.command
1323
+ , result.target
1324
+ , result.value
1325
+ );
1326
+ rollup.replacementIndexes = result.replacementIndexes;
1327
+ return rollup;
1328
+ }
1329
+ return false;
1330
+ };
1331
+ }
1332
+
1333
+ this.getExpandedCommands = function(kwargs) {
1334
+ var commands = [];
1335
+ var expandedCommands = (rollupRuleShorthand.expandedCommands
1336
+ ? rollupRuleShorthand.expandedCommands
1337
+ : rollupRuleShorthand.getExpandedCommands(
1338
+ parse_kwargs(kwargs)));
1339
+ for (var i = 0; i < expandedCommands.length; ++i) {
1340
+ var command = expandedCommands[i];
1341
+ commands.push(new Command(
1342
+ command.command
1343
+ , command.target
1344
+ , command.value
1345
+ ));
1346
+ }
1347
+ return commands;
1348
+ };
1349
+ };
1350
+
1351
+ this.init(rollupRuleShorthand);
1352
+ }
1353
+
1354
+
1355
+
1356
+ /**
1357
+ *
1358
+ */
1359
+ function RollupManager()
1360
+ {
1361
+ // singleton pattern
1362
+ RollupManager.self = this;
1363
+
1364
+ this.init = function()
1365
+ {
1366
+ this.rollupRules = {};
1367
+ if (is_IDE()) {
1368
+ Editor.rollupManager = this;
1369
+ }
1370
+ };
1371
+
1372
+ /**
1373
+ * Adds a new RollupRule to the repository. Returns true on success, or
1374
+ * false if the rule couldn't be added.
1375
+ *
1376
+ * @param rollupRuleShorthand shorthand JSON specification of the new
1377
+ * RollupRule, possibly including CommandMatcher
1378
+ * shorthand too.
1379
+ * @return true if the rule was added successfully,
1380
+ * false otherwise.
1381
+ */
1382
+ this.addRollupRule = function(rollupRuleShorthand)
1383
+ {
1384
+ try {
1385
+ var rule = new RollupRule(rollupRuleShorthand);
1386
+ this.rollupRules[rule.name] = rule;
1387
+ }
1388
+ catch(e) {
1389
+ smart_alert("Could not create RollupRule from shorthand:\n\n"
1390
+ + e.message);
1391
+ return false;
1392
+ }
1393
+ return true;
1394
+ };
1395
+
1396
+ /**
1397
+ * Returns a RollupRule by name.
1398
+ *
1399
+ * @param rollupName the name of the rule to fetch
1400
+ * @return the RollupRule, or null if it isn't found.
1401
+ */
1402
+ this.getRollupRule = function(rollupName)
1403
+ {
1404
+ return (this.rollupRules[rollupName] || null);
1405
+ };
1406
+
1407
+ /**
1408
+ * Returns a list of name-description pairs for use in populating the
1409
+ * auto-populated target dropdown in the IDE. Rules that have an alternate
1410
+ * command defined are not included in the list, as they are not bona-fide
1411
+ * rollups.
1412
+ *
1413
+ * @return a list of name-description pairs
1414
+ */
1415
+ this.getRollupRulesForDropdown = function()
1416
+ {
1417
+ var targets = [];
1418
+ var names = keys(this.rollupRules).sort();
1419
+ for (var i = 0; i < names.length; ++i) {
1420
+ var name = names[i];
1421
+ if (this.rollupRules[name].alternateCommand) {
1422
+ continue;
1423
+ }
1424
+ targets.push([ name, this.rollupRules[name].description ]);
1425
+ }
1426
+ return targets;
1427
+ };
1428
+
1429
+ /**
1430
+ * Applies all rules to the current editor commands, asking the user in
1431
+ * each case if it's okay to perform the replacement. The rules are applied
1432
+ * repeatedly until there are no more matches. The algorithm should
1433
+ * remember when the user has declined a replacement, and not ask to do it
1434
+ * again.
1435
+ *
1436
+ * @return the list of commands with rollup replacements performed
1437
+ */
1438
+ this.applyRollupRules = function()
1439
+ {
1440
+ var commands = editor.getTestCase().commands;
1441
+ var blacklistedRollups = {};
1442
+
1443
+ // so long as rollups were performed, we need to keep iterating through
1444
+ // the commands starting at the beginning, because further rollups may
1445
+ // potentially be applied on the newly created ones.
1446
+ while (true) {
1447
+ var performedRollup = false;
1448
+ for (var i = 0; i < commands.length; ++i) {
1449
+ // iterate through commands
1450
+ for (var rollupName in this.rollupRules) {
1451
+ var rule = this.rollupRules[rollupName];
1452
+ var rollup = rule.getRollup(commands.slice(i));
1453
+ if (rollup) {
1454
+ // since we passed in a sliced version of the commands
1455
+ // array to the getRollup() method, we need to re-add
1456
+ // the offset to the replacementIndexes
1457
+ var k = 0;
1458
+ for (; k < rollup.replacementIndexes.length; ++k) {
1459
+ rollup.replacementIndexes[k] += i;
1460
+ }
1461
+
1462
+ // build the confirmation message
1463
+ var msg = "Perform the following command rollup?\n\n";
1464
+ for (k = 0; k < rollup.replacementIndexes.length; ++k) {
1465
+ var replacementIndex = rollup.replacementIndexes[k];
1466
+ var command = commands[replacementIndex];
1467
+ msg += '[' + replacementIndex + ']: ';
1468
+ msg += command + "\n";
1469
+ }
1470
+ msg += "\n";
1471
+ msg += rollup;
1472
+
1473
+ // check against blacklisted rollups
1474
+ if (blacklistedRollups[msg]) {
1475
+ continue;
1476
+ }
1477
+
1478
+ // highlight the potentially replaced rows
1479
+ for (k = 0; k < commands.length; ++k) {
1480
+ var command = commands[k];
1481
+ command.result = '';
1482
+ if (rollup.replacementIndexes.indexOf(k) != -1) {
1483
+ command.selectedForReplacement = true;
1484
+ }
1485
+ editor.view.rowUpdated(replacementIndex);
1486
+ }
1487
+
1488
+ // get confirmation from user
1489
+ if (confirm(msg)) {
1490
+ // perform rollup
1491
+ var deleteRanges = [];
1492
+ var replacementIndexes = rollup.replacementIndexes;
1493
+ for (k = 0; k < replacementIndexes.length; ++k) {
1494
+ // this is expected to be list of ranges. A
1495
+ // range has a start, and a list of commands.
1496
+ // The deletion only checks the length of the
1497
+ // command list.
1498
+ deleteRanges.push({
1499
+ start: replacementIndexes[k]
1500
+ , commands: [ 1 ]
1501
+ });
1502
+ }
1503
+ editor.view.executeAction(new TreeView
1504
+ .DeleteCommandAction(editor.view,deleteRanges));
1505
+ editor.view.insertAt(i, rollup);
1506
+
1507
+ performedRollup = true;
1508
+ }
1509
+ else {
1510
+ // cleverly remember not to try this rollup again
1511
+ blacklistedRollups[msg] = true;
1512
+ }
1513
+
1514
+ // unhighlight
1515
+ for (k = 0; k < commands.length; ++k) {
1516
+ commands[k].selectedForReplacement = false;
1517
+ editor.view.rowUpdated(k);
1518
+ }
1519
+ }
1520
+ }
1521
+ }
1522
+ if (!performedRollup) {
1523
+ break;
1524
+ }
1525
+ }
1526
+ return commands;
1527
+ };
1528
+
1529
+ this.init();
1530
+ }
1531
+
1532
+ RollupManager.getInstance = function() {
1533
+ return (RollupManager.self == null)
1534
+ ? new RollupManager()
1535
+ : RollupManager.self;
1536
+ }
1537
+