View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software 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    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.regexp;
16  
17  import org.htmlunit.WebDriverTestCase;
18  import org.htmlunit.junit.BrowserRunner;
19  import org.htmlunit.junit.annotation.Alerts;
20  import org.htmlunit.junit.annotation.HtmlUnitNYI;
21  import org.junit.Test;
22  import org.junit.runner.RunWith;
23  import org.openqa.selenium.WebDriver;
24  
25  /**
26   * Tests for the RegEx support.
27   *
28   * @author Ronald Brill
29   */
30  @RunWith(BrowserRunner.class)
31  public class RegExpTest extends WebDriverTestCase {
32  
33      /**
34       * @throws Exception if an error occurs
35       */
36      @Test
37      @Alerts({"g", "true", "false", "false", "false"})
38      public void globalCtor() throws Exception {
39          testEvaluateProperties("new RegExp('foo', 'g');");
40      }
41  
42      /**
43       * @throws Exception if an error occurs
44       */
45      @Test
46      @Alerts({"g", "true", "false", "false", "false"})
47      public void global() throws Exception {
48          testEvaluateProperties("/foo/g;");
49      }
50  
51      /**
52       * @throws Exception if an error occurs
53       */
54      @Test
55      @Alerts({"i", "false", "true", "false", "false"})
56      public void ignoreCaseCtor() throws Exception {
57          testEvaluateProperties("new RegExp('foo', 'i');");
58      }
59  
60      /**
61       * @throws Exception if an error occurs
62       */
63      @Test
64      @Alerts({"i", "false", "true", "false", "false"})
65      public void ignoreCase() throws Exception {
66          testEvaluateProperties("/foo/i;");
67      }
68  
69      /**
70       * @throws Exception if an error occurs
71       */
72      @Test
73      @Alerts({"m", "false", "false", "true", "false"})
74      public void multilineCtor() throws Exception {
75          testEvaluateProperties("new RegExp('foo', 'm');");
76      }
77  
78      /**
79       * @throws Exception if an error occurs
80       */
81      @Test
82      @Alerts({"m", "false", "false", "true", "false"})
83      public void multiline() throws Exception {
84          testEvaluateProperties("/foo/m;");
85      }
86  
87      /**
88       * @throws Exception if an error occurs
89       */
90      @Test
91      @Alerts({"y", "false", "false", "false", "true"})
92      public void stickyCtor() throws Exception {
93          testEvaluateProperties("new RegExp('foo', 'y');");
94      }
95  
96      /**
97       * @throws Exception if an error occurs
98       */
99      @Test
100     @Alerts({"y", "false", "false", "false", "true"})
101     public void sticky() throws Exception {
102         testEvaluateProperties("/foo/y;");
103     }
104 
105     /**
106      * @throws Exception if an error occurs
107      */
108     @Test
109     @Alerts({"gm", "true", "false", "true", "false"})
110     public void globalMultilineCtor() throws Exception {
111         testEvaluateProperties("new RegExp('foo', 'gm');");
112     }
113 
114     /**
115      * @throws Exception if an error occurs
116      */
117     @Test
118     @Alerts({"gm", "true", "false", "true", "false"})
119     public void globalMultiline() throws Exception {
120         testEvaluateProperties("/foo/gm;");
121     }
122 
123     /**
124      * @throws Exception if an error occurs
125      */
126     @Test
127     @Alerts({"gi", "true", "true", "false", "false"})
128     public void globalIgnoreCaseCtor() throws Exception {
129         testEvaluateProperties("new RegExp('foo', 'ig');");
130     }
131 
132     /**
133      * @throws Exception if an error occurs
134      */
135     @Test
136     @Alerts({"gi", "true", "true", "false", "false"})
137     public void globalIgnoreCase() throws Exception {
138         testEvaluateProperties("/foo/ig;");
139     }
140 
141     /**
142      * @throws Exception if an error occurs
143      */
144     @Test
145     @Alerts({"gy", "true", "false", "false", "true"})
146     public void globalStickyCtor() throws Exception {
147         testEvaluateProperties("new RegExp('foo', 'gy');");
148     }
149 
150     /**
151      * @throws Exception if an error occurs
152      */
153     @Test
154     @Alerts({"gy", "true", "false", "false", "true"})
155     public void globalSticky() throws Exception {
156         testEvaluateProperties("/foo/gy;");
157     }
158 
159     /**
160      * @throws Exception if an error occurs
161      */
162     @Test
163     @Alerts({"gim", "true", "true", "true", "false"})
164     public void globalMultilineIgnoreCaseCtor() throws Exception {
165         testEvaluateProperties("new RegExp('foo', 'mig');");
166     }
167 
168     /**
169      * @throws Exception if an error occurs
170      */
171     @Test
172     @Alerts({"gim", "true", "true", "true", "false"})
173     public void globalMultilineIgnoreCase() throws Exception {
174         testEvaluateProperties("/foo/gmi;");
175     }
176 
177     /**
178      * @throws Exception if an error occurs
179      */
180     @Test
181     @Alerts({"giy", "true", "true", "false", "true"})
182     public void globalIgnoreCaseStickyCtor() throws Exception {
183         testEvaluateProperties("new RegExp('foo', 'yig');");
184     }
185 
186     /**
187      * @throws Exception if an error occurs
188      */
189     @Test
190     @Alerts({"giy", "true", "true", "false", "true"})
191     public void globalIgnoreCaseSticky() throws Exception {
192         testEvaluateProperties("/foo/ygi;");
193     }
194 
195     /**
196      * @throws Exception if an error occurs
197      */
198     @Test
199     @Alerts({"gmy", "true", "false", "true", "true"})
200     public void globalMultilineStickyCtor() throws Exception {
201         testEvaluateProperties("new RegExp('foo', 'gmy');");
202     }
203 
204     /**
205      * @throws Exception if an error occurs
206      */
207     @Test
208     @Alerts({"gmy", "true", "false", "true", "true"})
209     public void globalMultilineSticky() throws Exception {
210         testEvaluateProperties("/foo/gmy;");
211     }
212 
213     /**
214      * @throws Exception if an error occurs
215      */
216     @Test
217     @Alerts({"im", "false", "true", "true", "false"})
218     public void ignoreCaseMultilineCtor() throws Exception {
219         testEvaluateProperties("new RegExp('foo', 'im');");
220     }
221 
222     /**
223      * @throws Exception if an error occurs
224      */
225     @Test
226     @Alerts({"im", "false", "true", "true", "false"})
227     public void ignoreCaseMultiline() throws Exception {
228         testEvaluateProperties("/foo/mi;");
229     }
230 
231     /**
232      * @throws Exception if an error occurs
233      */
234     @Test
235     @Alerts({"iy", "false", "true", "false", "true"})
236     public void ignoreCaseStickyCtor() throws Exception {
237         testEvaluateProperties("new RegExp('foo', 'yi');");
238     }
239 
240     /**
241      * @throws Exception if an error occurs
242      */
243     @Test
244     @Alerts({"iy", "false", "true", "false", "true"})
245     public void ignoreCaseSticky() throws Exception {
246         testEvaluateProperties("/foo/iy;");
247     }
248 
249     /**
250      * @throws Exception if an error occurs
251      */
252     @Test
253     @Alerts({"my", "false", "false", "true", "true"})
254     public void multilineStickyCtor() throws Exception {
255         testEvaluateProperties("new RegExp('foo', 'my');");
256     }
257 
258     /**
259      * @throws Exception if an error occurs
260      */
261     @Test
262     @Alerts({"my", "false", "false", "true", "true"})
263     public void multilineSticky() throws Exception {
264         testEvaluateProperties("/foo/my;");
265     }
266 
267     private void testEvaluateProperties(final String script) throws Exception {
268         final String html =
269                 "<html>\n"
270                 + "<head>\n"
271                 + "<script>\n"
272                 + LOG_TEXTAREA_FUNCTION
273                 + "function test() {\n"
274                 + "  var regex = " + script + "\n"
275                 + "  log(regex.flags);\n"
276                 + "  log(regex.global);\n"
277                 + "  log(regex.ignoreCase);\n"
278                 + "  log(regex.multiline);\n"
279                 + "  log(regex.sticky);\n"
280                 + "}\n"
281                 + "</script>\n"
282                 + "</head>\n"
283                 + "<body onload='test()'>\n"
284                 + LOG_TEXTAREA
285                 + "</body></html>";
286 
287         loadPageVerifyTextArea2(html);
288     }
289 
290     /**
291      * @throws Exception if an error occurs
292      */
293     @Test
294     @Alerts("false")
295     public void stickyStartOfLine() throws Exception {
296         final String script =
297                 "var regex = /^foo/y;\n"
298                 + "regex.lastIndex = 2;\n"
299                 + "log(regex.test('..foo'));";
300         testEvaluate(script);
301     }
302 
303     /**
304      * @throws Exception if an error occurs
305      */
306     @Test
307     @Alerts({"false", "true"})
308     public void stickyStartOfLineMultiline() throws Exception {
309         final String script =
310                 "var regex = /^foo/my;\n"
311                 + "regex.lastIndex = 2;\n"
312                 + "log(regex.test('..foo'))\n"
313                 + "regex.lastIndex = 2;\n"
314                 + "log(regex.test('.\\nfoo'));";
315         testEvaluate(script);
316     }
317 
318     /**
319      * @throws Exception if an error occurs
320      */
321     @Test
322     @Alerts({"2", "a", "a"})
323     public void stickyAndGlobal() throws Exception {
324         final String script =
325                 "var result = 'aaba'.match(/a/yg);\n"
326                 + "log(result.length);\n"
327                 + "log(result[0]);\n"
328                 + "log(result[1]);\n";
329         testEvaluate(script);
330     }
331 
332     /**
333      * @throws Exception if an error occurs
334      */
335     @Test
336     @Alerts({"1", "a\na"})
337     public void dotAll() throws Exception {
338         final String script =
339                 "var result = 'a\\naba'.match(/a.a/s);\n"
340                 + "log(result.length);\n"
341                 + "log(result[0]);\n";
342         testEvaluate(script);
343     }
344 
345     /**
346      * @throws Exception if an error occurs
347      */
348     @Test
349     @Alerts({"1", "a\na"})
350     public void dotAllAndGlobal() throws Exception {
351         final String script =
352                 "var result = 'a\\naba'.match(/a.a/sg);\n"
353                 + "log(result.length);\n"
354                 + "log(result[0]);\n";
355         testEvaluate(script);
356     }
357 
358     /**
359      * @throws Exception if an error occurs
360      */
361     @Test
362     @Alerts({"0", "undefined", "true", "false", "undefined"})
363     @HtmlUnitNYI(CHROME = {"get undefined", "", "false", "false", "false"},
364             EDGE = {"get undefined", "", "false", "false", "false"},
365             FF = {"get undefined", "", "false", "false", "false"},
366             FF_ESR = {"get undefined", "", "false", "false", "false"})
367     public void flagsProperty() throws Exception {
368         testProperty("flags");
369     }
370 
371     /**
372      * @throws Exception if an error occurs
373      */
374     @Test
375     @Alerts({"0", "undefined", "true", "false", "undefined"})
376     @HtmlUnitNYI(CHROME = {"get undefined", "false", "false", "false", "false"},
377             EDGE = {"get undefined", "false", "false", "false", "false"},
378             FF = {"get undefined", "false", "false", "false", "false"},
379             FF_ESR = {"get undefined", "false", "false", "false", "false"})
380     public void globalProperty() throws Exception {
381         testProperty("global");
382     }
383 
384     /**
385      * @throws Exception if an error occurs
386      */
387     @Test
388     @Alerts({"0", "undefined", "true", "false", "undefined"})
389     @HtmlUnitNYI(CHROME = {"get undefined", "false", "false", "false", "false"},
390             EDGE = {"get undefined", "false", "false", "false", "false"},
391             FF = {"get undefined", "false", "false", "false", "false"},
392             FF_ESR = {"get undefined", "false", "false", "false", "false"})
393     public void ignoreCaseProperty() throws Exception {
394         testProperty("ignoreCase");
395     }
396 
397     /**
398      * @throws Exception if an error occurs
399      */
400     @Test
401     @Alerts({"0", "undefined", "true", "false", "undefined"})
402     @HtmlUnitNYI(CHROME = {"get undefined", "false", "false", "false", "false"},
403             EDGE = {"get undefined", "false", "false", "false", "false"},
404             FF = {"get undefined", "false", "false", "false", "false"},
405             FF_ESR = {"get undefined", "false", "false", "false", "false"})
406     public void multilineProperty() throws Exception {
407         testProperty("multiline");
408     }
409 
410     /**
411      * @throws Exception if an error occurs
412      */
413     @Test
414     @Alerts({"0", "undefined", "true", "false", "undefined"})
415     @HtmlUnitNYI(CHROME = {"get undefined", "false", "false", "false", "false"},
416             EDGE = {"get undefined", "false", "false", "false", "false"},
417             FF = {"get undefined", "false", "false", "false", "false"},
418             FF_ESR = {"get undefined", "false", "false", "false", "false"})
419     public void stickyProperty() throws Exception {
420         testProperty("sticky");
421     }
422 
423     /**
424      * @throws Exception if an error occurs
425      */
426     @Test
427     @Alerts({"0", "undefined", "true", "false", "undefined"})
428     @HtmlUnitNYI(CHROME = {"get undefined", "false", "false", "false", "false"},
429             EDGE = {"get undefined", "false", "false", "false", "false"},
430             FF = {"get undefined", "false", "false", "false", "false"},
431             FF_ESR = {"get undefined", "false", "false", "false", "false"})
432     public void dotAllProperty() throws Exception {
433         testProperty("dotAll");
434     }
435 
436     private void testProperty(final String property) throws Exception {
437         final String script =
438                 "var get = Object.getOwnPropertyDescriptor(RegExp.prototype, '" + property + "');\n"
439                 + "log(get.get == undefined ? 'get undefined' : get.get.length);\n"
440                 + "log(get.value);\n"
441                 + "log(get.configurable);\n"
442                 + "log(get.enumerable);\n"
443                 + "log(get.writable);\n";
444 
445         testEvaluate(script);
446     }
447 
448     /**
449      * @throws Exception if an error occurs
450      */
451     @Test
452     public void stringReplace() throws Exception {
453         testEvaluate("xyz", "''.replace('', 'xyz')");
454         testEvaluate("1", "'121'.replace('21', '')");
455         testEvaluate("xyz121", "'121'.replace('', 'xyz')");
456         testEvaluate("a$c21", "'121'.replace('1', 'a$c')");
457         testEvaluate("a121", "'121'.replace('1', 'a$&')");
458         testEvaluate("a$c21", "'121'.replace('1', 'a$$c')");
459         testEvaluate("abaabe", "'abcde'.replace('cd', 'a$`')");
460         testEvaluate("a21", "'121'.replace('1', 'a$`')");
461         testEvaluate("abaee", "'abcde'.replace('cd', \"a$'\")");
462         testEvaluate("aba", "'abcd'.replace('cd', \"a$'\")");
463         testEvaluate("aba$0", "'abcd'.replace('cd', 'a$0')");
464         testEvaluate("aba$1", "'abcd'.replace('cd', 'a$1')");
465         testEvaluate("abCD", "'abcd'.replace('cd', function (matched) { return matched.toUpperCase() })");
466         testEvaluate("", "'123456'.replace(/\\d+/, '')");
467         testEvaluate("123ABCD321abcd",
468                 "'123abcd321abcd'.replace(/[a-z]+/, function (matched) { return matched.toUpperCase() })");
469     }
470 
471     /**
472      * @throws Exception if an error occurs
473      */
474     @Test
475     public void stringReplaceAll() throws Exception {
476         testEvaluate("xyz", "''.replaceAll('', 'xyz')");
477         testEvaluate("1", "'12121'.replaceAll('21', '')");
478         testEvaluate("xyz1xyz2xyz1xyz", "'121'.replaceAll('', 'xyz')");
479         testEvaluate("a$c2a$c", "'121'.replaceAll('1', 'a$c')");
480         testEvaluate("a12a1", "'121'.replaceAll('1', 'a$&')");
481         testEvaluate("a$c2a$c", "'121'.replaceAll('1', 'a$$c')");
482         testEvaluate("aaadaaabcda", "'abcdabc'.replaceAll('bc', 'a$`')");
483         testEvaluate("a2a12", "'121'.replaceAll('1', 'a$`')");
484         testEvaluate("aadabcdaa", "'abcdabc'.replaceAll('bc', \"a$'\")");
485         testEvaluate("aadabcdaa", "'abcdabc'.replaceAll('bc', \"a$'\")");
486         testEvaluate("aa$0daa$0", "'abcdabc'.replaceAll('bc', 'a$0')");
487         testEvaluate("aa$1daa$1", "'abcdabc'.replaceAll('bc', 'a$1')");
488         testEvaluate("", "'123456'.replaceAll(/\\d+/g, '')");
489         testEvaluate("123456", "'123456'.replaceAll(undefined, '')");
490         testEvaluate("afoobarb", "'afoob'.replaceAll(/(foo)/g, '$1bar')");
491         testEvaluate("foobarb", "'foob'.replaceAll(/(foo)/gy, '$1bar')");
492         testEvaluate("hllo", "'hello'.replaceAll(/(h)e/gy, '$1')");
493         testEvaluate("$1llo", "'hello'.replaceAll(/he/g, '$1')");
494         testEvaluate("I$want$these$periods$to$be$$s", "'I.want.these.periods.to.be.$s'.replaceAll(/\\./g, '$')");
495         testEvaluate("food bar", "'foo bar'.replaceAll(/foo/g, '$&d')");
496         testEvaluate("foo foo ", "'foo bar'.replaceAll(/bar/g, '$`')");
497         testEvaluate(" bar bar", "'foo bar'.replaceAll(/foo/g, '$\\'')");
498         testEvaluate("$' bar", "'foo bar'.replaceAll(/foo/g, '$$\\'')");
499         testEvaluate("123FOOBAR321BARFOO123",
500                 "'123foobar321barfoo123'.replace(/[a-z]+/g, function (matched) { return matched.toUpperCase() })");
501 
502         testEvaluate(
503                 "TypeError: replaceAll must be called with a global RegExp",
504                 "'hello'.replaceAll(/he/i, 'x')");
505     }
506 
507     /**
508      * @throws Exception if an error occurs
509      */
510     @Test
511     @Alerts({"ad$0db", "ad$0db", "ad$0dbd$0dc"})
512     public void stringReplaceAllBrowserSpecific() throws Exception {
513         testEvaluate(getExpectedAlerts()[0], "'afoob'.replaceAll(/(foo)/g, 'd$0d')");
514         testEvaluate(getExpectedAlerts()[1], "'afkxxxkob'.replace(/(f)k(.*)k(o)/g, 'd$0d')");
515         testEvaluate(getExpectedAlerts()[2], "'afoobfuoc'.replaceAll(/(f.o)/g, 'd$0d')");
516     }
517 
518     private void testEvaluate(final String expected, final String script) throws Exception {
519         final String html =
520                 "<html>\n"
521                 + "<head>\n"
522                 + "<script>\n"
523                 + LOG_TEXTAREA_FUNCTION
524                 + "function test() {\n"
525                 + "  try {\n"
526                 + "    log(" + script + ");\n"
527                 + "  } catch(e) { log(e); }\n"
528                 + "}\n"
529                 + "</script>\n"
530                 + "</head>\n"
531                 + "<body onload='test()'>\n"
532                 + LOG_TEXTAREA
533                 + "</body></html>";
534 
535         final WebDriver driver = loadPage2(html);
536         verifyTextArea2(driver, expected);
537     }
538 
539     private void testEvaluate(final String script) throws Exception {
540         final String html =
541                 "<html>\n"
542                 + "<head>\n"
543                 + "<script>\n"
544                 + LOG_TEXTAREA_FUNCTION
545                 + "function test() {\n"
546                 + script
547                 + "}\n"
548                 + "</script>\n"
549                 + "</head>\n"
550                 + "<body onload='test()'>\n"
551                 + LOG_TEXTAREA
552                 + "</body></html>";
553 
554         loadPageVerifyTextArea2(html);
555     }
556 }