browsercms 3.1.4 → 3.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. data/app/controllers/cms/content_block_controller.rb +2 -2
  2. data/app/controllers/cms/section_nodes_controller.rb +6 -1
  3. data/app/controllers/cms/sections_controller.rb +1 -1
  4. data/app/helpers/cms/application_helper.rb +1 -1
  5. data/app/helpers/cms/content_block_helper.rb +27 -0
  6. data/app/helpers/cms/section_nodes_helper.rb +43 -5
  7. data/app/models/abstract_file_block.rb +16 -1
  8. data/app/models/attachment.rb +17 -35
  9. data/app/models/file_block.rb +0 -12
  10. data/app/models/image_block.rb +0 -12
  11. data/app/models/link.rb +4 -21
  12. data/app/models/page.rb +31 -34
  13. data/app/models/section.rb +82 -44
  14. data/app/models/section_node.rb +39 -24
  15. data/app/models/user.rb +5 -0
  16. data/app/views/cms/blocks/index.html.erb +4 -4
  17. data/app/views/cms/file_blocks/_form.html.erb +1 -1
  18. data/app/views/cms/image_blocks/_form.html.erb +1 -1
  19. data/app/views/cms/section_nodes/_link.html.erb +6 -3
  20. data/app/views/cms/section_nodes/_node.html.erb +11 -1
  21. data/app/views/cms/section_nodes/_page.html.erb +13 -7
  22. data/app/views/cms/section_nodes/_section.html.erb +24 -8
  23. data/app/views/cms/section_nodes/index.html.erb +28 -16
  24. data/app/views/layouts/templates/default.html.erb +17 -0
  25. data/browsercms.gemspec +28 -1413
  26. data/db/migrate/20120117144039_browsercms315.rb +94 -0
  27. data/db/migrate/{20081114172307_load_seed_data.rb → 20121114172307_load_seeds.rb} +8 -1
  28. data/lib/acts_as_list.rb +1 -1
  29. data/lib/browsercms.rb +2 -0
  30. data/lib/cms/addressable.rb +83 -0
  31. data/lib/cms/behaviors/attaching.rb +44 -24
  32. data/lib/cms/behaviors/connecting.rb +2 -1
  33. data/lib/cms/behaviors/publishing.rb +12 -3
  34. data/lib/cms/behaviors/versioning.rb +83 -53
  35. data/lib/cms/content_rendering_support.rb +3 -3
  36. data/lib/cms/error_pages.rb +8 -0
  37. data/lib/cms/init.rb +5 -3
  38. data/lib/cms/version.rb +1 -1
  39. data/templates/blank.rb +2 -0
  40. data/templates/demo.rb +2 -0
  41. data/templates/module.rb +2 -0
  42. data/test/custom_assertions.rb +7 -1
  43. data/test/factories.rb +3 -1
  44. data/test/factories/sitemap_factories.rb +28 -0
  45. data/test/fixtures/connectors.yml +97 -0
  46. data/test/fixtures/content_type_groups.yml +13 -0
  47. data/test/fixtures/content_types.yml +50 -0
  48. data/test/fixtures/dynamic_view_versions.yml +26 -0
  49. data/test/fixtures/dynamic_views.yml +26 -0
  50. data/test/fixtures/group_permissions.yml +16 -0
  51. data/test/fixtures/group_sections.yml +31 -0
  52. data/test/fixtures/group_type_permissions.yml +11 -0
  53. data/test/fixtures/group_types.yml +25 -0
  54. data/test/fixtures/groups.yml +25 -0
  55. data/test/fixtures/html_block_versions.yml +67 -0
  56. data/test/fixtures/html_blocks.yml +63 -0
  57. data/test/fixtures/page_versions.yml +265 -0
  58. data/test/fixtures/pages.yml +85 -0
  59. data/test/fixtures/permissions.yml +28 -0
  60. data/test/fixtures/section_nodes.yml +46 -0
  61. data/test/fixtures/sections.yml +19 -0
  62. data/test/fixtures/sites.yml +9 -0
  63. data/test/fixtures/user_group_memberships.yml +11 -0
  64. data/test/fixtures/users.yml +15 -0
  65. data/test/functional/cms/content_controller_test.rb +6 -1
  66. data/test/functional/cms/file_blocks_controller_test.rb +1 -0
  67. data/test/functional/cms/html_blocks_controller_test.rb +1 -0
  68. data/test/functional/cms/image_blocks_controller_test.rb +39 -32
  69. data/test/functional/cms/section_nodes_controller_test.rb +48 -20
  70. data/test/functional/cms/sections_controller_test.rb +3 -1
  71. data/test/functional/tests/pretend_controller_test.rb +6 -3
  72. data/test/integration/cms/ckeditor_test.rb +5 -2
  73. data/test/integration/sitemap_performance_test.rb +26 -0
  74. data/test/selenium-core/Blank.html +7 -0
  75. data/test/selenium-core/InjectedRemoteRunner.html +8 -0
  76. data/test/selenium-core/RemoteRunner.html +110 -0
  77. data/test/selenium-core/SeleniumLog.html +109 -0
  78. data/test/selenium-core/TestPrompt.html +145 -0
  79. data/test/selenium-core/TestRunner-splash.html +55 -0
  80. data/test/selenium-core/TestRunner.hta +176 -0
  81. data/test/selenium-core/TestRunner.html +176 -0
  82. data/test/selenium-core/domviewer/butmin.gif +0 -0
  83. data/test/selenium-core/domviewer/butplus.gif +0 -0
  84. data/test/selenium-core/domviewer/domviewer.css +298 -0
  85. data/test/selenium-core/domviewer/domviewer.html +16 -0
  86. data/test/selenium-core/domviewer/selenium-domviewer.js +205 -0
  87. data/test/selenium-core/icons/all.png +0 -0
  88. data/test/selenium-core/icons/continue.png +0 -0
  89. data/test/selenium-core/icons/continue_disabled.png +0 -0
  90. data/test/selenium-core/icons/pause.png +0 -0
  91. data/test/selenium-core/icons/pause_disabled.png +0 -0
  92. data/test/selenium-core/icons/selected.png +0 -0
  93. data/test/selenium-core/icons/step.png +0 -0
  94. data/test/selenium-core/icons/step_disabled.png +0 -0
  95. data/test/selenium-core/iedoc-core.xml +1515 -0
  96. data/test/selenium-core/iedoc.xml +1469 -0
  97. data/test/selenium-core/lib/cssQuery/cssQuery-p.js +6 -0
  98. data/test/selenium-core/lib/cssQuery/src/cssQuery-level2.js +142 -0
  99. data/test/selenium-core/lib/cssQuery/src/cssQuery-level3.js +150 -0
  100. data/test/selenium-core/lib/cssQuery/src/cssQuery-standard.js +53 -0
  101. data/test/selenium-core/lib/cssQuery/src/cssQuery.js +356 -0
  102. data/test/selenium-core/lib/prototype.js +2006 -0
  103. data/test/selenium-core/lib/scriptaculous/builder.js +101 -0
  104. data/test/selenium-core/lib/scriptaculous/controls.js +815 -0
  105. data/test/selenium-core/lib/scriptaculous/dragdrop.js +915 -0
  106. data/test/selenium-core/lib/scriptaculous/effects.js +958 -0
  107. data/test/selenium-core/lib/scriptaculous/scriptaculous.js +47 -0
  108. data/test/selenium-core/lib/scriptaculous/slider.js +283 -0
  109. data/test/selenium-core/lib/scriptaculous/unittest.js +383 -0
  110. data/test/selenium-core/scripts/find_matching_child.js +69 -0
  111. data/test/selenium-core/scripts/htmlutils.js +894 -0
  112. data/test/selenium-core/scripts/injection.html +72 -0
  113. data/test/selenium-core/scripts/js2html.js +70 -0
  114. data/test/selenium-core/scripts/narcissus-defs.js +175 -0
  115. data/test/selenium-core/scripts/narcissus-exec.js +1054 -0
  116. data/test/selenium-core/scripts/narcissus-parse.js +1003 -0
  117. data/test/selenium-core/scripts/se2html.js +63 -0
  118. data/test/selenium-core/scripts/selenium-api.js +2409 -0
  119. data/test/selenium-core/scripts/selenium-browserbot.js +2203 -0
  120. data/test/selenium-core/scripts/selenium-browserdetect.js +150 -0
  121. data/test/selenium-core/scripts/selenium-commandhandlers.js +377 -0
  122. data/test/selenium-core/scripts/selenium-executionloop.js +175 -0
  123. data/test/selenium-core/scripts/selenium-logging.js +147 -0
  124. data/test/selenium-core/scripts/selenium-remoterunner.js +571 -0
  125. data/test/selenium-core/scripts/selenium-testrunner.js +1333 -0
  126. data/test/selenium-core/scripts/selenium-version.js +5 -0
  127. data/test/selenium-core/scripts/user-extensions.js +3 -0
  128. data/test/selenium-core/scripts/user-extensions.js.sample +75 -0
  129. data/test/selenium-core/scripts/xmlextras.js +153 -0
  130. data/test/selenium-core/selenium-logo.png +0 -0
  131. data/test/selenium-core/selenium-test.css +43 -0
  132. data/test/selenium-core/selenium.css +299 -0
  133. data/test/selenium-core/xpath/dom.js +428 -0
  134. data/test/selenium-core/xpath/misc.js +252 -0
  135. data/test/selenium-core/xpath/xpath.js +2223 -0
  136. data/test/selenium/_login_as_cmsadmin.rsel +4 -0
  137. data/test/selenium/dashboard.rsel +5 -0
  138. data/test/selenium/html_blocks.rsel +4 -0
  139. data/test/selenium/login/failed_login.rsel +8 -0
  140. data/test/selenium/login/successful_login.rsel +9 -0
  141. data/test/selenium/page_templates.rsel +12 -0
  142. data/test/selenium/pages/edit_properties.rsel +5 -0
  143. data/test/selenium/site/view_home_page.rsel +4 -0
  144. data/test/selenium/sitemap/move_page.rsel +9 -0
  145. data/test/selenium/sitemap/open_section.rsel +6 -0
  146. data/test/selenium/sitemap/select_page.rsel +12 -0
  147. data/test/selenium/sitemap/select_section.rsel +17 -0
  148. data/test/test_helper.rb +30 -12
  149. data/test/unit/behaviors/attaching_test.rb +4 -6
  150. data/test/unit/behaviors/connectable_test.rb +29 -0
  151. data/test/unit/behaviors/publishable_test.rb +40 -9
  152. data/test/unit/behaviors/versioning_test.rb +36 -0
  153. data/test/unit/helpers/menu_helper_test.rb +5 -2
  154. data/test/unit/helpers/page_helper_test.rb +2 -0
  155. data/test/unit/lib/cms/sitemap_test.rb +206 -0
  156. data/test/unit/models/attachment_test.rb +51 -31
  157. data/test/unit/models/file_block_test.rb +74 -55
  158. data/test/unit/models/link_test.rb +44 -0
  159. data/test/unit/models/page_test.rb +290 -224
  160. data/test/unit/models/sections_test.rb +144 -44
  161. data/test/unit/models/user_test.rb +28 -18
  162. metadata +581 -350
  163. data/app/views/cms/section_nodes/_section_node.html.erb +0 -10
  164. data/test/unit/models/section_node_test.rb +0 -92
@@ -0,0 +1,69 @@
1
+ /*
2
+ * Copyright 2004 ThoughtWorks, Inc
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ elementFindMatchingChildren = function(element, selector) {
19
+ var matches = [];
20
+
21
+ var childCount = element.childNodes.length;
22
+ for (var i=0; i<childCount; i++) {
23
+ var child = element.childNodes[i];
24
+ if (selector(child)) {
25
+ matches.push(child);
26
+ } else {
27
+ childMatches = elementFindMatchingChildren(child, selector);
28
+ matches.push(childMatches);
29
+ }
30
+ }
31
+
32
+ return matches.flatten();
33
+ }
34
+
35
+ ELEMENT_NODE_TYPE = 1;
36
+
37
+ elementFindFirstMatchingChild = function(element, selector) {
38
+
39
+ var childCount = element.childNodes.length;
40
+ for (var i=0; i<childCount; i++) {
41
+ var child = element.childNodes[i];
42
+ if (child.nodeType == ELEMENT_NODE_TYPE) {
43
+ if (selector(child)) {
44
+ return child;
45
+ }
46
+ result = elementFindFirstMatchingChild(child, selector);
47
+ if (result) {
48
+ return result;
49
+ }
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+
55
+ elementFindFirstMatchingParent = function(element, selector) {
56
+ var current = element.parentNode;
57
+ while (current != null) {
58
+ if (selector(current)) {
59
+ break;
60
+ }
61
+ current = current.parentNode;
62
+ }
63
+ return current;
64
+ }
65
+
66
+ elementFindMatchingChildById = function(element, id) {
67
+ return elementFindFirstMatchingChild(element, function(element){return element.id==id} );
68
+ }
69
+
@@ -0,0 +1,894 @@
1
+ /*
2
+ * Copyright 2004 ThoughtWorks, Inc
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ // This script contains a badly-organised collection of miscellaneous
19
+ // functions that really better homes.
20
+
21
+ function classCreate() {
22
+ return function() {
23
+ this.initialize.apply(this, arguments);
24
+ }
25
+ }
26
+
27
+ function objectExtend(destination, source) {
28
+ for (var property in source) {
29
+ destination[property] = source[property];
30
+ }
31
+ return destination;
32
+ }
33
+
34
+ function sel$() {
35
+ var results = [], element;
36
+ for (var i = 0; i < arguments.length; i++) {
37
+ element = arguments[i];
38
+ if (typeof element == 'string')
39
+ element = document.getElementById(element);
40
+ results[results.length] = element;
41
+ }
42
+ return results.length < 2 ? results[0] : results;
43
+ }
44
+
45
+ function sel$A(iterable) {
46
+ if (!iterable) return [];
47
+ if (iterable.toArray) {
48
+ return iterable.toArray();
49
+ } else {
50
+ var results = [];
51
+ for (var i = 0; i < iterable.length; i++)
52
+ results.push(iterable[i]);
53
+ return results;
54
+ }
55
+ }
56
+
57
+ function fnBind() {
58
+ var args = sel$A(arguments), __method = args.shift(), object = args.shift();
59
+ var retval = function() {
60
+ return __method.apply(object, args.concat(sel$A(arguments)));
61
+ }
62
+ retval.__method = __method;
63
+ return retval;
64
+ }
65
+
66
+ function fnBindAsEventListener(fn, object) {
67
+ var __method = fn;
68
+ return function(event) {
69
+ return __method.call(object, event || window.event);
70
+ }
71
+ }
72
+
73
+ function removeClassName(element, name) {
74
+ var re = new RegExp("\\b" + name + "\\b", "g");
75
+ element.className = element.className.replace(re, "");
76
+ }
77
+
78
+ function addClassName(element, name) {
79
+ element.className = element.className + ' ' + name;
80
+ }
81
+
82
+ function elementSetStyle(element, style) {
83
+ for (var name in style) {
84
+ var value = style[name];
85
+ if (value == null) value = "";
86
+ element.style[name] = value;
87
+ }
88
+ }
89
+
90
+ function elementGetStyle(element, style) {
91
+ var value = element.style[style];
92
+ if (!value) {
93
+ if (document.defaultView && document.defaultView.getComputedStyle) {
94
+ var css = document.defaultView.getComputedStyle(element, null);
95
+ value = css ? css.getPropertyValue(style) : null;
96
+ } else if (element.currentStyle) {
97
+ value = element.currentStyle[style];
98
+ }
99
+ }
100
+
101
+ /** DGF necessary?
102
+ if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
103
+ if (Element.getStyle(element, 'position') == 'static') value = 'auto'; */
104
+
105
+ return value == 'auto' ? null : value;
106
+ }
107
+
108
+ String.prototype.trim = function() {
109
+ var result = this.replace(/^\s+/g, "");
110
+ // strip leading
111
+ return result.replace(/\s+$/g, "");
112
+ // strip trailing
113
+ };
114
+ String.prototype.lcfirst = function() {
115
+ return this.charAt(0).toLowerCase() + this.substr(1);
116
+ };
117
+ String.prototype.ucfirst = function() {
118
+ return this.charAt(0).toUpperCase() + this.substr(1);
119
+ };
120
+ String.prototype.startsWith = function(str) {
121
+ return this.indexOf(str) == 0;
122
+ };
123
+
124
+ // Returns the text in this element
125
+ function getText(element) {
126
+ var text = "";
127
+
128
+ var isRecentFirefox = (browserVersion.isFirefox && browserVersion.firefoxVersion >= "1.5");
129
+ if (isRecentFirefox || browserVersion.isKonqueror || browserVersion.isSafari || browserVersion.isOpera) {
130
+ text = getTextContent(element);
131
+ } else if (element.textContent) {
132
+ text = element.textContent;
133
+ } else if (element.innerText) {
134
+ text = element.innerText;
135
+ }
136
+
137
+ text = normalizeNewlines(text);
138
+ text = normalizeSpaces(text);
139
+
140
+ return text.trim();
141
+ }
142
+
143
+ function getTextContent(element, preformatted) {
144
+ if (element.nodeType == 3 /*Node.TEXT_NODE*/) {
145
+ var text = element.data;
146
+ if (!preformatted) {
147
+ text = text.replace(/\n|\r|\t/g, " ");
148
+ }
149
+ return text;
150
+ }
151
+ if (element.nodeType == 1 /*Node.ELEMENT_NODE*/) {
152
+ var childrenPreformatted = preformatted || (element.tagName == "PRE");
153
+ var text = "";
154
+ for (var i = 0; i < element.childNodes.length; i++) {
155
+ var child = element.childNodes.item(i);
156
+ text += getTextContent(child, childrenPreformatted);
157
+ }
158
+ // Handle block elements that introduce newlines
159
+ // -- From HTML spec:
160
+ //<!ENTITY % block
161
+ // "P | %heading; | %list; | %preformatted; | DL | DIV | NOSCRIPT |
162
+ // BLOCKQUOTE | F:wORM | HR | TABLE | FIELDSET | ADDRESS">
163
+ //
164
+ // TODO: should potentially introduce multiple newlines to separate blocks
165
+ if (element.tagName == "P" || element.tagName == "BR" || element.tagName == "HR" || element.tagName == "DIV") {
166
+ text += "\n";
167
+ }
168
+ return text;
169
+ }
170
+ return '';
171
+ }
172
+
173
+ /**
174
+ * Convert all newlines to \m
175
+ */
176
+ function normalizeNewlines(text)
177
+ {
178
+ return text.replace(/\r\n|\r/g, "\n");
179
+ }
180
+
181
+ /**
182
+ * Replace multiple sequential spaces with a single space, and then convert &nbsp; to space.
183
+ */
184
+ function normalizeSpaces(text)
185
+ {
186
+ // IE has already done this conversion, so doing it again will remove multiple nbsp
187
+ if (browserVersion.isIE)
188
+ {
189
+ return text;
190
+ }
191
+
192
+ // Replace multiple spaces with a single space
193
+ // TODO - this shouldn't occur inside PRE elements
194
+ text = text.replace(/\ +/g, " ");
195
+
196
+ // Replace &nbsp; with a space
197
+ var nbspPattern = new RegExp(String.fromCharCode(160), "g");
198
+ if (browserVersion.isSafari) {
199
+ return replaceAll(text, String.fromCharCode(160), " ");
200
+ } else {
201
+ return text.replace(nbspPattern, " ");
202
+ }
203
+ }
204
+
205
+ function replaceAll(text, oldText, newText) {
206
+ while (text.indexOf(oldText) != -1) {
207
+ text = text.replace(oldText, newText);
208
+ }
209
+ return text;
210
+ }
211
+
212
+
213
+ function xmlDecode(text) {
214
+ text = text.replace(/&quot;/g, '"');
215
+ text = text.replace(/&apos;/g, "'");
216
+ text = text.replace(/&lt;/g, "<");
217
+ text = text.replace(/&gt;/g, ">");
218
+ text = text.replace(/&amp;/g, "&");
219
+ return text;
220
+ }
221
+
222
+ // Sets the text in this element
223
+ function setText(element, text) {
224
+ if (element.textContent != null) {
225
+ element.textContent = text;
226
+ } else if (element.innerText != null) {
227
+ element.innerText = text;
228
+ }
229
+ }
230
+
231
+ // Get the value of an <input> element
232
+ function getInputValue(inputElement) {
233
+ if (inputElement.type) {
234
+ if (inputElement.type.toUpperCase() == 'CHECKBOX' ||
235
+ inputElement.type.toUpperCase() == 'RADIO')
236
+ {
237
+ return (inputElement.checked ? 'on' : 'off');
238
+ }
239
+ }
240
+ if (inputElement.value == null) {
241
+ throw new SeleniumError("This element has no value; is it really a form field?");
242
+ }
243
+ return inputElement.value;
244
+ }
245
+
246
+ /* Fire an event in a browser-compatible manner */
247
+ function triggerEvent(element, eventType, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
248
+ canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
249
+ if (element.fireEvent) {
250
+ var evt = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
251
+ element.fireEvent('on' + eventType, evt);
252
+ }
253
+ else {
254
+ var evt = document.createEvent('HTMLEvents');
255
+
256
+ try {
257
+ evt.shiftKey = shiftKeyDown;
258
+ evt.metaKey = metaKeyDown;
259
+ evt.altKey = altKeyDown;
260
+ evt.ctrlKey = controlKeyDown;
261
+ } catch (e) {
262
+ // On Firefox 1.0, you can only set these during initMouseEvent or initKeyEvent
263
+ // we'll have to ignore them here
264
+ LOG.exception(e);
265
+ }
266
+
267
+ evt.initEvent(eventType, canBubble, true);
268
+ element.dispatchEvent(evt);
269
+ }
270
+ }
271
+
272
+ function getKeyCodeFromKeySequence(keySequence) {
273
+ var match = /^\\(\d{1,3})$/.exec(keySequence);
274
+ if (match != null) {
275
+ return match[1];
276
+ }
277
+ match = /^.$/.exec(keySequence);
278
+ if (match != null) {
279
+ return match[0].charCodeAt(0);
280
+ }
281
+ // this is for backward compatibility with existing tests
282
+ // 1 digit ascii codes will break however because they are used for the digit chars
283
+ match = /^\d{2,3}$/.exec(keySequence);
284
+ if (match != null) {
285
+ return match[0];
286
+ }
287
+ throw new SeleniumError("invalid keySequence");
288
+ }
289
+
290
+ function createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
291
+ var evt = element.ownerDocument.createEventObject();
292
+ evt.shiftKey = shiftKeyDown;
293
+ evt.metaKey = metaKeyDown;
294
+ evt.altKey = altKeyDown;
295
+ evt.ctrlKey = controlKeyDown;
296
+ return evt;
297
+ }
298
+
299
+ function triggerKeyEvent(element, eventType, keySequence, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
300
+ var keycode = getKeyCodeFromKeySequence(keySequence);
301
+ canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
302
+ if (element.fireEvent) {
303
+ var keyEvent = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
304
+ keyEvent.keyCode = keycode;
305
+ element.fireEvent('on' + eventType, keyEvent);
306
+ }
307
+ else {
308
+ var evt;
309
+ if (window.KeyEvent) {
310
+ evt = document.createEvent('KeyEvents');
311
+ evt.initKeyEvent(eventType, true, true, window, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown, keycode, keycode);
312
+ } else {
313
+ evt = document.createEvent('UIEvents');
314
+
315
+ evt.shiftKey = shiftKeyDown;
316
+ evt.metaKey = metaKeyDown;
317
+ evt.altKey = altKeyDown;
318
+ evt.ctrlKey = controlKeyDown;
319
+
320
+ evt.initUIEvent(eventType, true, true, window, 1);
321
+ evt.keyCode = keycode;
322
+ evt.which = keycode;
323
+ }
324
+
325
+ element.dispatchEvent(evt);
326
+ }
327
+ }
328
+
329
+ function removeLoadListener(element, command) {
330
+ LOG.debug('Removing loadListenter for ' + element + ', ' + command);
331
+ if (window.removeEventListener)
332
+ element.removeEventListener("load", command, true);
333
+ else if (window.detachEvent)
334
+ element.detachEvent("onload", command);
335
+ }
336
+
337
+ function addLoadListener(element, command) {
338
+ LOG.debug('Adding loadListenter for ' + element + ', ' + command);
339
+ var augmentedCommand = function() {
340
+ command.call(this, element);
341
+ }
342
+ if (window.addEventListener && !browserVersion.isOpera)
343
+ element.addEventListener("load", augmentedCommand, true);
344
+ else if (window.attachEvent)
345
+ element.attachEvent("onload", augmentedCommand);
346
+ }
347
+
348
+ /**
349
+ * Override the broken getFunctionName() method from JsUnit
350
+ * This file must be loaded _after_ the jsunitCore.js
351
+ */
352
+ function getFunctionName(aFunction) {
353
+ var regexpResult = aFunction.toString().match(/function (\w*)/);
354
+ if (regexpResult && regexpResult[1]) {
355
+ return regexpResult[1];
356
+ }
357
+ return 'anonymous';
358
+ }
359
+
360
+ function getDocumentBase(doc) {
361
+ var bases = document.getElementsByTagName("base");
362
+ if (bases && bases.length && bases[0].href) {
363
+ return bases[0].href;
364
+ }
365
+ return "";
366
+ }
367
+
368
+ function getTagName(element) {
369
+ var tagName;
370
+ if (element && element.tagName && element.tagName.toLowerCase) {
371
+ tagName = element.tagName.toLowerCase();
372
+ }
373
+ return tagName;
374
+ }
375
+
376
+ function selArrayToString(a) {
377
+ if (isArray(a)) {
378
+ // DGF copying the array, because the array-like object may be a non-modifiable nodelist
379
+ var retval = [];
380
+ for (var i = 0; i < a.length; i++) {
381
+ var item = a[i];
382
+ var replaced = new String(item).replace(/([,\\])/g, '\\$1');
383
+ retval[i] = replaced;
384
+ }
385
+ return retval;
386
+ }
387
+ return new String(a);
388
+ }
389
+
390
+
391
+ function isArray(x) {
392
+ return ((typeof x) == "object") && (x["length"] != null);
393
+ }
394
+
395
+ function absolutify(url, baseUrl) {
396
+ /** returns a relative url in its absolute form, given by baseUrl.
397
+ *
398
+ * This function is a little odd, because it can take baseUrls that
399
+ * aren't necessarily directories. It uses the same rules as the HTML
400
+ * &lt;base&gt; tag; if the baseUrl doesn't end with "/", we'll assume
401
+ * that it points to a file, and strip the filename off to find its
402
+ * base directory.
403
+ *
404
+ * So absolutify("foo", "http://x/bar") will return "http://x/foo" (stripping off bar),
405
+ * whereas absolutify("foo", "http://x/bar/") will return "http://x/bar/foo" (preserving bar).
406
+ * Naturally absolutify("foo", "http://x") will return "http://x/foo", appropriately.
407
+ *
408
+ * @param url the url to make absolute; if this url is already absolute, we'll just return that, unchanged
409
+ * @param baseUrl the baseUrl from which we'll absolutify, following the rules above.
410
+ * @return 'url' if it was already absolute, or the absolutized version of url if it was not absolute.
411
+ */
412
+
413
+ // DGF isn't there some library we could use for this?
414
+
415
+ if (/^\w+:/.test(url)) {
416
+ // it's already absolute
417
+ return url;
418
+ }
419
+
420
+ var loc;
421
+ try {
422
+ loc = parseUrl(baseUrl);
423
+ } catch (e) {
424
+ // is it an absolute windows file path? let's play the hero in that case
425
+ if (/^\w:\\/.test(baseUrl)) {
426
+ baseUrl = "file:///" + baseUrl.replace(/\\/g, "/");
427
+ loc = parseUrl(baseUrl);
428
+ } else {
429
+ throw new SeleniumError("baseUrl wasn't absolute: " + baseUrl);
430
+ }
431
+ }
432
+ loc.search = null;
433
+ loc.hash = null;
434
+
435
+ // if url begins with /, then that's the whole pathname
436
+ if (/^\//.test(url)) {
437
+ loc.pathname = url;
438
+ var result = reassembleLocation(loc);
439
+ return result;
440
+ }
441
+
442
+ // if pathname is null, then we'll just append "/" + the url
443
+ if (!loc.pathname) {
444
+ loc.pathname = "/" + url;
445
+ var result = reassembleLocation(loc);
446
+ return result;
447
+ }
448
+
449
+ // if pathname ends with /, just append url
450
+ if (/\/$/.test(loc.pathname)) {
451
+ loc.pathname += url;
452
+ var result = reassembleLocation(loc);
453
+ return result;
454
+ }
455
+
456
+ // if we're here, then the baseUrl has a pathname, but it doesn't end with /
457
+ // in that case, we replace everything after the final / with the relative url
458
+ loc.pathname = loc.pathname.replace(/[^\/\\]+$/, url);
459
+ var result = reassembleLocation(loc);
460
+ return result;
461
+
462
+ }
463
+
464
+ var URL_REGEX = /^((\w+):\/\/)(([^:]+):?([^@]+)?@)?([^\/\?:]*):?(\d+)?(\/?[^\?#]+)?\??([^#]+)?#?(.+)?/;
465
+
466
+ function parseUrl(url) {
467
+ var fields = ['url', null, 'protocol', null, 'username', 'password', 'host', 'port', 'pathname', 'search', 'hash'];
468
+ var result = URL_REGEX.exec(url);
469
+ if (!result) {
470
+ throw new SeleniumError("Invalid URL: " + url);
471
+ }
472
+ var loc = new Object();
473
+ for (var i = 0; i < fields.length; i++) {
474
+ var field = fields[i];
475
+ if (field == null) {
476
+ continue;
477
+ }
478
+ loc[field] = result[i];
479
+ }
480
+ return loc;
481
+ }
482
+
483
+ function reassembleLocation(loc) {
484
+ if (!loc.protocol) {
485
+ throw new Error("Not a valid location object: " + o2s(loc));
486
+ }
487
+ var protocol = loc.protocol;
488
+ protocol = protocol.replace(/:$/, "");
489
+ var url = protocol + "://";
490
+ if (loc.username) {
491
+ url += loc.username;
492
+ if (loc.password) {
493
+ url += ":" + loc.password;
494
+ }
495
+ url += "@";
496
+ }
497
+ if (loc.host) {
498
+ url += loc.host;
499
+ }
500
+
501
+ if (loc.port) {
502
+ url += ":" + loc.port;
503
+ }
504
+
505
+ if (loc.pathname) {
506
+ url += loc.pathname;
507
+ }
508
+
509
+ if (loc.search) {
510
+ url += "?" + loc.search;
511
+ }
512
+ if (loc.hash) {
513
+ var hash = loc.hash;
514
+ hash = loc.hash.replace(/^#/, "");
515
+ url += "#" + hash;
516
+ }
517
+ return url;
518
+ }
519
+
520
+ function canonicalize(url) {
521
+ var tempLink = window.document.createElement("link");
522
+ tempLink.href = url; // this will canonicalize the href on most browsers
523
+ var loc = parseUrl(tempLink.href)
524
+ if (!/\/\.\.\//.test(loc.pathname)) {
525
+ return tempLink.href;
526
+ }
527
+ // didn't work... let's try it the hard way
528
+ var originalParts = loc.pathname.split("/");
529
+ var newParts = [];
530
+ newParts.push(originalParts.shift());
531
+ for (var i = 0; i < originalParts.length; i++) {
532
+ var part = originalParts[i];
533
+ if (".." == part) {
534
+ newParts.pop();
535
+ continue;
536
+ }
537
+ newParts.push(part);
538
+ }
539
+ loc.pathname = newParts.join("/");
540
+ return reassembleLocation(loc);
541
+ }
542
+
543
+ function extractExceptionMessage(ex) {
544
+ if (ex == null) return "null exception";
545
+ if (ex.message != null) return ex.message;
546
+ if (ex.toString && ex.toString() != null) return ex.toString();
547
+ }
548
+
549
+
550
+ function describe(object, delimiter) {
551
+ var props = new Array();
552
+ for (var prop in object) {
553
+ try {
554
+ props.push(prop + " -> " + object[prop]);
555
+ } catch (e) {
556
+ props.push(prop + " -> [htmlutils: ack! couldn't read this property! (Permission Denied?)]");
557
+ }
558
+ }
559
+ return props.join(delimiter || '\n');
560
+ }
561
+
562
+ var PatternMatcher = function(pattern) {
563
+ this.selectStrategy(pattern);
564
+ };
565
+ PatternMatcher.prototype = {
566
+
567
+ selectStrategy: function(pattern) {
568
+ this.pattern = pattern;
569
+ var strategyName = 'glob';
570
+ // by default
571
+ if (/^([a-z-]+):(.*)/.test(pattern)) {
572
+ var possibleNewStrategyName = RegExp.$1;
573
+ var possibleNewPattern = RegExp.$2;
574
+ if (PatternMatcher.strategies[possibleNewStrategyName]) {
575
+ strategyName = possibleNewStrategyName;
576
+ pattern = possibleNewPattern;
577
+ }
578
+ }
579
+ var matchStrategy = PatternMatcher.strategies[strategyName];
580
+ if (!matchStrategy) {
581
+ throw new SeleniumError("cannot find PatternMatcher.strategies." + strategyName);
582
+ }
583
+ this.strategy = matchStrategy;
584
+ this.matcher = new matchStrategy(pattern);
585
+ },
586
+
587
+ matches: function(actual) {
588
+ return this.matcher.matches(actual + '');
589
+ // Note: appending an empty string avoids a Konqueror bug
590
+ }
591
+
592
+ };
593
+
594
+ /**
595
+ * A "static" convenience method for easy matching
596
+ */
597
+ PatternMatcher.matches = function(pattern, actual) {
598
+ return new PatternMatcher(pattern).matches(actual);
599
+ };
600
+
601
+ PatternMatcher.strategies = {
602
+
603
+ /**
604
+ * Exact matching, e.g. "exact:***"
605
+ */
606
+ exact: function(expected) {
607
+ this.expected = expected;
608
+ this.matches = function(actual) {
609
+ return actual == this.expected;
610
+ };
611
+ },
612
+
613
+ /**
614
+ * Match by regular expression, e.g. "regexp:^[0-9]+$"
615
+ */
616
+ regexp: function(regexpString) {
617
+ this.regexp = new RegExp(regexpString);
618
+ this.matches = function(actual) {
619
+ return this.regexp.test(actual);
620
+ };
621
+ },
622
+
623
+ regex: function(regexpString) {
624
+ this.regexp = new RegExp(regexpString);
625
+ this.matches = function(actual) {
626
+ return this.regexp.test(actual);
627
+ };
628
+ },
629
+
630
+ /**
631
+ * "globContains" (aka "wildmat") patterns, e.g. "glob:one,two,*",
632
+ * but don't require a perfect match; instead succeed if actual
633
+ * contains something that matches globString.
634
+ * Making this distinction is motivated by a bug in IE6 which
635
+ * leads to the browser hanging if we implement *TextPresent tests
636
+ * by just matching against a regular expression beginning and
637
+ * ending with ".*". The globcontains strategy allows us to satisfy
638
+ * the functional needs of the *TextPresent ops more efficiently
639
+ * and so avoid running into this IE6 freeze.
640
+ */
641
+ globContains: function(globString) {
642
+ this.regexp = new RegExp(PatternMatcher.regexpFromGlobContains(globString));
643
+ this.matches = function(actual) {
644
+ return this.regexp.test(actual);
645
+ };
646
+ },
647
+
648
+
649
+ /**
650
+ * "glob" (aka "wildmat") patterns, e.g. "glob:one,two,*"
651
+ */
652
+ glob: function(globString) {
653
+ this.regexp = new RegExp(PatternMatcher.regexpFromGlob(globString));
654
+ this.matches = function(actual) {
655
+ return this.regexp.test(actual);
656
+ };
657
+ }
658
+
659
+ };
660
+
661
+ PatternMatcher.convertGlobMetaCharsToRegexpMetaChars = function(glob) {
662
+ var re = glob;
663
+ re = re.replace(/([.^$+(){}\[\]\\|])/g, "\\$1");
664
+ re = re.replace(/\?/g, "(.|[\r\n])");
665
+ re = re.replace(/\*/g, "(.|[\r\n])*");
666
+ return re;
667
+ };
668
+
669
+ PatternMatcher.regexpFromGlobContains = function(globContains) {
670
+ return PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(globContains);
671
+ };
672
+
673
+ PatternMatcher.regexpFromGlob = function(glob) {
674
+ return "^" + PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(glob) + "$";
675
+ };
676
+
677
+ var Assert = {
678
+
679
+ fail: function(message) {
680
+ throw new AssertionFailedError(message);
681
+ },
682
+
683
+ /*
684
+ * Assert.equals(comment?, expected, actual)
685
+ */
686
+ equals: function() {
687
+ var args = new AssertionArguments(arguments);
688
+ if (args.expected === args.actual) {
689
+ return;
690
+ }
691
+ Assert.fail(args.comment +
692
+ "Expected '" + args.expected +
693
+ "' but was '" + args.actual + "'");
694
+ },
695
+
696
+ /*
697
+ * Assert.matches(comment?, pattern, actual)
698
+ */
699
+ matches: function() {
700
+ var args = new AssertionArguments(arguments);
701
+ if (PatternMatcher.matches(args.expected, args.actual)) {
702
+ return;
703
+ }
704
+ Assert.fail(args.comment +
705
+ "Actual value '" + args.actual +
706
+ "' did not match '" + args.expected + "'");
707
+ },
708
+
709
+ /*
710
+ * Assert.notMtches(comment?, pattern, actual)
711
+ */
712
+ notMatches: function() {
713
+ var args = new AssertionArguments(arguments);
714
+ if (!PatternMatcher.matches(args.expected, args.actual)) {
715
+ return;
716
+ }
717
+ Assert.fail(args.comment +
718
+ "Actual value '" + args.actual +
719
+ "' did match '" + args.expected + "'");
720
+ }
721
+
722
+ };
723
+
724
+ // Preprocess the arguments to allow for an optional comment.
725
+ function AssertionArguments(args) {
726
+ if (args.length == 2) {
727
+ this.comment = "";
728
+ this.expected = args[0];
729
+ this.actual = args[1];
730
+ } else {
731
+ this.comment = args[0] + "; ";
732
+ this.expected = args[1];
733
+ this.actual = args[2];
734
+ }
735
+ }
736
+
737
+ function AssertionFailedError(message) {
738
+ this.isAssertionFailedError = true;
739
+ this.isSeleniumError = true;
740
+ this.message = message;
741
+ this.failureMessage = message;
742
+ }
743
+
744
+ function SeleniumError(message) {
745
+ var error = new Error(message);
746
+ if (typeof(arguments.caller) != 'undefined') { // IE, not ECMA
747
+ var result = '';
748
+ for (var a = arguments.caller; a != null; a = a.caller) {
749
+ result += '> ' + a.callee.toString() + '\n';
750
+ if (a.caller == a) {
751
+ result += '*';
752
+ break;
753
+ }
754
+ }
755
+ error.stack = result;
756
+ }
757
+ error.isSeleniumError = true;
758
+ return error;
759
+ }
760
+
761
+ function highlight(element) {
762
+ var highLightColor = "yellow";
763
+ if (element.originalColor == undefined) { // avoid picking up highlight
764
+ element.originalColor = elementGetStyle(element, "background-color");
765
+ }
766
+ elementSetStyle(element, {"backgroundColor" : highLightColor});
767
+ window.setTimeout(function() {
768
+ try {
769
+ //if element is orphan, probably page of it has already gone, so ignore
770
+ if (!element.parentNode) {
771
+ return;
772
+ }
773
+ elementSetStyle(element, {"backgroundColor" : element.originalColor});
774
+ } catch (e) {} // DGF unhighlighting is very dangerous and low priority
775
+ }, 200);
776
+ }
777
+
778
+
779
+
780
+ // for use from vs.2003 debugger
781
+ function o2s(obj) {
782
+ var s = "";
783
+ for (key in obj) {
784
+ var line = key + "->" + obj[key];
785
+ line.replace("\n", " ");
786
+ s += line + "\n";
787
+ }
788
+ return s;
789
+ }
790
+
791
+ var seenReadyStateWarning = false;
792
+
793
+ function openSeparateApplicationWindow(url, suppressMozillaWarning) {
794
+ // resize the Selenium window itself
795
+ window.resizeTo(1200, 500);
796
+ window.moveTo(window.screenX, 0);
797
+
798
+ var appWindow = window.open(url + '?start=true', 'main');
799
+ if (appWindow == null) {
800
+ var errorMessage = "Couldn't open app window; is the pop-up blocker enabled?"
801
+ LOG.error(errorMessage);
802
+ throw new Error("Couldn't open app window; is the pop-up blocker enabled?");
803
+ }
804
+ try {
805
+ var windowHeight = 500;
806
+ if (window.outerHeight) {
807
+ windowHeight = window.outerHeight;
808
+ } else if (document.documentElement && document.documentElement.offsetHeight) {
809
+ windowHeight = document.documentElement.offsetHeight;
810
+ }
811
+
812
+ if (window.screenLeft && !window.screenX) window.screenX = window.screenLeft;
813
+ if (window.screenTop && !window.screenY) window.screenY = window.screenTop;
814
+
815
+ appWindow.resizeTo(1200, screen.availHeight - windowHeight - 60);
816
+ appWindow.moveTo(window.screenX, window.screenY + windowHeight + 25);
817
+ } catch (e) {
818
+ LOG.error("Couldn't resize app window");
819
+ LOG.exception(e);
820
+ }
821
+
822
+
823
+ if (!suppressMozillaWarning && window.document.readyState == null && !seenReadyStateWarning) {
824
+ alert("Beware! Mozilla bug 300992 means that we can't always reliably detect when a new page has loaded. Install the Selenium IDE extension or the readyState extension available from selenium.openqa.org to make page load detection more reliable.");
825
+ seenReadyStateWarning = true;
826
+ }
827
+
828
+ return appWindow;
829
+ }
830
+
831
+ var URLConfiguration = classCreate();
832
+ objectExtend(URLConfiguration.prototype, {
833
+ initialize: function() {
834
+ },
835
+ _isQueryParameterTrue: function (name) {
836
+ var parameterValue = this._getQueryParameter(name);
837
+ if (parameterValue == null) return false;
838
+ if (parameterValue.toLowerCase() == "true") return true;
839
+ if (parameterValue.toLowerCase() == "on") return true;
840
+ return false;
841
+ },
842
+
843
+ _getQueryParameter: function(searchKey) {
844
+ var str = this.queryString
845
+ if (str == null) return null;
846
+ var clauses = str.split('&');
847
+ for (var i = 0; i < clauses.length; i++) {
848
+ var keyValuePair = clauses[i].split('=', 2);
849
+ var key = unescape(keyValuePair[0]);
850
+ if (key == searchKey) {
851
+ return unescape(keyValuePair[1]);
852
+ }
853
+ }
854
+ return null;
855
+ },
856
+
857
+ _extractArgs: function() {
858
+ var str = SeleniumHTARunner.commandLine;
859
+ if (str == null || str == "") return new Array();
860
+ var matches = str.match(/(?:\"([^\"]+)\"|(?!\"([^\"]+)\")(\S+))/g);
861
+ // We either want non quote stuff ([^"]+) surrounded by quotes
862
+ // or we want to look-ahead, see that the next character isn't
863
+ // a quoted argument, and then grab all the non-space stuff
864
+ // this will return for the line: "foo" bar
865
+ // the results "\"foo\"" and "bar"
866
+
867
+ // So, let's unquote the quoted arguments:
868
+ var args = new Array;
869
+ for (var i = 0; i < matches.length; i++) {
870
+ args[i] = matches[i];
871
+ args[i] = args[i].replace(/^"(.*)"$/, "$1");
872
+ }
873
+ return args;
874
+ },
875
+
876
+ isMultiWindowMode:function() {
877
+ return this._isQueryParameterTrue('multiWindow');
878
+ },
879
+
880
+ getBaseUrl:function() {
881
+ return this._getQueryParameter('baseUrl');
882
+
883
+ }
884
+ });
885
+
886
+
887
+ function safeScrollIntoView(element) {
888
+ if (element.scrollIntoView) {
889
+ element.scrollIntoView(false);
890
+ return;
891
+ }
892
+ // TODO: work out how to scroll browsers that don't support
893
+ // scrollIntoView (like Konqueror)
894
+ }