riched20: Fix crash with NULL lParam in EM_SETTEXTEX.
[wine] / dlls / riched20 / tests / editor.c
1 /*
2 * Unit test suite for rich edit control
3 *
4 * Copyright 2006 Google (Thomas Kho)
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #include <wine/test.h>
22 #include <windows.h>
23 #include <richedit.h>
24 #include <time.h>
25
26 static HMODULE hmoduleRichEdit;
27
28 static HWND new_window(LPCTSTR lpClassName, DWORD dwStyle, HWND parent) {
29   HWND hwnd;
30   hwnd = CreateWindow(lpClassName, NULL, dwStyle|WS_POPUP|WS_HSCROLL|WS_VSCROLL
31                       |WS_VISIBLE, 0, 0, 200, 60, parent, NULL,
32                       hmoduleRichEdit, NULL);
33   ok(hwnd != NULL, "class: %s, error: %d\n", lpClassName, (int) GetLastError());
34   return hwnd;
35 }
36
37 static HWND new_richedit(HWND parent) {
38   return new_window(RICHEDIT_CLASS, ES_MULTILINE, parent);
39 }
40
41 static const char haystack[] = "WINEWine wineWine wine WineWine";
42                              /* ^0        ^10       ^20       ^30 */
43
44 struct find_s {
45   int start;
46   int end;
47   const char *needle;
48   int flags;
49   int expected_loc;
50   int _todo_wine;
51 };
52
53 struct find_s find_tests[] = {
54   /* Find in empty text */
55   {0, -1, "foo", FR_DOWN, -1, 0},
56   {0, -1, "foo", 0, -1, 0},
57   {0, -1, "", FR_DOWN, -1, 0},
58   {20, 5, "foo", FR_DOWN, -1, 0},
59   {5, 20, "foo", FR_DOWN, -1, 0}
60 };
61
62 struct find_s find_tests2[] = {
63   /* No-result find */
64   {0, -1, "foo", FR_DOWN | FR_MATCHCASE, -1, 0},
65   {5, 20, "WINE", FR_DOWN | FR_MATCHCASE, -1, 0},
66
67   /* Subsequent finds */
68   {0, -1, "Wine", FR_DOWN | FR_MATCHCASE, 4, 0},
69   {5, 31, "Wine", FR_DOWN | FR_MATCHCASE, 13, 0},
70   {14, 31, "Wine", FR_DOWN | FR_MATCHCASE, 23, 0},
71   {24, 31, "Wine", FR_DOWN | FR_MATCHCASE, 27, 0},
72
73   /* Find backwards */
74   {19, 20, "Wine", FR_MATCHCASE, 13, 0},
75   {10, 20, "Wine", FR_MATCHCASE, 4, 0},
76   {20, 10, "Wine", FR_MATCHCASE, 13, 0},
77
78   /* Case-insensitive */
79   {1, 31, "wInE", FR_DOWN, 4, 0},
80   {1, 31, "Wine", FR_DOWN, 4, 0},
81
82   /* High-to-low ranges */
83   {20, 5, "Wine", FR_DOWN, -1, 0},
84   {2, 1, "Wine", FR_DOWN, -1, 0},
85   {30, 29, "Wine", FR_DOWN, -1, 0},
86   {20, 5, "Wine", 0, 13, 0},
87
88   /* Find nothing */
89   {5, 10, "", FR_DOWN, -1, 0},
90   {10, 5, "", FR_DOWN, -1, 0},
91   {0, -1, "", FR_DOWN, -1, 0},
92   {10, 5, "", 0, -1, 0},
93
94   /* Whole-word search */
95   {0, -1, "wine", FR_DOWN | FR_WHOLEWORD, 18, 0},
96   {0, -1, "win", FR_DOWN | FR_WHOLEWORD, -1, 0},
97   {13, -1, "wine", FR_DOWN | FR_WHOLEWORD, 18, 0},
98   {0, -1, "winewine", FR_DOWN | FR_WHOLEWORD, 0, 0},
99   {10, -1, "winewine", FR_DOWN | FR_WHOLEWORD, 23, 0},
100   {11, -1, "winewine", FR_WHOLEWORD, 0, 0},
101   {31, -1, "winewine", FR_WHOLEWORD, 23, 0},
102   
103   /* Bad ranges */
104   {5, 200, "XXX", FR_DOWN, -1, 0},
105   {-20, 20, "Wine", FR_DOWN, -1, 0},
106   {-20, 20, "Wine", FR_DOWN, -1, 0},
107   {-15, -20, "Wine", FR_DOWN, -1, 0},
108   {1<<12, 1<<13, "Wine", FR_DOWN, -1, 0},
109
110   /* Check the case noted in bug 4479 where matches at end aren't recognized */
111   {23, 31, "Wine", FR_DOWN | FR_MATCHCASE, 23, 0},
112   {27, 31, "Wine", FR_DOWN | FR_MATCHCASE, 27, 0},
113   {27, 32, "Wine", FR_DOWN | FR_MATCHCASE, 27, 0},
114   {13, 31, "WineWine", FR_DOWN | FR_MATCHCASE, 23, 0},
115   {13, 32, "WineWine", FR_DOWN | FR_MATCHCASE, 23, 0},
116
117   /* The backwards case of bug 4479; bounds look right
118    * Fails because backward find is wrong */
119   {19, 20, "WINE", FR_MATCHCASE, 0, 0},
120   {0, 20, "WINE", FR_MATCHCASE, -1, 0}
121 };
122
123 static void check_EM_FINDTEXT(HWND hwnd, const char *name, struct find_s *f, int id) {
124   int findloc;
125   FINDTEXT ft;
126   memset(&ft, 0, sizeof(ft));
127   ft.chrg.cpMin = f->start;
128   ft.chrg.cpMax = f->end;
129   ft.lpstrText = f->needle;
130   findloc = SendMessage(hwnd, EM_FINDTEXT, f->flags, (LPARAM) &ft);
131   ok(findloc == f->expected_loc,
132      "EM_FINDTEXT(%s,%d) '%s' in range(%d,%d), flags %08x, got start at %d\n",
133      name, id, f->needle, f->start, f->end, f->flags, findloc);
134 }
135
136 static void check_EM_FINDTEXTEX(HWND hwnd, const char *name, struct find_s *f,
137     int id) {
138   int findloc;
139   FINDTEXTEX ft;
140   memset(&ft, 0, sizeof(ft));
141   ft.chrg.cpMin = f->start;
142   ft.chrg.cpMax = f->end;
143   ft.lpstrText = f->needle;
144   findloc = SendMessage(hwnd, EM_FINDTEXTEX, f->flags, (LPARAM) &ft);
145   ok(findloc == f->expected_loc,
146       "EM_FINDTEXTEX(%s,%d) '%s' in range(%d,%d), flags %08x, start at %d\n",
147       name, id, f->needle, f->start, f->end, f->flags, findloc);
148   ok(ft.chrgText.cpMin == f->expected_loc,
149       "EM_FINDTEXTEX(%s,%d) '%s' in range(%d,%d), flags %08x, start at %ld\n",
150       name, id, f->needle, f->start, f->end, f->flags, ft.chrgText.cpMin);
151   ok(ft.chrgText.cpMax == ((f->expected_loc == -1) ? -1
152         : f->expected_loc + strlen(f->needle)),
153       "EM_FINDTEXTEX(%s,%d) '%s' in range(%d,%d), flags %08x, end at %ld\n",
154       name, id, f->needle, f->start, f->end, f->flags, ft.chrgText.cpMax);
155 }
156
157 static void run_tests_EM_FINDTEXT(HWND hwnd, const char *name, struct find_s *find,
158     int num_tests)
159 {
160   int i;
161
162   for (i = 0; i < num_tests; i++) {
163     if (find[i]._todo_wine) {
164       todo_wine {
165         check_EM_FINDTEXT(hwnd, name, &find[i], i);
166         check_EM_FINDTEXTEX(hwnd, name, &find[i], i);
167       }
168     } else {
169         check_EM_FINDTEXT(hwnd, name, &find[i], i);
170         check_EM_FINDTEXTEX(hwnd, name, &find[i], i);
171     }
172   }
173 }
174
175 static void test_EM_FINDTEXT(void)
176 {
177   HWND hwndRichEdit = new_richedit(NULL);
178
179   /* Empty rich edit control */
180   run_tests_EM_FINDTEXT(hwndRichEdit, "1", find_tests,
181       sizeof(find_tests)/sizeof(struct find_s));
182
183   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) haystack);
184
185   /* Haystack text */
186   run_tests_EM_FINDTEXT(hwndRichEdit, "2", find_tests2,
187       sizeof(find_tests2)/sizeof(struct find_s));
188
189   DestroyWindow(hwndRichEdit);
190 }
191
192 static const struct getline_s {
193   int line;
194   size_t buffer_len;
195   const char *text;
196 } gl[] = {
197   {0, 10, "foo bar\r"},
198   {1, 10, "\r"},
199   {2, 10, "bar\r"},
200   {3, 10, "\r"},
201
202   /* Buffer smaller than line length */
203   {0, 2, "foo bar\r"},
204   {0, 1, "foo bar\r"},
205   {0, 0, "foo bar\r"}
206 };
207
208 static void test_EM_GETLINE(void)
209 {
210   int i;
211   HWND hwndRichEdit = new_richedit(NULL);
212   static const int nBuf = 1024;
213   char dest[1024], origdest[1024];
214   const char text[] = "foo bar\n"
215       "\n"
216       "bar\n";
217
218   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
219
220   memset(origdest, 0xBB, nBuf);
221   for (i = 0; i < sizeof(gl)/sizeof(struct getline_s); i++)
222   {
223     int nCopied;
224     int expected_nCopied = min(gl[i].buffer_len, strlen(gl[i].text));
225     int expected_bytes_written = min(gl[i].buffer_len, strlen(gl[i].text) + 1);
226     memset(dest, 0xBB, nBuf);
227     *(WORD *) dest = gl[i].buffer_len;
228
229     /* EM_GETLINE appends a "\r\0" to the end of the line
230      * nCopied counts up to and including the '\r' */
231     nCopied = SendMessage(hwndRichEdit, EM_GETLINE, gl[i].line, (LPARAM) dest);
232     ok(nCopied == expected_nCopied, "%d: %d!=%d\n", i, nCopied,
233        expected_nCopied);
234     /* two special cases since a parameter is passed via dest */
235     if (gl[i].buffer_len == 0)
236       ok(!dest[0] && !dest[1] && !strncmp(dest+2, origdest+2, nBuf-2),
237          "buffer_len=0\n");
238     else if (gl[i].buffer_len == 1)
239       ok(dest[0] == gl[i].text[0] && !dest[1] &&
240          !strncmp(dest+2, origdest+2, nBuf-2), "buffer_len=1\n");
241     else
242     {
243       ok(!strncmp(dest, gl[i].text, expected_bytes_written),
244          "%d: expected_bytes_written=%d\n", i, expected_bytes_written);
245       ok(!strncmp(dest + expected_bytes_written, origdest
246                   + expected_bytes_written, nBuf - expected_bytes_written),
247          "%d: expected_bytes_written=%d\n", i, expected_bytes_written);
248     }
249   }
250
251   DestroyWindow(hwndRichEdit);
252 }
253
254 static int get_scroll_pos_y(HWND hwnd)
255 {
256   POINT p = {-1, -1};
257   SendMessage(hwnd, EM_GETSCROLLPOS, 0, (LPARAM) &p);
258   ok(p.x != -1 && p.y != -1, "p.x:%ld p.y:%ld\n", p.x, p.y);
259   return p.y;
260 }
261
262 static void move_cursor(HWND hwnd, long charindex)
263 {
264   CHARRANGE cr;
265   cr.cpMax = charindex;
266   cr.cpMin = charindex;
267   SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM) &cr);
268 }
269
270 static void line_scroll(HWND hwnd, int amount)
271 {
272   SendMessage(hwnd, EM_LINESCROLL, 0, amount);
273 }
274
275 static void test_EM_SCROLLCARET(void)
276 {
277   int prevY, curY;
278   HWND hwndRichEdit = new_richedit(NULL);
279   const char text[] = "aa\n"
280       "this is a long line of text that should be longer than the "
281       "control's width\n"
282       "cc\n"
283       "dd\n"
284       "ee\n"
285       "ff\n"
286       "gg\n"
287       "hh\n";
288
289   /* Can't verify this */
290   SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0);
291
292   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
293
294   /* Caret above visible window */
295   line_scroll(hwndRichEdit, 3);
296   prevY = get_scroll_pos_y(hwndRichEdit);
297   SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0);
298   curY = get_scroll_pos_y(hwndRichEdit);
299   ok(prevY != curY, "%d == %d\n", prevY, curY);
300
301   /* Caret below visible window */
302   move_cursor(hwndRichEdit, sizeof(text) - 1);
303   line_scroll(hwndRichEdit, -3);
304   prevY = get_scroll_pos_y(hwndRichEdit);
305   SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0);
306   curY = get_scroll_pos_y(hwndRichEdit);
307   ok(prevY != curY, "%d == %d\n", prevY, curY);
308
309   /* Caret in visible window */
310   move_cursor(hwndRichEdit, sizeof(text) - 2);
311   prevY = get_scroll_pos_y(hwndRichEdit);
312   SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0);
313   curY = get_scroll_pos_y(hwndRichEdit);
314   ok(prevY == curY, "%d != %d\n", prevY, curY);
315
316   /* Caret still in visible window */
317   line_scroll(hwndRichEdit, -1);
318   prevY = get_scroll_pos_y(hwndRichEdit);
319   SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0);
320   curY = get_scroll_pos_y(hwndRichEdit);
321   ok(prevY == curY, "%d != %d\n", prevY, curY);
322
323   DestroyWindow(hwndRichEdit);
324 }
325
326 static void test_EM_SETTEXTMODE(void)
327 {
328   HWND hwndRichEdit = new_richedit(NULL);
329   CHARFORMAT2 cf2, cf2test;
330   CHARRANGE cr;
331   int rc = 0;
332
333   /*Test that EM_SETTEXTMODE fails if text exists within the control*/
334   /*Insert text into the control*/
335
336   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
337
338   /*Attempt to change the control to plain text mode*/
339   rc = SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_PLAINTEXT, 0);
340   ok(rc != 0, "EM_SETTEXTMODE: changed text mode in control containing text - returned: %d\n", rc);
341
342   /*Test that EM_SETTEXTMODE does not allow rich edit text to be pasted.
343   If rich text is pasted, it should have the same formatting as the rest
344   of the text in the control*/
345
346   /*Italicize the text
347   *NOTE: If the default text was already italicized, the test will simply
348   reverse; in other words, it will copy a regular "wine" into a plain
349   text window that uses an italicized format*/
350   cf2.cbSize = sizeof(CHARFORMAT2);
351   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_DEFAULT,
352              (LPARAM) &cf2);
353
354   cf2.dwMask = CFM_ITALIC | cf2.dwMask;
355   cf2.dwEffects = CFE_ITALIC ^ cf2.dwEffects;
356
357   /*EM_SETCHARFORMAT is not yet fully implemented for all WPARAMs in wine;
358   however, SCF_ALL has been implemented*/
359   SendMessage(hwndRichEdit, EM_SETCHARFORMAT, (WPARAM) SCF_ALL, (LPARAM) &cf2);
360   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
361
362   /*Select the string "wine"*/
363   cr.cpMin = 0;
364   cr.cpMax = 4;
365   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
366
367   /*Copy the italicized "wine" to the clipboard*/
368   SendMessage(hwndRichEdit, WM_COPY, 0, 0);
369
370   /*Reset the formatting to default*/
371   cf2.dwEffects = CFE_ITALIC^cf2.dwEffects;
372   SendMessage(hwndRichEdit, EM_SETCHARFORMAT, (WPARAM) SCF_ALL, (LPARAM) &cf2);
373
374   /*Clear the text in the control*/
375   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
376
377   /*Switch to Plain Text Mode*/
378   rc = SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_PLAINTEXT, 0);
379   ok(rc == 0, "EM_SETTEXTMODE: unable to switch to plain text mode with empty control:  returned: %d\n", rc);
380
381   /*Input "wine" again in normal format*/
382   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
383
384   /*Paste the italicized "wine" into the control*/
385   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
386
387   /*Select a character from the first "wine" string*/
388   cr.cpMin = 2;
389   cr.cpMax = 3;
390   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
391
392   /*Retrieve its formatting*/
393   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION,
394               (LPARAM) &cf2);
395
396   /*Select a character from the second "wine" string*/
397   cr.cpMin = 5;
398   cr.cpMax = 6;
399   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
400
401   /*Retrieve its formatting*/
402   cf2test.cbSize = sizeof(CHARFORMAT2);
403   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION,
404                (LPARAM) &cf2test);
405
406   /*Compare the two formattings*/
407     ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects == cf2test.dwEffects),
408       "two formats found in plain text mode - cf2.dwEffects: %lx cf2test.dwEffects: %lx\n",
409        cf2.dwEffects, cf2test.dwEffects);
410   /*Test TM_RICHTEXT by: switching back to Rich Text mode
411                          printing "wine" in the current format(normal)
412                          pasting "wine" from the clipboard(italicized)
413                          comparing the two formats(should differ)*/
414
415   /*Attempt to switch with text in control*/
416   rc = SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_RICHTEXT, 0);
417   ok(rc != 0, "EM_SETTEXTMODE: changed from plain text to rich text with text in control - returned: %d\n", rc);
418
419   /*Clear control*/
420   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
421
422   /*Switch into Rich Text mode*/
423   rc = SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_RICHTEXT, 0);
424   ok(rc == 0, "EM_SETTEXTMODE: unable to change to rich text with empty control - returned: %d\n", rc);
425
426   /*Print "wine" in normal formatting into the control*/
427   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
428
429   /*Paste italicized "wine" into the control*/
430   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
431
432   /*Select text from the first "wine" string*/
433   cr.cpMin = 1;
434   cr.cpMax = 3;
435   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
436
437   /*Retrieve its formatting*/
438   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION,
439                 (LPARAM) &cf2);
440
441   /*Select text from the second "wine" string*/
442   cr.cpMin = 6;
443   cr.cpMax = 7;
444   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
445
446   /*Retrieve its formatting*/
447   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION,
448                 (LPARAM) &cf2test);
449
450   /*Test that the two formattings are not the same*/
451   todo_wine ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects != cf2test.dwEffects),
452       "expected different formats - cf2.dwMask: %lx, cf2test.dwMask: %lx, cf2.dwEffects: %lx, cf2test.dwEffects: %lx\n",
453       cf2.dwMask, cf2test.dwMask, cf2.dwEffects, cf2test.dwEffects);
454
455   DestroyWindow(hwndRichEdit);
456 }
457
458 static void test_TM_PLAINTEXT()
459 {
460   /*Tests plain text properties*/
461
462   HWND hwndRichEdit = new_richedit(NULL);
463   CHARFORMAT2 cf2, cf2test;
464   CHARRANGE cr;
465
466   /*Switch to plain text mode*/
467
468   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
469   SendMessage(hwndRichEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0);
470
471   /*Fill control with text*/
472
473   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "Is Wine an emulator? No it's not");
474
475   /*Select some text and bold it*/
476
477   cr.cpMin = 10;
478   cr.cpMax = 20;
479   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
480   cf2.cbSize = sizeof(CHARFORMAT2);
481   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_DEFAULT,
482               (LPARAM) &cf2);
483
484   cf2.dwMask = CFM_BOLD | cf2.dwMask;
485   cf2.dwEffects = CFE_BOLD ^ cf2.dwEffects;
486
487   SendMessage(hwndRichEdit, EM_SETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2);
488
489   /*Get the formatting of those characters*/
490
491   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2);
492
493   /*Get the formatting of some other characters*/
494   cf2test.cbSize = sizeof(CHARFORMAT2);
495   cr.cpMin = 21;
496   cr.cpMax = 30;
497   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
498   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2test);
499
500   /*Test that they are the same as plain text allows only one formatting*/
501
502   ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects == cf2test.dwEffects),
503      "two selections' formats differ - cf2.dwMask: %lx, cf2test.dwMask %lx, cf2.dwEffects: %lx, cf2test.dwEffects: %lx\n",
504      cf2.dwMask, cf2test.dwMask, cf2.dwEffects, cf2test.dwEffects);
505   
506   /*Fill the control with a "wine" string, which when inserted will be bold*/
507
508   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
509
510   /*Copy the bolded "wine" string*/
511
512   cr.cpMin = 0;
513   cr.cpMax = 4;
514   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
515   SendMessage(hwndRichEdit, WM_COPY, 0, 0);
516
517   /*Swap back to rich text*/
518
519   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
520   SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_RICHTEXT, 0);
521
522   /*Set the default formatting to bold italics*/
523
524   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_DEFAULT, (LPARAM) &cf2);
525   cf2.dwMask |= CFM_ITALIC;
526   cf2.dwEffects ^= CFE_ITALIC;
527   SendMessage(hwndRichEdit, EM_SETCHARFORMAT, (WPARAM) SCF_ALL, (LPARAM) &cf2);
528
529   /*Set the text in the control to "wine", which will be bold and italicized*/
530
531   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
532
533   /*Paste the plain text "wine" string, which should take the insert
534    formatting, which at the moment is bold italics*/
535
536   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
537
538   /*Select the first "wine" string and retrieve its formatting*/
539
540   cr.cpMin = 1;
541   cr.cpMax = 3;
542   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
543   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2);
544
545   /*Select the second "wine" string and retrieve its formatting*/
546
547   cr.cpMin = 5;
548   cr.cpMax = 7;
549   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
550   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2test);
551
552   /*Compare the two formattings. They should be the same.*/
553
554   ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects == cf2test.dwEffects),
555      "Copied text retained formatting - cf2.dwMask: %lx, cf2test.dwMask: %lx, cf2.dwEffects: %lx, cf2test.dwEffects: %lx\n",
556      cf2.dwMask, cf2test.dwMask, cf2.dwEffects, cf2test.dwEffects);
557   DestroyWindow(hwndRichEdit);
558 }
559
560 static void test_WM_GETTEXT()
561 {
562     HWND hwndRichEdit = new_richedit(NULL);
563     static const char text[] = "Hello. My name is RichEdit!";
564     char buffer[1024] = {0};
565     int result;
566
567     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
568     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
569     result = strcmp(buffer,text);
570     ok(result == 0, 
571         "WM_GETTEXT: settext and gettext differ. strcmp: %d\n", result);
572 }
573
574 /* FIXME: need to test unimplemented options and robustly test wparam */
575 static void test_EM_SETOPTIONS()
576 {
577     HWND hwndRichEdit = new_richedit(NULL);
578     static const char text[] = "Hello. My name is RichEdit!";
579     char buffer[1024] = {0};
580
581     /* NEGATIVE TESTING - NO OPTIONS SET */
582     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
583     SendMessage(hwndRichEdit, EM_SETOPTIONS, ECOOP_SET, 0);
584
585     /* testing no readonly by sending 'a' to the control*/
586     SetFocus(hwndRichEdit);
587     SendMessage(hwndRichEdit, WM_CHAR, 'a', 0x1E0001);
588     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
589     ok(buffer[0]=='a', 
590        "EM_SETOPTIONS: Text not changed! s1:%s s2:%s\n", text, buffer);
591     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
592
593     /* READONLY - sending 'a' to the control */
594     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
595     SendMessage(hwndRichEdit, EM_SETOPTIONS, ECOOP_SET, ECO_READONLY);
596     SetFocus(hwndRichEdit);
597     SendMessage(hwndRichEdit, WM_CHAR, 'a', 0x1E0001);
598     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
599     ok(buffer[0]==text[0], 
600        "EM_SETOPTIONS: Text changed! s1:%s s2:%s\n", text, buffer); 
601
602     DestroyWindow(hwndRichEdit);
603 }
604
605 static void check_CFE_LINK_rcvd(HWND hwnd, int is_url)
606 {
607   CHARFORMAT2W text_format;
608   int link_present = 0;
609   text_format.cbSize = sizeof(text_format);
610   SendMessage(hwnd, EM_SETSEL, 0, 0);
611   SendMessage(hwnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &text_format);
612   link_present = text_format.dwEffects & CFE_LINK;
613   if (is_url) 
614   { /* control text is url; should get CFE_LINK */
615         ok(0 != link_present, "URL Case: CFE_LINK not set.\n");
616   }
617   else 
618   {
619     ok(0 == link_present, "Non-URL Case: CFE_LINK set.\n");
620   }
621 }
622
623 static HWND new_static_wnd(HWND parent) {
624   return new_window("Static", 0, parent);
625 }
626
627 static void test_EM_AUTOURLDETECT(void)
628 {
629   struct urls_s {
630     const char *text;
631     int is_url;
632   } urls[12] = {
633     {"winehq.org", 0},
634     {"http://www.winehq.org", 1},
635     {"http//winehq.org", 0},
636     {"ww.winehq.org", 0},
637     {"www.winehq.org", 1},
638     {"ftp://192.168.1.1", 1},
639     {"ftp//192.168.1.1", 0},
640     {"mailto:your@email.com", 1},    
641     {"prospero:prosperoserver", 1},
642     {"telnet:test", 1},
643     {"news:newserver", 1},
644     {"wais:waisserver", 1}  
645   };
646
647   int i;
648   int urlRet=-1;
649   HWND hwndRichEdit, parent;
650
651   parent = new_static_wnd(NULL);
652   hwndRichEdit = new_richedit(parent);
653   /* Try and pass EM_AUTOURLDETECT some test wParam values */
654   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0);
655   ok(urlRet==0, "Good wParam: urlRet is: %d\n", urlRet);
656   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, 1, 0);
657   ok(urlRet==0, "Good wParam2: urlRet is: %d\n", urlRet);
658   /* Windows returns -2147024809 (0x80070057) on bad wParam values */
659   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, 8, 0);
660   ok(urlRet==E_INVALIDARG, "Bad wParam: urlRet is: %d\n", urlRet);
661   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, (WPARAM)"h", (LPARAM)"h");
662   ok(urlRet==E_INVALIDARG, "Bad wParam2: urlRet is: %d\n", urlRet);
663   /* for each url, check the text to see if CFE_LINK effect is present */
664   for (i = 0; i < sizeof(urls)/sizeof(struct urls_s); i++) {
665     SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0);
666     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
667     SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
668     check_CFE_LINK_rcvd(hwndRichEdit, 0);
669     SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
670     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
671     SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
672     check_CFE_LINK_rcvd(hwndRichEdit, urls[i].is_url);
673   }
674   DestroyWindow(hwndRichEdit);
675   DestroyWindow(parent);
676 }
677
678 static void test_EM_SCROLL()
679 {
680   int i, j;
681   int r; /* return value */
682   int expr; /* expected return value */
683   HWND hwndRichEdit = new_richedit(NULL);
684   int y_before, y_after; /* units of lines of text */
685
686   /* test a richedit box containing a single line of text */
687   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "a");/* one line of text */
688   expr = 0x00010000;
689   for (i = 0; i < 4; i++) {
690     static const int cmd[4] = { SB_PAGEDOWN, SB_PAGEUP, SB_LINEDOWN, SB_LINEUP };
691
692     r = SendMessage(hwndRichEdit, EM_SCROLL, cmd[i], 0);
693     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
694     ok(expr == r, "EM_SCROLL improper return value returned (i == %d). "
695        "Got 0x%08x, expected 0x%08x\n", i, r, expr);
696     ok(y_after == 0, "EM_SCROLL improper scroll. scrolled to line %d, not 1 "
697        "(i == %d)\n", y_after, i);
698   }
699
700   /*
701    * test a richedit box that will scroll. There are two general
702    * cases: the case without any long lines and the case with a long
703    * line.
704    */
705   for (i = 0; i < 2; i++) { /* iterate through different bodies of text */
706     if (i == 0)
707       SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "a\nb\nc\nd\ne");
708     else
709       SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM)
710                   "a LONG LINE LONG LINE LONG LINE LONG LINE LONG LINE "
711                   "LONG LINE LONG LINE LONG LINE LONG LINE LONG LINE "
712                   "LONG LINE \nb\nc\nd\ne");
713     for (j = 0; j < 12; j++) /* reset scrol position to top */
714       SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEUP, 0);
715
716     /* get first visible line */
717     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
718     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEDOWN, 0); /* page down */
719
720     /* get new current first visible line */
721     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
722
723     ok(((r & 0xffffff00) == 0x00010000) &&
724        ((r & 0x000000ff) != 0x00000000),
725        "EM_SCROLL page down didn't scroll by a small positive number of "
726        "lines (r == 0x%08x)\n", r);
727     ok(y_after > y_before, "EM_SCROLL page down not functioning "
728        "(line %d scrolled to line %d\n", y_before, y_after);
729
730     y_before = y_after;
731     
732     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEUP, 0); /* page up */
733     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
734     ok(((r & 0xffffff00) == 0x0001ff00),
735        "EM_SCROLL page up didn't scroll by a small negative number of lines "
736        "(r == 0x%08x)\n", r);
737     ok(y_after < y_before, "EM_SCROLL page up not functioning (line "
738        "%d scrolled to line %d\n", y_before, y_after);
739     
740     y_before = y_after;
741
742     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_LINEDOWN, 0); /* line down */
743
744     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
745
746     ok(r == 0x00010001, "EM_SCROLL line down didn't scroll by one line "
747        "(r == 0x%08x)\n", r);
748     ok(y_after -1 == y_before, "EM_SCROLL line down didn't go down by "
749        "1 line (%d scrolled to %d)\n", y_before, y_after);
750
751     y_before = y_after;
752
753     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_LINEUP, 0); /* line up */
754
755     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
756
757     ok(r == 0x0001ffff, "EM_SCROLL line up didn't scroll by one line "
758        "(r == 0x%08x)\n", r);
759     ok(y_after +1 == y_before, "EM_SCROLL line up didn't go up by 1 "
760        "line (%d scrolled to %d)\n", y_before, y_after);
761
762     y_before = y_after;
763
764     r = SendMessage(hwndRichEdit, EM_SCROLL,
765                     SB_LINEUP, 0); /* lineup beyond top */
766
767     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
768
769     ok(r == 0x00010000,
770        "EM_SCROLL line up returned indicating movement (0x%08x)\n", r);
771     ok(y_before == y_after,
772        "EM_SCROLL line up beyond top worked (%d)\n", y_after);
773
774     y_before = y_after;
775
776     r = SendMessage(hwndRichEdit, EM_SCROLL,
777                     SB_PAGEUP, 0);/*page up beyond top */
778
779     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
780
781     ok(r == 0x00010000,
782        "EM_SCROLL page up returned indicating movement (0x%08x)\n", r);
783     ok(y_before == y_after,
784        "EM_SCROLL page up beyond top worked (%d)\n", y_after);
785
786     for (j = 0; j < 12; j++) /* page down all the way to the bottom */
787       SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEDOWN, 0);
788     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
789     r = SendMessage(hwndRichEdit, EM_SCROLL,
790                     SB_PAGEDOWN, 0); /* page down beyond bot */
791     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
792
793     ok(r == 0x00010000,
794        "EM_SCROLL page down returned indicating movement (0x%08x)\n", r);
795     ok(y_before == y_after,
796        "EM_SCROLL page down beyond bottom worked (%d -> %d)\n",
797        y_before, y_after);
798
799     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
800     SendMessage(hwndRichEdit, EM_SCROLL,
801                 SB_LINEDOWN, 0); /* line down beyond bot */
802     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
803     
804     ok(r == 0x00010000,
805        "EM_SCROLL line down returned indicating movement (0x%08x)\n", r);
806     ok(y_before == y_after,
807        "EM_SCROLL line down beyond bottom worked (%d -> %d)\n",
808        y_before, y_after);
809   }
810   DestroyWindow(hwndRichEdit);
811 }
812
813 static void test_EM_SETUNDOLIMIT()
814 {
815   /* cases we test for:
816    * default behaviour - limiting at 100 undo's 
817    * undo disabled - setting a limit of 0
818    * undo limited -  undo limit set to some to some number, like 2
819    * bad input - sending a negative number should default to 100 undo's */
820  
821   HWND hwndRichEdit = new_richedit(NULL);
822   CHARRANGE cr;
823   int i;
824   int result;
825   
826   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "x");
827   cr.cpMin = 0;
828   cr.cpMax = 1;
829   SendMessage(hwndRichEdit, WM_COPY, 0, 0);
830     /*Load "x" into the clipboard. Paste is an easy, undo'able operation.
831       also, multiple pastes don't combine like WM_CHAR would */
832   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
833
834   /* first case - check the default */
835   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0,0); 
836   for (i=0; i<101; i++) /* Put 101 undo's on the stack */
837     SendMessage(hwndRichEdit, WM_PASTE, 0, 0); 
838   for (i=0; i<100; i++) /* Undo 100 of them */
839     SendMessage(hwndRichEdit, WM_UNDO, 0, 0); 
840   ok(!SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
841      "EM_SETUNDOLIMIT allowed more than a hundred undo's by default.\n");
842
843   /* second case - cannot undo */
844   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0, 0); 
845   SendMessage(hwndRichEdit, EM_SETUNDOLIMIT, 0, 0); 
846   SendMessage(hwndRichEdit,
847               WM_PASTE, 0, 0); /* Try to put something in the undo stack */
848   ok(!SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
849      "EM_SETUNDOLIMIT allowed undo with UNDOLIMIT set to 0\n");
850
851   /* third case - set it to an arbitrary number */
852   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0, 0); 
853   SendMessage(hwndRichEdit, EM_SETUNDOLIMIT, 2, 0); 
854   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
855   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
856   SendMessage(hwndRichEdit, WM_PASTE, 0, 0); 
857   /* If SETUNDOLIMIT is working, there should only be two undo's after this */
858   ok(SendMessage(hwndRichEdit, EM_CANUNDO, 0,0),
859      "EM_SETUNDOLIMIT didn't allow the first undo with UNDOLIMIT set to 2\n");
860   SendMessage(hwndRichEdit, WM_UNDO, 0, 0);
861   ok(SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
862      "EM_SETUNDOLIMIT didn't allow a second undo with UNDOLIMIT set to 2\n");
863   SendMessage(hwndRichEdit, WM_UNDO, 0, 0); 
864   ok(!SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
865      "EM_SETUNDOLIMIT allowed a third undo with UNDOLIMIT set to 2\n");
866   
867   /* fourth case - setting negative numbers should default to 100 undos */
868   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0,0); 
869   result = SendMessage(hwndRichEdit, EM_SETUNDOLIMIT, -1, 0);
870   ok (result == 100, 
871       "EM_SETUNDOLIMIT returned %d when set to -1, instead of 100\n",result);
872       
873   DestroyWindow(hwndRichEdit);
874 }
875
876 static void test_ES_PASSWORD()
877 {
878   /* This isn't hugely testable, so we're just going to run it through it's paces. */
879         
880   HWND hwndRichEdit = new_richedit(NULL);
881   WCHAR result;
882         
883   /* First, check the default of a regular control */
884   result = SendMessage(hwndRichEdit, EM_GETPASSWORDCHAR, 0, 0);
885   ok (result == 0,
886         "EM_GETPASSWORDCHAR returned %c by default, instead of NULL\n",result);
887   
888   /* Now, set it to something normal */
889   SendMessage(hwndRichEdit, EM_SETPASSWORDCHAR, 'x', 0);
890   result = SendMessage(hwndRichEdit, EM_GETPASSWORDCHAR, 0, 0);
891   ok (result == 120,
892         "EM_GETPASSWORDCHAR returned %c (%d) when set to 'x', instead of x (120)\n",result,result);
893         
894   /* Now, set it to something odd */    
895   SendMessage(hwndRichEdit, EM_SETPASSWORDCHAR, (WCHAR)1234, 0);
896   result = SendMessage(hwndRichEdit, EM_GETPASSWORDCHAR, 0, 0);
897   ok (result == 1234,
898         "EM_GETPASSWORDCHAR returned %c (%d) when set to 'x', instead of x (120)\n",result,result);
899 }
900
901 static void test_EM_SETTEXTEX()
902 {
903   HWND hwndRichEdit = new_richedit(NULL);
904   SETTEXTEX setText;
905   GETTEXTEX getText;
906   WCHAR TestItem1[] = {'T', 'e', 's', 't', 
907                        'S', 'o', 'm', 'e', 
908                        'T', 'e', 'x', 't', 0}; 
909 #define MAX_BUF_LEN 1024
910   WCHAR buf[MAX_BUF_LEN];
911   int result;
912   CHARRANGE cr;
913
914   setText.codepage = 1200;  /* no constant for unicode */
915   getText.codepage = 1200;  /* no constant for unicode */
916   getText.cb = MAX_BUF_LEN;
917   getText.flags = GT_DEFAULT;
918
919   setText.flags = 0;
920   SendMessage(hwndRichEdit, EM_SETTEXTEX, (WPARAM)&setText, (LPARAM) TestItem1);
921   SendMessage(hwndRichEdit, EM_GETTEXTEX, (WPARAM)&getText, (LPARAM) buf);
922   ok(lstrcmpW(buf, TestItem1) == 0,
923       "EM_GETTEXTEX results not what was set by EM_SETTEXTEX\n");
924
925   result = SendMessage(hwndRichEdit, EM_SETTEXTEX, 
926                        (WPARAM)&setText, (LPARAM) NULL);
927   SendMessage(hwndRichEdit, EM_GETTEXTEX, (WPARAM)&getText, (LPARAM) buf);
928   
929   ok (result == 1, 
930       "EM_SETTEXTEX returned %d, instead of 1\n",result);
931   ok(lstrlenW(buf) == 0,
932       "EM_SETTEXTEX with NULL lParam should clear rich edit.\n");
933   
934   /* put some text back */
935   setText.flags = 0;
936   SendMessage(hwndRichEdit, EM_SETTEXTEX, (WPARAM)&setText, (LPARAM) TestItem1);
937   /* select some text */
938   cr.cpMax = 1;
939   cr.cpMin = 3;
940   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
941   /* replace current selection */
942   setText.flags = ST_SELECTION;
943   result = SendMessage(hwndRichEdit, EM_SETTEXTEX, 
944                        (WPARAM)&setText, (LPARAM) NULL);
945   ok(result == 0,
946       "EM_SETTEXTEX with NULL lParam to replace selection"
947       " with no text should return 0. Got %i\n",
948       result);
949   
950   /* put some text back */
951   setText.flags = 0;
952   SendMessage(hwndRichEdit, EM_SETTEXTEX, (WPARAM)&setText, (LPARAM) TestItem1);
953   /* select some text */
954   cr.cpMax = 1;
955   cr.cpMin = 3;
956   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
957   /* replace current selection */
958   setText.flags = ST_SELECTION;
959   result = SendMessage(hwndRichEdit, EM_SETTEXTEX,
960                        (WPARAM)&setText, (LPARAM) TestItem1);
961   /* get text */
962   SendMessage(hwndRichEdit, EM_GETTEXTEX, (WPARAM)&getText, (LPARAM) buf);
963   ok(result == lstrlenW(TestItem1),
964       "EM_SETTEXTEX with NULL lParam to replace selection"
965       " with no text should return 0. Got %i\n",
966       result);
967   ok(lstrlenW(buf) == 22,
968       "EM_SETTEXTEX to replace selection with more text failed: %i.\n",
969       lstrlenW(buf) );
970
971   DestroyWindow(hwndRichEdit);
972 }
973
974
975 START_TEST( editor )
976 {
977   MSG msg;
978   time_t end;
979
980   /* Must explicitly LoadLibrary(). The test has no references to functions in
981    * RICHED20.DLL, so the linker doesn't actually link to it. */
982   hmoduleRichEdit = LoadLibrary("RICHED20.DLL");
983   ok(hmoduleRichEdit != NULL, "error: %d\n", (int) GetLastError());
984   test_EM_FINDTEXT();
985   test_EM_GETLINE();
986   test_EM_SCROLLCARET();
987   test_EM_SCROLL();
988   test_EM_SETTEXTMODE();
989   test_TM_PLAINTEXT();
990   test_EM_SETOPTIONS();
991   test_WM_GETTEXT();
992   test_EM_AUTOURLDETECT();
993   test_EM_SETUNDOLIMIT();
994   test_ES_PASSWORD();
995   test_EM_SETTEXTEX();
996
997   /* Set the environment variable WINETEST_RICHED20 to keep windows
998    * responsive and open for 30 seconds. This is useful for debugging.
999    *
1000    * The message pump uses PeekMessage() to empty the queue and then sleeps for
1001    * 50ms before retrying the queue. */
1002   end = time(NULL) + 30;
1003   if (getenv( "WINETEST_RICHED20" )) {
1004     while (time(NULL) < end) {
1005       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
1006         TranslateMessage(&msg);
1007         DispatchMessage(&msg);
1008       } else {
1009         Sleep(50);
1010       }
1011     }
1012   }
1013
1014   ok(FreeLibrary(hmoduleRichEdit) != 0, "error: %d\n", (int) GetLastError());
1015 }