1 /*
2  * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
3  *
4  * This library is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This library is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this library. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include "evolution-config.h"
18 
19 #include <string.h>
20 #include <stdlib.h>
21 
22 #include "e-util/e-util.h"
23 
24 #include "test-html-editor-units-utils.h"
25 
26 static guint event_processing_delay_ms = 25;
27 static gboolean in_background = FALSE;
28 static gboolean use_multiple_web_processes = FALSE;
29 static gboolean glob_keep_going = FALSE;
30 static GObject *global_web_context = NULL;
31 
32 void
test_utils_set_event_processing_delay_ms(guint value)33 test_utils_set_event_processing_delay_ms (guint value)
34 {
35 	event_processing_delay_ms = value;
36 }
37 
38 guint
test_utils_get_event_processing_delay_ms(void)39 test_utils_get_event_processing_delay_ms (void)
40 {
41 	return event_processing_delay_ms;
42 }
43 
44 void
test_utils_set_background(gboolean background)45 test_utils_set_background (gboolean background)
46 {
47 	in_background = background;
48 }
49 
50 gboolean
test_utils_get_background(void)51 test_utils_get_background (void)
52 {
53 	return in_background;
54 }
55 
56 void
test_utils_set_multiple_web_processes(gboolean multiple_web_processes)57 test_utils_set_multiple_web_processes (gboolean multiple_web_processes)
58 {
59 	use_multiple_web_processes = multiple_web_processes;
60 }
61 
62 gboolean
test_utils_get_multiple_web_processes(void)63 test_utils_get_multiple_web_processes (void)
64 {
65 	return use_multiple_web_processes;
66 }
67 
68 void
test_utils_set_keep_going(gboolean keep_going)69 test_utils_set_keep_going (gboolean keep_going)
70 {
71 	glob_keep_going = keep_going;
72 }
73 
74 gboolean
test_utils_get_keep_going(void)75 test_utils_get_keep_going (void)
76 {
77 	return glob_keep_going;
78 }
79 
80 void
test_utils_free_global_memory(void)81 test_utils_free_global_memory (void)
82 {
83 	g_clear_object (&global_web_context);
84 }
85 
86 typedef struct _GetContentData {
87 	EContentEditorContentHash *content_hash;
88 	gpointer async_data;
89 } GetContentData;
90 
91 static void
get_editor_content_hash_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)92 get_editor_content_hash_ready_cb (GObject *source_object,
93 				  GAsyncResult *result,
94 				  gpointer user_data)
95 {
96 	GetContentData *gcd = user_data;
97 	GError *error = NULL;
98 
99 	g_assert_nonnull (gcd);
100 	g_assert (E_IS_CONTENT_EDITOR (source_object));
101 
102 	gcd->content_hash = e_content_editor_get_content_finish (E_CONTENT_EDITOR (source_object), result, &error);
103 
104 	g_assert_no_error (error);
105 
106 	g_clear_error (&error);
107 
108 	test_utils_async_call_finish (gcd->async_data);
109 }
110 
111 static EContentEditorContentHash *
test_utils_get_editor_content_hash_sync(EContentEditor * cnt_editor,guint32 flags)112 test_utils_get_editor_content_hash_sync (EContentEditor *cnt_editor,
113 					 guint32 flags)
114 {
115 	GetContentData gcd;
116 
117 	g_assert (E_IS_CONTENT_EDITOR (cnt_editor));
118 
119 	gcd.content_hash = NULL;
120 	gcd.async_data = test_utils_async_call_prepare ();
121 
122 	e_content_editor_get_content (cnt_editor, flags, "test-domain", NULL, get_editor_content_hash_ready_cb, &gcd);
123 
124 	g_assert (test_utils_async_call_wait (gcd.async_data, MAX (event_processing_delay_ms / 25, 1) + 1));
125 	g_assert_nonnull (gcd.content_hash);
126 
127 	return gcd.content_hash;
128 }
129 
130 typedef struct _UndoContent {
131 	gchar *html;
132 	gchar *plain;
133 } UndoContent;
134 
135 static UndoContent *
undo_content_new(TestFixture * fixture)136 undo_content_new (TestFixture *fixture)
137 {
138 	EContentEditor *cnt_editor;
139 	EContentEditorContentHash *content_hash;
140 	UndoContent *uc;
141 
142 	g_return_val_if_fail (fixture != NULL, NULL);
143 	g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), NULL);
144 
145 	cnt_editor = e_html_editor_get_content_editor (fixture->editor);
146 	content_hash = test_utils_get_editor_content_hash_sync (cnt_editor, E_CONTENT_EDITOR_GET_TO_SEND_HTML | E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
147 
148 	uc = g_new0 (UndoContent, 1);
149 	uc->html = e_content_editor_util_steal_content_data (content_hash, E_CONTENT_EDITOR_GET_TO_SEND_HTML, NULL);
150 	uc->plain = e_content_editor_util_steal_content_data (content_hash, E_CONTENT_EDITOR_GET_TO_SEND_PLAIN, NULL);
151 
152 	e_content_editor_util_free_content_hash (content_hash);
153 
154 	g_warn_if_fail (uc->html != NULL);
155 	g_warn_if_fail (uc->plain != NULL);
156 
157 	return uc;
158 }
159 
160 static void
undo_content_free(gpointer ptr)161 undo_content_free (gpointer ptr)
162 {
163 	UndoContent *uc = ptr;
164 
165 	if (uc) {
166 		g_free (uc->html);
167 		g_free (uc->plain);
168 		g_free (uc);
169 	}
170 }
171 
172 static gboolean
undo_content_test(TestFixture * fixture,const UndoContent * uc,gint cmd_index)173 undo_content_test (TestFixture *fixture,
174 		   const UndoContent *uc,
175 		   gint cmd_index)
176 {
177 	EContentEditor *cnt_editor;
178 	EContentEditorContentHash *content_hash;
179 	const gchar *text;
180 
181 	g_return_val_if_fail (fixture != NULL, FALSE);
182 	g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
183 	g_return_val_if_fail (uc != NULL, FALSE);
184 
185 	cnt_editor = e_html_editor_get_content_editor (fixture->editor);
186 	content_hash = test_utils_get_editor_content_hash_sync (cnt_editor, E_CONTENT_EDITOR_GET_TO_SEND_HTML | E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
187 
188 	text = e_content_editor_util_get_content_data (content_hash, E_CONTENT_EDITOR_GET_TO_SEND_HTML);
189 	g_return_val_if_fail (text != NULL, FALSE);
190 
191 	if (!test_utils_html_equal (fixture, text, uc->html)) {
192 		if (glob_keep_going)
193 			g_printerr ("%s: returned HTML\n---%s---\n and expected HTML\n---%s---\n do not match at command %d\n", G_STRFUNC, text, uc->html, cmd_index);
194 		else
195 			g_warning ("%s: returned HTML\n---%s---\n and expected HTML\n---%s---\n do not match at command %d", G_STRFUNC, text, uc->html, cmd_index);
196 
197 		e_content_editor_util_free_content_hash (content_hash);
198 
199 		return FALSE;
200 	}
201 
202 	text = e_content_editor_util_get_content_data (content_hash, E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
203 	g_return_val_if_fail (text != NULL, FALSE);
204 
205 	if (!test_utils_html_equal (fixture, text, uc->plain)) {
206 		if (glob_keep_going)
207 			g_printerr ("%s: returned Plain\n---%s---\n and expected Plain\n---%s---\n do not match at command %d\n", G_STRFUNC, text, uc->plain, cmd_index);
208 		else
209 			g_warning ("%s: returned Plain\n---%s---\n and expected Plain\n---%s---\n do not match at command %d", G_STRFUNC, text, uc->plain, cmd_index);
210 
211 		e_content_editor_util_free_content_hash (content_hash);
212 
213 		return FALSE;
214 	}
215 
216 	e_content_editor_util_free_content_hash (content_hash);
217 
218 	return TRUE;
219 }
220 
221 static gboolean
test_utils_web_process_crashed_cb(WebKitWebView * web_view,gpointer user_data)222 test_utils_web_process_crashed_cb (WebKitWebView *web_view,
223 				   gpointer user_data)
224 {
225 	g_warning ("%s:", G_STRFUNC);
226 
227 	return FALSE;
228 }
229 
230 /* <Control>+<Shift>+I */
231 #define WEBKIT_INSPECTOR_MOD  (GDK_CONTROL_MASK | GDK_SHIFT_MASK)
232 #define WEBKIT_INSPECTOR_KEY  (GDK_KEY_I)
233 
234 static gboolean
wk_editor_key_press_event_cb(WebKitWebView * web_view,GdkEventKey * event)235 wk_editor_key_press_event_cb (WebKitWebView *web_view,
236 			      GdkEventKey *event)
237 {
238 	WebKitWebInspector *inspector;
239 	gboolean handled = FALSE;
240 
241 	inspector = webkit_web_view_get_inspector (web_view);
242 
243 	if ((event->state & WEBKIT_INSPECTOR_MOD) == WEBKIT_INSPECTOR_MOD &&
244 	    event->keyval == WEBKIT_INSPECTOR_KEY) {
245 		webkit_web_inspector_show (inspector);
246 		handled = TRUE;
247 	}
248 
249 	return handled;
250 }
251 
252 typedef struct _CreateData {
253 	gpointer async_data;
254 	TestFixture *fixture;
255 	gboolean created;
256 } CreateData;
257 
258 static void
test_utils_html_editor_created_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)259 test_utils_html_editor_created_cb (GObject *source_object,
260 				   GAsyncResult *result,
261 				   gpointer user_data)
262 {
263 	CreateData *create_data = user_data;
264 	TestFixture *fixture;
265 	EContentEditor *cnt_editor;
266 	GtkWidget *html_editor;
267 	GError *error = NULL;
268 
269 	g_return_if_fail (create_data != NULL);
270 
271 	create_data->created = TRUE;
272 
273 	fixture = create_data->fixture;
274 
275 	html_editor = e_html_editor_new_finish (result, &error);
276 	if (error) {
277 		g_warning ("%s: Failed to create editor: %s", G_STRFUNC, error->message);
278 		g_clear_error (&error);
279 		return;
280 	}
281 
282 	fixture->editor = E_HTML_EDITOR (html_editor);
283 
284 	g_object_set (G_OBJECT (fixture->editor),
285 		"halign", GTK_ALIGN_FILL,
286 		"hexpand", TRUE,
287 		"valign", GTK_ALIGN_FILL,
288 		"vexpand", TRUE,
289 		NULL);
290 	gtk_widget_show (GTK_WIDGET (fixture->editor));
291 	gtk_container_add (GTK_CONTAINER (fixture->window), GTK_WIDGET (fixture->editor));
292 
293 	fixture->focus_tracker = e_focus_tracker_new (GTK_WINDOW (fixture->window));
294 
295 	e_focus_tracker_set_cut_clipboard_action (fixture->focus_tracker,
296 		e_html_editor_get_action (fixture->editor, "cut"));
297 
298 	e_focus_tracker_set_copy_clipboard_action (fixture->focus_tracker,
299 		e_html_editor_get_action (fixture->editor, "copy"));
300 
301 	e_focus_tracker_set_paste_clipboard_action (fixture->focus_tracker,
302 		e_html_editor_get_action (fixture->editor, "paste"));
303 
304 	e_focus_tracker_set_select_all_action (fixture->focus_tracker,
305 		e_html_editor_get_action (fixture->editor, "select-all"));
306 
307 	e_focus_tracker_set_undo_action (fixture->focus_tracker,
308 		e_html_editor_get_action (fixture->editor, "undo"));
309 
310 	e_focus_tracker_set_redo_action (fixture->focus_tracker,
311 		e_html_editor_get_action (fixture->editor, "redo"));
312 
313 	/* Make sure this is off */
314 	test_utils_fixture_change_setting_boolean (fixture,
315 		"org.gnome.evolution.mail", "prompt-on-composer-mode-switch", FALSE);
316 
317 	cnt_editor = e_html_editor_get_content_editor (fixture->editor);
318 	g_object_set (G_OBJECT (cnt_editor),
319 		"halign", GTK_ALIGN_FILL,
320 		"hexpand", TRUE,
321 		"valign", GTK_ALIGN_FILL,
322 		"vexpand", TRUE,
323 		"height-request", 150,
324 		NULL);
325 
326 	g_signal_connect (cnt_editor, "web-process-crashed",
327 		G_CALLBACK (test_utils_web_process_crashed_cb), NULL);
328 
329 	if (WEBKIT_IS_WEB_VIEW (cnt_editor)) {
330 		WebKitSettings *web_settings;
331 
332 		web_settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (cnt_editor));
333 		webkit_settings_set_enable_developer_extras (web_settings, TRUE);
334 		webkit_settings_set_enable_write_console_messages_to_stdout (web_settings, TRUE);
335 
336 		g_signal_connect (
337 			cnt_editor, "key-press-event",
338 			G_CALLBACK (wk_editor_key_press_event_cb), NULL);
339 
340 		if (!test_utils_get_multiple_web_processes () && !global_web_context) {
341 			WebKitWebContext *web_context;
342 
343 			web_context = webkit_web_view_get_context (WEBKIT_WEB_VIEW (cnt_editor));
344 			global_web_context = G_OBJECT (g_object_ref (web_context));
345 		}
346 	}
347 
348 	gtk_window_set_focus (GTK_WINDOW (fixture->window), GTK_WIDGET (cnt_editor));
349 	gtk_widget_show (fixture->window);
350 
351 	test_utils_async_call_finish (create_data->async_data);
352 }
353 
354 /* The tests do not use the 'user_data' argument, thus the functions avoid them and the typecast is needed. */
355 typedef void (* ETestFixtureFunc) (TestFixture *fixture, gconstpointer user_data);
356 
357 void
test_utils_add_test(const gchar * name,ETestFixtureSimpleFunc func)358 test_utils_add_test (const gchar *name,
359 		     ETestFixtureSimpleFunc func)
360 {
361 	g_test_add (name, TestFixture, NULL,
362 		test_utils_fixture_set_up, (ETestFixtureFunc) func, test_utils_fixture_tear_down);
363 }
364 
365 static void test_utils_async_call_free (gpointer async_data);
366 
367 void
test_utils_fixture_set_up(TestFixture * fixture,gconstpointer user_data)368 test_utils_fixture_set_up (TestFixture *fixture,
369 			   gconstpointer user_data)
370 {
371 	CreateData create_data;
372 
373 	fixture->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
374 	fixture->undo_stack = NULL;
375 	fixture->key_state = 0;
376 
377 	if (test_utils_get_background ()) {
378 		gtk_window_set_keep_below (GTK_WINDOW (fixture->window), TRUE);
379 		gtk_window_set_focus_on_map (GTK_WINDOW (fixture->window), FALSE);
380 	}
381 
382 	create_data.async_data = test_utils_async_call_prepare ();
383 	create_data.fixture = fixture;
384 	create_data.created = FALSE;
385 
386 	e_html_editor_new (test_utils_html_editor_created_cb, &create_data);
387 
388 	if (create_data.created)
389 		test_utils_async_call_free (create_data.async_data);
390 	else
391 		test_utils_async_call_wait (create_data.async_data, 60);
392 
393 	g_warn_if_fail (fixture->editor != NULL);
394 	g_warn_if_fail (E_IS_HTML_EDITOR (fixture->editor));
395 }
396 
397 static void
free_old_settings(gpointer ptr)398 free_old_settings (gpointer ptr)
399 {
400 	TestSettings *data = ptr;
401 
402 	if (data) {
403 		GSettings *settings;
404 
405 		settings = e_util_ref_settings (data->schema);
406 		g_settings_set_value (settings, data->key, data->old_value);
407 		g_clear_object (&settings);
408 
409 		g_variant_unref (data->old_value);
410 		g_free (data->schema);
411 		g_free (data->key);
412 		g_free (data);
413 	}
414 }
415 
416 void
test_utils_fixture_tear_down(TestFixture * fixture,gconstpointer user_data)417 test_utils_fixture_tear_down (TestFixture *fixture,
418 			      gconstpointer user_data)
419 {
420 	g_clear_object (&fixture->focus_tracker);
421 
422 	gtk_widget_destroy (GTK_WIDGET (fixture->window));
423 	fixture->editor = NULL;
424 
425 	g_slist_free_full (fixture->settings, free_old_settings);
426 	fixture->settings = NULL;
427 
428 	g_slist_free_full (fixture->undo_stack, undo_content_free);
429 	fixture->undo_stack = NULL;
430 }
431 
432 void
test_utils_fixture_change_setting(TestFixture * fixture,const gchar * schema,const gchar * key,GVariant * value)433 test_utils_fixture_change_setting (TestFixture *fixture,
434 				   const gchar *schema,
435 				   const gchar *key,
436 				   GVariant *value)
437 {
438 	TestSettings *data;
439 	GSettings *settings;
440 
441 	g_return_if_fail (fixture != NULL);
442 	g_return_if_fail (schema != NULL);
443 	g_return_if_fail (key != NULL);
444 	g_return_if_fail (value != NULL);
445 
446 	g_variant_ref_sink (value);
447 
448 	settings = e_util_ref_settings (schema);
449 
450 	data = g_new0 (TestSettings, 1);
451 	data->schema = g_strdup (schema);
452 	data->key = g_strdup (key);
453 	data->old_value = g_settings_get_value (settings, key);
454 
455 	/* Use prepend, thus the restore comes in the opposite order, thus a change
456 	   of the same key is not a problem. */
457 	fixture->settings = g_slist_prepend (fixture->settings, data);
458 
459 	g_settings_set_value (settings, key, value);
460 
461 	g_clear_object (&settings);
462 	g_variant_unref (value);
463 }
464 
465 void
test_utils_fixture_change_setting_boolean(TestFixture * fixture,const gchar * schema,const gchar * key,gboolean value)466 test_utils_fixture_change_setting_boolean (TestFixture *fixture,
467 					   const gchar *schema,
468 					   const gchar *key,
469 					   gboolean value)
470 {
471 	test_utils_fixture_change_setting (fixture, schema, key, g_variant_new_boolean (value));
472 }
473 
474 void
test_utils_fixture_change_setting_int32(TestFixture * fixture,const gchar * schema,const gchar * key,gint value)475 test_utils_fixture_change_setting_int32 (TestFixture *fixture,
476 					 const gchar *schema,
477 					 const gchar *key,
478 					 gint value)
479 {
480 	test_utils_fixture_change_setting (fixture, schema, key, g_variant_new_int32 (value));
481 }
482 
483 void
test_utils_fixture_change_setting_string(TestFixture * fixture,const gchar * schema,const gchar * key,const gchar * value)484 test_utils_fixture_change_setting_string (TestFixture *fixture,
485 					  const gchar *schema,
486 					  const gchar *key,
487 					  const gchar *value)
488 {
489 	test_utils_fixture_change_setting (fixture, schema, key, g_variant_new_string (value));
490 }
491 
492 static void
test_utils_flush_main_context(void)493 test_utils_flush_main_context (void)
494 {
495 	GMainContext *main_context;
496 
497 	main_context = g_main_context_default ();
498 
499 	while (g_main_context_pending (main_context)) {
500 		g_main_context_iteration (main_context, FALSE);
501 	}
502 }
503 
504 static void
test_utils_async_call_free(gpointer async_data)505 test_utils_async_call_free (gpointer async_data)
506 {
507 	GMainLoop *loop = async_data;
508 
509 	test_utils_flush_main_context ();
510 
511 	g_main_loop_unref (loop);
512 }
513 
514 gpointer
test_utils_async_call_prepare(void)515 test_utils_async_call_prepare (void)
516 {
517 	return g_main_loop_new (NULL, FALSE);
518 }
519 
520 typedef struct _AsynCallData {
521 	GMainLoop *loop;
522 	gboolean timeout_reached;
523 } AsyncCallData;
524 
525 static gboolean
test_utils_async_call_timeout_reached_cb(gpointer user_data)526 test_utils_async_call_timeout_reached_cb (gpointer user_data)
527 {
528 	AsyncCallData *async_call_data = user_data;
529 
530 	g_return_val_if_fail (async_call_data != NULL, FALSE);
531 	g_return_val_if_fail (async_call_data->loop != NULL, FALSE);
532 	g_return_val_if_fail (!async_call_data->timeout_reached, FALSE);
533 
534 	if (!g_source_is_destroyed (g_main_current_source ())) {
535 		async_call_data->timeout_reached = TRUE;
536 		g_main_loop_quit (async_call_data->loop);
537 	}
538 
539 	return FALSE;
540 }
541 
542 gboolean
test_utils_async_call_wait(gpointer async_data,guint timeout_seconds)543 test_utils_async_call_wait (gpointer async_data,
544 			    guint timeout_seconds)
545 {
546 	GMainLoop *loop = async_data;
547 	AsyncCallData async_call_data;
548 	GSource *source = NULL;
549 
550 	g_return_val_if_fail (loop != NULL, FALSE);
551 
552 	async_call_data.loop = loop;
553 	async_call_data.timeout_reached = FALSE;
554 
555 	/* 0 is to wait forever */
556 	if (timeout_seconds > 0) {
557 		source = g_timeout_source_new_seconds (timeout_seconds);
558 		g_source_set_callback (source, test_utils_async_call_timeout_reached_cb, &async_call_data, NULL);
559 		g_source_attach (source, NULL);
560 	}
561 
562 	g_main_loop_run (loop);
563 
564 	if (source) {
565 		g_source_destroy (source);
566 		g_source_unref (source);
567 	}
568 
569 	test_utils_async_call_free (async_data);
570 
571 	return !async_call_data.timeout_reached;
572 }
573 
574 gboolean
test_utils_async_call_finish(gpointer async_data)575 test_utils_async_call_finish (gpointer async_data)
576 {
577 	GMainLoop *loop = async_data;
578 
579 	g_return_val_if_fail (loop != NULL, FALSE);
580 
581 	g_main_loop_quit (loop);
582 
583 	return FALSE;
584 }
585 
586 gboolean
test_utils_wait_milliseconds(guint milliseconds)587 test_utils_wait_milliseconds (guint milliseconds)
588 {
589 	gpointer async_data;
590 
591 	async_data = test_utils_async_call_prepare ();
592 	g_timeout_add (milliseconds, test_utils_async_call_finish, async_data);
593 
594 	return test_utils_async_call_wait (async_data, milliseconds / 1000 + 1);
595 }
596 
597 static void
test_utils_send_key_event(GtkWidget * widget,GdkEventType type,guint keyval,guint state)598 test_utils_send_key_event (GtkWidget *widget,
599 			   GdkEventType type,
600 			   guint keyval,
601 			   guint state)
602 {
603 	GdkKeymap *keymap;
604 	GdkKeymapKey *keys = NULL;
605 	gint n_keys;
606 	GdkEvent *event;
607 
608 	g_return_if_fail (GTK_IS_WIDGET (widget));
609 
610 	event = gdk_event_new (type);
611 	event->key.is_modifier =
612 		keyval == GDK_KEY_Shift_L ||
613 		keyval == GDK_KEY_Shift_R ||
614 		keyval == GDK_KEY_Control_L ||
615 		keyval == GDK_KEY_Control_R ||
616 		keyval == GDK_KEY_Alt_L ||
617 		keyval == GDK_KEY_Alt_R;
618 	event->key.keyval = keyval;
619 	event->key.state = state;
620 	event->key.window = g_object_ref (gtk_widget_get_window (widget));
621 	event->key.send_event = TRUE;
622 	event->key.length = 0;
623 	event->key.string = NULL;
624 	event->key.hardware_keycode = 0;
625 	event->key.group = 0;
626 	event->key.time = GDK_CURRENT_TIME;
627 
628 	gdk_event_set_device (event, gdk_seat_get_keyboard (gdk_display_get_default_seat (gtk_widget_get_display (widget))));
629 
630 	keymap = gdk_keymap_get_for_display (gtk_widget_get_display (widget));
631 	if (gdk_keymap_get_entries_for_keyval (keymap, keyval, &keys, &n_keys)) {
632 		if (n_keys > 0) {
633 			event->key.hardware_keycode = keys[0].keycode;
634 			event->key.group = keys[0].group;
635 		}
636 
637 		g_free (keys);
638 	}
639 
640 	gtk_main_do_event (event);
641 
642 	test_utils_wait_milliseconds (event_processing_delay_ms);
643 
644 	gdk_event_free (event);
645 }
646 
647 gboolean
test_utils_type_text(TestFixture * fixture,const gchar * text)648 test_utils_type_text (TestFixture *fixture,
649 		      const gchar *text)
650 {
651 	GtkWidget *widget;
652 
653 	g_return_val_if_fail (fixture != NULL, FALSE);
654 	g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
655 
656 	widget = GTK_WIDGET (e_html_editor_get_content_editor (fixture->editor));
657 	g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
658 
659 	g_return_val_if_fail (text != NULL, FALSE);
660 	g_return_val_if_fail (g_utf8_validate (text, -1, NULL), FALSE);
661 
662 	while (*text) {
663 		guint keyval;
664 		gunichar unichar;
665 
666 		unichar = g_utf8_get_char (text);
667 		text = g_utf8_next_char (text);
668 
669 		switch (unichar) {
670 		case '\n':
671 			keyval = GDK_KEY_Return;
672 			break;
673 		case '\t':
674 			keyval = GDK_KEY_Tab;
675 			break;
676 		case '\b':
677 			keyval = GDK_KEY_BackSpace;
678 			break;
679 		default:
680 			keyval = gdk_unicode_to_keyval (unichar);
681 			break;
682 		}
683 
684 		test_utils_send_key_event (widget, GDK_KEY_PRESS, keyval, fixture->key_state);
685 		test_utils_send_key_event (widget, GDK_KEY_RELEASE, keyval, fixture->key_state);
686 	}
687 
688 	test_utils_wait_milliseconds (event_processing_delay_ms);
689 
690 	return TRUE;
691 }
692 
693 typedef struct _HTMLEqualData {
694 	gpointer async_data;
695 	gboolean equal;
696 } HTMLEqualData;
697 
698 static void
test_html_equal_done_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)699 test_html_equal_done_cb (GObject *source_object,
700 			 GAsyncResult *result,
701 			 gpointer user_data)
702 {
703 	HTMLEqualData *hed = user_data;
704 	WebKitJavascriptResult *js_result;
705 	JSCException *exception;
706 	JSCValue *js_value;
707 	GError *error = NULL;
708 
709 	g_return_if_fail (hed != NULL);
710 
711 	js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (source_object), result, &error);
712 
713 	g_assert_no_error (error);
714 	g_clear_error (&error);
715 
716 	g_assert_nonnull (js_result);
717 
718 	js_value = webkit_javascript_result_get_js_value (js_result);
719 	g_assert_nonnull (js_value);
720 	g_assert (jsc_value_is_boolean (js_value));
721 
722 	hed->equal = jsc_value_to_boolean (js_value);
723 
724 	exception = jsc_context_get_exception (jsc_value_get_context (js_value));
725 
726 	if (exception) {
727 		g_warning ("Failed to call EvoEditorTest.isHTMLEqual: %s", jsc_exception_get_message (exception));
728 		jsc_context_clear_exception (jsc_value_get_context (js_value));
729 	}
730 
731 	webkit_javascript_result_unref (js_result);
732 
733 	test_utils_async_call_finish (hed->async_data);
734 }
735 
736 gboolean
test_utils_html_equal(TestFixture * fixture,const gchar * html1,const gchar * html2)737 test_utils_html_equal (TestFixture *fixture,
738 		       const gchar *html1,
739 		       const gchar *html2)
740 {
741 	EContentEditor *cnt_editor;
742 	gchar *script;
743 	HTMLEqualData hed;
744 
745 	g_return_val_if_fail (fixture != NULL, FALSE);
746 	g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
747 	g_return_val_if_fail (html1 != NULL, FALSE);
748 	g_return_val_if_fail (html2 != NULL, FALSE);
749 
750 	cnt_editor = e_html_editor_get_content_editor (fixture->editor);
751 	g_return_val_if_fail (cnt_editor != NULL, FALSE);
752 	g_return_val_if_fail (WEBKIT_IS_WEB_VIEW (cnt_editor), FALSE);
753 
754 	script = e_web_view_jsc_printf_script (
755 		"var EvoEditorTest = {};\n"
756 		"EvoEditorTest.fixupBlockquotes = function(parent) {\n"
757 		"	var ii, elems = parent.getElementsByTagName(\"BLOCKQUOTE\");\n"
758 		"	for (ii = 0; ii < elems.length; ii++) {\n"
759 		"		elems[ii].removeAttribute(\"spellcheck\");\n"
760 		"	}\n"
761 		"}\n"
762 		"EvoEditorTest.isHTMLEqual = function(html1, html2) {\n"
763 		"	var elem1, elem2;\n"
764 		"	elem1 = document.createElement(\"testHtmlEqual\");\n"
765 		"	elem2 = document.createElement(\"testHtmlEqual\");\n"
766 		"	elem1.innerHTML = html1.replace(/&nbsp;/g, \" \").replace(/ /g, \" \");\n"
767 		"	elem2.innerHTML = html2.replace(/&nbsp;/g, \" \").replace(/ /g, \" \");\n"
768 		"	EvoEditorTest.fixupBlockquotes(elem1);\n"
769 		"	EvoEditorTest.fixupBlockquotes(elem2);\n"
770 		"	elem1.normalize();\n"
771 		"	elem2.normalize();\n"
772 		"	return elem1.isEqualNode(elem2);\n"
773 		"}\n"
774 		"EvoEditorTest.isHTMLEqual(%s, %s);", html1, html2);
775 
776 	hed.async_data = test_utils_async_call_prepare ();
777 	hed.equal = FALSE;
778 
779 	webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (cnt_editor), script, NULL,
780 		test_html_equal_done_cb, &hed);
781 
782 	test_utils_async_call_wait (hed.async_data, 10);
783 
784 	g_free (script);
785 
786 	return hed.equal;
787 }
788 
789 static gboolean
test_utils_process_sequence(TestFixture * fixture,const gchar * sequence)790 test_utils_process_sequence (TestFixture *fixture,
791 			     const gchar *sequence)
792 {
793 	GtkWidget *widget;
794 	const gchar *seq;
795 	guint keyval;
796 	gboolean success = TRUE;
797 
798 	g_return_val_if_fail (fixture != NULL, FALSE);
799 	g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
800 	g_return_val_if_fail (sequence != NULL, FALSE);
801 
802 	widget = GTK_WIDGET (e_html_editor_get_content_editor (fixture->editor));
803 	g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
804 
805 	for (seq = sequence; *seq && success; seq++) {
806 		gboolean call_press = TRUE, call_release = TRUE;
807 		guint change_state = fixture->key_state;
808 
809 		switch (*seq) {
810 		case 'S': /* Shift key press */
811 			keyval = GDK_KEY_Shift_L;
812 
813 			if ((fixture->key_state & GDK_SHIFT_MASK) != 0) {
814 				success = FALSE;
815 				g_warning ("%s: Shift is already pressed", G_STRFUNC);
816 			} else {
817 				change_state |= GDK_SHIFT_MASK;
818 			}
819 			call_release = FALSE;
820 			break;
821 		case 's': /* Shift key release */
822 			keyval = GDK_KEY_Shift_L;
823 
824 			if ((fixture->key_state & GDK_SHIFT_MASK) == 0) {
825 				success = FALSE;
826 				g_warning ("%s: Shift is already released", G_STRFUNC);
827 			} else {
828 				change_state &= ~GDK_SHIFT_MASK;
829 			}
830 			call_press = FALSE;
831 			break;
832 		case 'C': /* Ctrl key press */
833 			keyval = GDK_KEY_Control_L;
834 
835 			if ((fixture->key_state & GDK_CONTROL_MASK) != 0) {
836 				success = FALSE;
837 				g_warning ("%s: Control is already pressed", G_STRFUNC);
838 			} else {
839 				change_state |= GDK_CONTROL_MASK;
840 			}
841 			call_release = FALSE;
842 			break;
843 		case 'c': /* Ctrl key release */
844 			keyval = GDK_KEY_Control_L;
845 
846 			if ((fixture->key_state & GDK_CONTROL_MASK) == 0) {
847 				success = FALSE;
848 				g_warning ("%s: Control is already released", G_STRFUNC);
849 			} else {
850 				change_state &= ~GDK_CONTROL_MASK;
851 			}
852 			call_press = FALSE;
853 			break;
854 		case 'A': /* Alt key press */
855 			keyval = GDK_KEY_Alt_L;
856 
857 			if ((fixture->key_state & GDK_MOD1_MASK) != 0) {
858 				success = FALSE;
859 				g_warning ("%s: Alt is already pressed", G_STRFUNC);
860 			} else {
861 				change_state |= GDK_MOD1_MASK;
862 			}
863 			call_release = FALSE;
864 			break;
865 		case 'a': /* Alt key release */
866 			keyval = GDK_KEY_Alt_L;
867 
868 			if ((fixture->key_state & GDK_MOD1_MASK) == 0) {
869 				success = FALSE;
870 				g_warning ("%s: Alt is already released", G_STRFUNC);
871 			} else {
872 				change_state &= ~GDK_MOD1_MASK;
873 			}
874 			call_press = FALSE;
875 			break;
876 		case 'h': /* Home key press + release */
877 			keyval = GDK_KEY_Home;
878 			break;
879 		case 'e': /* End key press + release */
880 			keyval = GDK_KEY_End;
881 			break;
882 		case 'P': /* Page-Up key press + release */
883 			keyval = GDK_KEY_Page_Up;
884 			break;
885 		case 'p': /* Page-Down key press + release */
886 			keyval = GDK_KEY_Page_Down;
887 			break;
888 		case 'l': /* Arrow-Left key press + release */
889 			keyval = GDK_KEY_Left;
890 			break;
891 		case 'r': /* Arrow-Right key press + release */
892 			keyval = GDK_KEY_Right;
893 			break;
894 		case 'u': /* Arrow-Up key press + release */
895 			keyval = GDK_KEY_Up;
896 			break;
897 		case 'd': /* Arrow-Down key press + release */
898 			keyval = GDK_KEY_Down;
899 			break;
900 		case 'D': /* Delete key press + release */
901 			keyval = GDK_KEY_Delete;
902 			break;
903 		case 'b': /* Backspace key press + release */
904 			keyval = GDK_KEY_BackSpace;
905 			break;
906 		case 't': /* Tab key press + release */
907 			keyval = GDK_KEY_Tab;
908 			break;
909 		case 'n': /* Return key press + release */
910 			keyval = GDK_KEY_Return;
911 			break;
912 		case 'i': /* Insert key press + release */
913 			keyval = GDK_KEY_Insert;
914 			break;
915 		case '^': /* Escape key press + release */
916 			keyval = GDK_KEY_Escape;
917 			break;
918 		default:
919 			success = FALSE;
920 			g_warning ("%s: Unknown sequence command '%c' in sequence '%s'", G_STRFUNC, *seq, sequence);
921 			break;
922 		}
923 
924 		if (success) {
925 			if (call_press)
926 				test_utils_send_key_event (widget, GDK_KEY_PRESS, keyval, fixture->key_state);
927 
928 			if (call_release)
929 				test_utils_send_key_event (widget, GDK_KEY_RELEASE, keyval, fixture->key_state);
930 		}
931 
932 		fixture->key_state = change_state;
933 	}
934 
935 	test_utils_wait_milliseconds (event_processing_delay_ms);
936 
937 	return success;
938 }
939 
940 static gboolean
test_utils_execute_action(TestFixture * fixture,const gchar * action_name)941 test_utils_execute_action (TestFixture *fixture,
942 			   const gchar *action_name)
943 {
944 	GtkAction *action;
945 
946 	g_return_val_if_fail (fixture != NULL, FALSE);
947 	g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
948 	g_return_val_if_fail (action_name != NULL, FALSE);
949 
950 	action = e_html_editor_get_action (fixture->editor, action_name);
951 	if (action) {
952 		gtk_action_activate (action);
953 	} else {
954 		g_warning ("%s: Failed to find action '%s'", G_STRFUNC, action_name);
955 		return FALSE;
956 	}
957 
958 	return TRUE;
959 }
960 
961 static gboolean
test_utils_set_font_name(TestFixture * fixture,const gchar * font_name)962 test_utils_set_font_name (TestFixture *fixture,
963 			  const gchar *font_name)
964 {
965 	EContentEditor *cnt_editor;
966 
967 	g_return_val_if_fail (fixture != NULL, FALSE);
968 	g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
969 
970 	cnt_editor = e_html_editor_get_content_editor (fixture->editor);
971 	e_content_editor_set_font_name (cnt_editor, font_name);
972 
973 	return TRUE;
974 }
975 
976 /* Expects only the part like "undo" [ ":" number ] */
977 static gint
test_utils_maybe_extract_undo_number(const gchar * command)978 test_utils_maybe_extract_undo_number (const gchar *command)
979 {
980 	const gchar *ptr;
981 	gint number;
982 
983 	g_return_val_if_fail (command != NULL, -1);
984 
985 	ptr = strchr (command, ':');
986 	if (!ptr)
987 		return 1;
988 
989 	number = atoi (ptr + 1);
990 	g_return_val_if_fail (number > 0, -1);
991 
992 	return number;
993 }
994 
995 static const UndoContent *
test_utils_pick_undo_content(const GSList * undo_stack,gint number)996 test_utils_pick_undo_content (const GSList *undo_stack,
997 			      gint number)
998 {
999 	const GSList *link;
1000 
1001 	g_return_val_if_fail (undo_stack != NULL, NULL);
1002 
1003 	number--;
1004 	for (link = undo_stack; link && number > 0; link = g_slist_next (link)) {
1005 		number--;
1006 	}
1007 
1008 	g_return_val_if_fail (link != NULL, NULL);
1009 	g_return_val_if_fail (link->data != NULL, NULL);
1010 
1011 	return link->data;
1012 }
1013 
1014 /* Each line of 'commands' contains one command.
1015 
1016    commands  = command *("\n" command)
1017 
1018    command   = actioncmd ; Execute an action
1019              / modecmd   ; Change editor mode to HTML or Plain Text
1020              / fnmcmd    ; Set font name
1021              / seqcmd    ; Sequence of special key strokes
1022              / typecmd   ; Type a text
1023              / undocmd   ; Undo/redo commands
1024              / waitcmd   ; Wait command
1025 
1026    actioncmd = "action:" name
1027 
1028    modecmd   = "mode:" ("html" / "plain")
1029 
1030    fnmcmd    = "font-name:" name
1031 
1032    seqcmd    = "seq:" sequence
1033 
1034    sequence  = "S" ; Shift key press
1035              / "s" ; Shift key release
1036              / "C" ; Ctrl key press
1037              / "c" ; Ctrl key release
1038              / "A" ; Alt key press
1039              / "a" ; Alt key release
1040              / "h" ; Home key press + release
1041              / "e" ; End key press + release
1042              / "P" ; Page-Up key press + release
1043              / "p" ; Page-Down key press + release
1044              / "l" ; Arrow-Left key press + release
1045              / "r" ; Arrow-Right key press + release
1046              / "u" ; Arrow-Up key press + release
1047              / "d" ; Arrow-Down key press + release
1048              / "D" ; Delete key press + release
1049              / "b" ; Backspace key press + release
1050              / "t" ; Tab key press + release
1051              / "n" ; Return key press + release
1052              / "i" ; Insert key press + release
1053 	     / "^" ; Escape key press + release
1054 
1055    typecmd   = "type:" text ; the 'text' can contain escaped letters with a backslash, like "\\n" transforms into "\n"
1056 
1057    undocmd   = "undo:" undotype
1058 
1059    undotype  = "undo" [ ":" number ] ; Call 'undo', number-times; if 'number' is not provided, then call it exactly once
1060              / "redo" [ ":" number ] ; Call 'redo', number-times; if 'number' is not provided, then call it exactly once
1061 	     / "save"                ; Save current content of the editor for later tests
1062 	     / "drop" [ ":" number ] ; Forgets saved content, if 'number' is provided, then top number saves are forgotten
1063 	     / "test" [ ":" number ] ; Tests current editor content against any previously saved state; the optional
1064                                      ; 'number' argument can be used to specify which exact previous state to use
1065 
1066    waitcmd   = "wait:" milliseconds  ; waits for 'milliseconds'
1067  */
1068 gboolean
test_utils_process_commands(TestFixture * fixture,const gchar * commands)1069 test_utils_process_commands (TestFixture *fixture,
1070 			     const gchar *commands)
1071 {
1072 	gchar **cmds;
1073 	gint cc;
1074 	gboolean success = TRUE;
1075 
1076 	g_return_val_if_fail (fixture != NULL, FALSE);
1077 	g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
1078 	g_return_val_if_fail (commands != NULL, FALSE);
1079 
1080 	cmds = g_strsplit (commands, "\n", -1);
1081 	for (cc = 0; cmds && cmds[cc] && success; cc++) {
1082 		const gchar *command = cmds[cc];
1083 
1084 		if (g_str_has_prefix (command, "action:")) {
1085 			test_utils_execute_action (fixture, command + 7);
1086 		} else if (g_str_has_prefix (command, "mode:")) {
1087 			const gchar *mode_change = command + 5;
1088 
1089 			if (g_str_equal (mode_change, "html")) {
1090 				test_utils_execute_action (fixture, "mode-html");
1091 			} else if (g_str_equal (mode_change, "plain")) {
1092 				test_utils_execute_action (fixture, "mode-plain");
1093 			} else {
1094 				success = FALSE;
1095 				g_warning ("%s: Unknown mode '%s'", G_STRFUNC, mode_change);
1096 			}
1097 		} else if (g_str_has_prefix (command, "font-name:")) {
1098 			success = test_utils_set_font_name (fixture, command + 10);
1099 		} else if (g_str_has_prefix (command, "seq:")) {
1100 			success = test_utils_process_sequence (fixture, command + 4);
1101 		} else if (g_str_has_prefix (command, "type:")) {
1102 			gchar *text;
1103 
1104 			text = g_strcompress (command + 5);
1105 			success = test_utils_type_text (fixture, text);
1106 			if (!success)
1107 				g_warning ("%s: Failed to type text '%s'", G_STRFUNC, text);
1108 			g_free (text);
1109 		} else if (g_str_has_prefix (command, "undo:")) {
1110 			gint number;
1111 
1112 			command += 5;
1113 
1114 			if (g_str_equal (command, "undo") || g_str_has_prefix (command, "undo:")) {
1115 				number = test_utils_maybe_extract_undo_number (command);
1116 				while (number > 0 && success) {
1117 					success = test_utils_execute_action (fixture, "undo");
1118 					number--;
1119 				}
1120 			} else if (g_str_has_prefix (command, "redo") || g_str_has_prefix (command, "redo:")) {
1121 				number = test_utils_maybe_extract_undo_number (command);
1122 				while (number > 0 && success) {
1123 					success = test_utils_execute_action (fixture, "redo");
1124 					number--;
1125 				}
1126 			} else if (g_str_equal (command, "save")) {
1127 				UndoContent *uc;
1128 
1129 				uc = undo_content_new (fixture);
1130 				fixture->undo_stack = g_slist_prepend (fixture->undo_stack, uc);
1131 			} else if (g_str_equal (command, "drop") || g_str_has_prefix (command, "drop:")) {
1132 				number = test_utils_maybe_extract_undo_number (command);
1133 				g_warn_if_fail (number <= g_slist_length (fixture->undo_stack));
1134 
1135 				while (number > 0 && fixture->undo_stack) {
1136 					UndoContent *uc = fixture->undo_stack->data;
1137 
1138 					fixture->undo_stack = g_slist_remove (fixture->undo_stack, uc);
1139 					undo_content_free (uc);
1140 					number--;
1141 				}
1142 			} else if (g_str_equal (command, "test") || g_str_has_prefix (command, "test:")) {
1143 				const UndoContent *uc;
1144 
1145 				number = test_utils_maybe_extract_undo_number (command);
1146 				uc = test_utils_pick_undo_content (fixture->undo_stack, number);
1147 				success = uc && undo_content_test (fixture, uc, cc);
1148 			} else {
1149 				g_warning ("%s: Unknown command 'undo:%s'", G_STRFUNC, command);
1150 				success = FALSE;
1151 			}
1152 
1153 			test_utils_wait_milliseconds (event_processing_delay_ms);
1154 		} else if (g_str_has_prefix (command, "wait:")) {
1155 			test_utils_wait_milliseconds (atoi (command + 5));
1156 		} else if (*command) {
1157 			g_warning ("%s: Unknown command '%s'", G_STRFUNC, command);
1158 			success = FALSE;
1159 		}
1160 
1161 		/* Wait at least 100 ms, to give a chance to move the cursor and
1162 		   other things for WebKit, for example before executing actions. */
1163 		test_utils_wait_milliseconds (MAX (event_processing_delay_ms, 100));
1164 	}
1165 
1166 	g_strfreev (cmds);
1167 
1168 	if (success) {
1169 		/* Give the editor some time to finish any ongoing async operations */
1170 		test_utils_wait_milliseconds (MAX (event_processing_delay_ms, 100));
1171 	}
1172 
1173 	return success;
1174 }
1175 
1176 gboolean
test_utils_run_simple_test(TestFixture * fixture,const gchar * commands,const gchar * expected_html,const gchar * expected_plain)1177 test_utils_run_simple_test (TestFixture *fixture,
1178 			    const gchar *commands,
1179 			    const gchar *expected_html,
1180 			    const gchar *expected_plain)
1181 {
1182 	EContentEditor *cnt_editor;
1183 	EContentEditorContentHash *content_hash;
1184 	const gchar *text;
1185 
1186 	g_return_val_if_fail (fixture != NULL, FALSE);
1187 	g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
1188 	g_return_val_if_fail (commands != NULL, FALSE);
1189 
1190 	cnt_editor = e_html_editor_get_content_editor (fixture->editor);
1191 
1192 	if (!test_utils_process_commands (fixture, commands))
1193 		return FALSE;
1194 
1195 	content_hash = test_utils_get_editor_content_hash_sync (cnt_editor, E_CONTENT_EDITOR_GET_TO_SEND_HTML | E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
1196 
1197 	if (expected_html) {
1198 		text = e_content_editor_util_get_content_data (content_hash, E_CONTENT_EDITOR_GET_TO_SEND_HTML);
1199 		g_return_val_if_fail (text != NULL, FALSE);
1200 
1201 		if (!test_utils_html_equal (fixture, text, expected_html)) {
1202 			if (glob_keep_going)
1203 				g_printerr ("%s: returned HTML\n---%s---\n and expected HTML\n---%s---\n do not match\n", G_STRFUNC, text, expected_html);
1204 			else
1205 				g_warning ("%s: returned HTML\n---%s---\n and expected HTML\n---%s---\n do not match", G_STRFUNC, text, expected_html);
1206 
1207 			e_content_editor_util_free_content_hash (content_hash);
1208 
1209 			return FALSE;
1210 		}
1211 	}
1212 
1213 	if (expected_plain) {
1214 		text = e_content_editor_util_get_content_data (content_hash, E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
1215 		g_return_val_if_fail (text != NULL, FALSE);
1216 
1217 		if (!test_utils_html_equal (fixture, text, expected_plain)) {
1218 			if (glob_keep_going)
1219 				g_printerr ("%s: returned Plain\n---%s---\n and expected Plain\n---%s---\n do not match\n", G_STRFUNC, text, expected_plain);
1220 			else
1221 				g_warning ("%s: returned Plain\n---%s---\n and expected Plain\n---%s---\n do not match", G_STRFUNC, text, expected_plain);
1222 
1223 			e_content_editor_util_free_content_hash (content_hash);
1224 
1225 			return FALSE;
1226 		}
1227 	}
1228 
1229 	e_content_editor_util_free_content_hash (content_hash);
1230 
1231 	return TRUE;
1232 }
1233 
1234 void
test_utils_insert_content(TestFixture * fixture,const gchar * content,EContentEditorInsertContentFlags flags)1235 test_utils_insert_content (TestFixture *fixture,
1236 			   const gchar *content,
1237 			   EContentEditorInsertContentFlags flags)
1238 {
1239 	EContentEditor *cnt_editor;
1240 
1241 	g_return_if_fail (fixture != NULL);
1242 	g_return_if_fail (E_IS_HTML_EDITOR (fixture->editor));
1243 	g_return_if_fail (content != NULL);
1244 
1245 	cnt_editor = e_html_editor_get_content_editor (fixture->editor);
1246 	e_content_editor_insert_content (cnt_editor, content, flags);
1247 }
1248 
1249 void
test_utils_set_clipboard_text(const gchar * text,gboolean is_html)1250 test_utils_set_clipboard_text (const gchar *text,
1251 			       gboolean is_html)
1252 {
1253 	GtkClipboard *clipboard;
1254 
1255 	g_return_if_fail (text != NULL);
1256 
1257 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1258 	g_return_if_fail (clipboard != NULL);
1259 
1260 	gtk_clipboard_clear (clipboard);
1261 
1262 	if (is_html) {
1263 		e_clipboard_set_html (clipboard, text, -1);
1264 	} else {
1265 		gtk_clipboard_set_text (clipboard, text, -1);
1266 	}
1267 }
1268 
1269 gchar *
test_utils_get_clipboard_text(gboolean request_html)1270 test_utils_get_clipboard_text (gboolean request_html)
1271 {
1272 	GtkClipboard *clipboard;
1273 	gchar *text;
1274 
1275 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1276 	g_return_val_if_fail (clipboard != NULL, NULL);
1277 
1278 	if (request_html) {
1279 		g_return_val_if_fail (e_clipboard_wait_is_html_available (clipboard), NULL);
1280 		text = e_clipboard_wait_for_html (clipboard);
1281 	} else {
1282 		g_return_val_if_fail (gtk_clipboard_wait_is_text_available (clipboard), NULL);
1283 		text = gtk_clipboard_wait_for_text (clipboard);
1284 	}
1285 
1286 	g_return_val_if_fail (text != NULL, NULL);
1287 
1288 	return text;
1289 }
1290 
1291 EContentEditor *
test_utils_get_content_editor(TestFixture * fixture)1292 test_utils_get_content_editor (TestFixture *fixture)
1293 {
1294 	EContentEditor *cnt_editor;
1295 
1296 	g_return_val_if_fail (fixture != NULL, NULL);
1297 	g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), NULL);
1298 
1299 	cnt_editor = e_html_editor_get_content_editor (fixture->editor);
1300 	return cnt_editor;
1301 }
1302 
1303 gchar *
test_utils_dup_image_uri(const gchar * path)1304 test_utils_dup_image_uri (const gchar *path)
1305 {
1306 	gchar *image_uri = NULL;
1307 	GError *error = NULL;
1308 
1309 	if (path && strchr (path, G_DIR_SEPARATOR)) {
1310 		image_uri = g_filename_to_uri (path, NULL, &error);
1311 	} else {
1312 		gchar *filename;
1313 
1314 		filename = e_icon_factory_get_icon_filename (path, GTK_ICON_SIZE_MENU);
1315 		if (filename) {
1316 			image_uri = g_filename_to_uri (filename, NULL, &error);
1317 			g_free (filename);
1318 		} else {
1319 			g_set_error (&error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Icon '%s' not found", path);
1320 		}
1321 	}
1322 
1323 	if (image_uri) {
1324 		gchar *tmp;
1325 
1326 		tmp = g_strconcat ("evo-", image_uri, NULL);
1327 		g_free (image_uri);
1328 		image_uri = tmp;
1329 	}
1330 
1331 	g_assert_no_error (error);
1332 	g_assert_nonnull (image_uri);
1333 
1334 	return image_uri;
1335 }
1336 
1337 static void
test_utils_insert_signature_done_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)1338 test_utils_insert_signature_done_cb (GObject *source_object,
1339 				     GAsyncResult *result,
1340 				     gpointer user_data)
1341 {
1342 	gpointer async_data = user_data;
1343 	WebKitJavascriptResult *js_result;
1344 	JSCException *exception;
1345 	JSCValue *js_value;
1346 	GError *error = NULL;
1347 
1348 	g_return_if_fail (async_data != NULL);
1349 
1350 	js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (source_object), result, &error);
1351 
1352 	g_assert_no_error (error);
1353 	g_clear_error (&error);
1354 
1355 	g_assert_nonnull (js_result);
1356 
1357 	js_value = webkit_javascript_result_get_js_value (js_result);
1358 	g_assert_nonnull (js_value);
1359 
1360 	exception = jsc_context_get_exception (jsc_value_get_context (js_value));
1361 
1362 	if (exception) {
1363 		g_warning ("Failed to call EvoEditor.InsertSignature: %s", jsc_exception_get_message (exception));
1364 		jsc_context_clear_exception (jsc_value_get_context (js_value));
1365 	}
1366 
1367 	webkit_javascript_result_unref (js_result);
1368 
1369 	test_utils_async_call_finish (async_data);
1370 }
1371 
1372 void
test_utils_insert_signature(TestFixture * fixture,const gchar * content,gboolean is_html,const gchar * uid,gboolean start_bottom,gboolean top_signature,gboolean add_delimiter)1373 test_utils_insert_signature (TestFixture *fixture,
1374 			     const gchar *content,
1375 			     gboolean is_html,
1376 			     const gchar *uid,
1377 			     gboolean start_bottom,
1378 			     gboolean top_signature,
1379 			     gboolean add_delimiter)
1380 {
1381 	EContentEditor *cnt_editor;
1382 	gchar *script;
1383 	gpointer async_data;
1384 
1385 	g_return_if_fail (fixture != NULL);
1386 	g_return_if_fail (E_IS_HTML_EDITOR (fixture->editor));
1387 	g_return_if_fail (content != NULL);
1388 	g_return_if_fail (uid != NULL);
1389 
1390 	cnt_editor = e_html_editor_get_content_editor (fixture->editor);
1391 	g_return_if_fail (cnt_editor != NULL);
1392 	g_return_if_fail (WEBKIT_IS_WEB_VIEW (cnt_editor));
1393 
1394 	script = e_web_view_jsc_printf_script (
1395 		"EvoEditor.InsertSignature(%s, %x, false, %s, false, false, true, %x, %x, %x);",
1396 		content, is_html, uid, start_bottom, top_signature, add_delimiter);
1397 
1398 	async_data = test_utils_async_call_prepare ();
1399 
1400 	webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (cnt_editor), script, NULL,
1401 		test_utils_insert_signature_done_cb, async_data);
1402 
1403 	test_utils_async_call_wait (async_data, 10);
1404 
1405 	g_free (script);
1406 }
1407