[evolution/wip/webkit2] Initial simple automated EHTMLEditor tests



commit 8efb29c4f35211a2cccc7ef21d29a285f872a4f7
Author: Milan Crha <mcrha redhat com>
Date:   Tue Jun 21 19:53:52 2016 +0200

    Initial simple automated EHTMLEditor tests

 e-util/Makefile.am                                 |    9 +
 e-util/e-html-editor-private.h                     |    4 +
 e-util/e-html-editor.c                             |   23 +
 e-util/test-html-editor-units-utils.c              |  810 ++++++++++++++++++++
 e-util/test-html-editor-units-utils.h              |   53 ++
 e-util/test-html-editor-units.c                    |  230 ++++++
 modules/webkit-editor/e-webkit-editor.c            |   31 +-
 modules/webkit-editor/web-extension/Makefile.am    |    2 +
 .../e-html-editor-test-dom-functions.c             |   69 ++
 .../e-html-editor-test-dom-functions.h             |   26 +
 .../web-extension/e-html-editor-web-extension.c    |   27 +-
 11 files changed, 1282 insertions(+), 2 deletions(-)
---
diff --git a/e-util/Makefile.am b/e-util/Makefile.am
index 43cd869..b6e9faa 100644
--- a/e-util/Makefile.am
+++ b/e-util/Makefile.am
@@ -61,6 +61,7 @@ noinst_PROGRAMS = \
        test-category-completion \
        test-contact-store \
        test-dateedit \
+       test-html-editor-units \
        test-mail-signatures \
        test-name-selector \
        test-preferences-window \
@@ -690,6 +691,14 @@ test_dateedit_LDADD = $(TEST_LDADD)
 #test_html_editor_SOURCES = test-html-editor.c
 #test_html_editor_LDADD = $(TEST_LDADD)
 
+test_html_editor_units_CPPFLAGS = $(TEST_CPPFLAGS) -DTEST_TOP_SRCDIR=\""$(top_srcdir)"\"
+test_html_editor_units_SOURCES = \
+       test-html-editor-units-utils.h \
+       test-html-editor-units-utils.c \
+       test-html-editor-units.c \
+       $(NULL)
+test_html_editor_units_LDADD = $(TEST_LDADD)
+
 test_mail_signatures_CPPFLAGS = $(TEST_CPPFLAGS)
 test_mail_signatures_SOURCES = test-mail-signatures.c
 test_mail_signatures_LDADD = $(TEST_LDADD)
diff --git a/e-util/e-html-editor-private.h b/e-util/e-html-editor-private.h
index d201214..37523a5 100644
--- a/e-util/e-html-editor-private.h
+++ b/e-util/e-html-editor-private.h
@@ -93,10 +93,14 @@ struct _EHTMLEditorPrivate {
        guint spell_suggestions_merge_id;
 
        gint editor_layout_row;
+
+       gboolean is_testing;
 };
 
 void           editor_actions_init             (EHTMLEditor *editor);
 void           editor_actions_bind             (EHTMLEditor *editor);
+const gchar *  e_html_editor_get_content_editor_name
+                                               (EHTMLEditor *editor);
 
 G_END_DECLS
 
diff --git a/e-util/e-html-editor.c b/e-util/e-html-editor.c
index 30d256e..c2f495a 100644
--- a/e-util/e-html-editor.c
+++ b/e-util/e-html-editor.c
@@ -956,6 +956,29 @@ e_html_editor_get_content_editor (EHTMLEditor *editor)
        return editor->priv->use_content_editor;
 }
 
+/* Private function */
+const gchar *
+e_html_editor_get_content_editor_name (EHTMLEditor *editor)
+{
+       EContentEditor *cnt_editor;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
+
+       cnt_editor = e_html_editor_get_content_editor (editor);
+       if (!cnt_editor)
+               return NULL;
+
+       g_hash_table_iter_init (&iter, editor->priv->content_editors);
+       if (g_hash_table_iter_next (&iter, &key, &value)) {
+               if (value == cnt_editor)
+                       return key;
+       }
+
+       return NULL;
+}
+
 void
 e_html_editor_register_content_editor (EHTMLEditor *editor,
                                       const gchar *name,
diff --git a/e-util/test-html-editor-units-utils.c b/e-util/test-html-editor-units-utils.c
new file mode 100644
index 0000000..a1d8b34
--- /dev/null
+++ b/e-util/test-html-editor-units-utils.c
@@ -0,0 +1,810 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "e-util/e-util.h"
+
+#include "test-html-editor-units-utils.h"
+
+typedef struct _UndoContent {
+       gchar *html;
+       gchar *plain;
+} UndoContent;
+
+static UndoContent *
+undo_content_new (TestFixture *fixture)
+{
+       EContentEditor *cnt_editor;
+       UndoContent *uc;
+
+       g_return_val_if_fail (fixture != NULL, NULL);
+       g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), NULL);
+
+       cnt_editor = e_html_editor_get_content_editor (fixture->editor);
+
+       uc = g_new0 (UndoContent, 1);
+       uc->html = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_HTML, NULL);
+       uc->plain = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_PLAIN, NULL);
+
+       g_warn_if_fail (uc->html != NULL);
+       g_warn_if_fail (uc->plain != NULL);
+
+       return uc;
+}
+
+static void
+undo_content_free (gpointer ptr)
+{
+       UndoContent *uc = ptr;
+
+       if (uc) {
+               g_free (uc->html);
+               g_free (uc->plain);
+               g_free (uc);
+       }
+}
+
+static gboolean
+undo_content_test (TestFixture *fixture,
+                  const UndoContent *uc)
+{
+       EContentEditor *cnt_editor;
+       gchar *text;
+
+       g_return_val_if_fail (fixture != NULL, FALSE);
+       g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
+       g_return_val_if_fail (uc != NULL, FALSE);
+
+       cnt_editor = e_html_editor_get_content_editor (fixture->editor);
+
+       text = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_HTML, NULL);
+       g_return_val_if_fail (text != NULL, FALSE);
+
+       if (!test_utils_html_equal (fixture, text, uc->html)) {
+               g_warning ("%s: returned HTML\n---%s---\n and expected HTML\n---%s---\n do not match", 
G_STRFUNC, text, uc->html);
+               g_free (text);
+               return FALSE;
+       }
+
+       g_free (text);
+
+       text = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_PLAIN, NULL);
+       g_return_val_if_fail (text != NULL, FALSE);
+
+       if (!test_utils_html_equal (fixture, text, uc->plain)) {
+               g_warning ("%s: returned Plain\n---%s---\n and expected Plain\n---%s---\n do not match", 
G_STRFUNC, text, uc->plain);
+               g_free (text);
+               return FALSE;
+       }
+
+       g_free (text);
+
+       return TRUE;
+}
+
+void
+test_utils_fixture_set_up (TestFixture *fixture,
+                          gconstpointer user_data)
+{
+       EContentEditor *cnt_editor;
+       GSettings *settings;
+       gpointer async_data;
+
+       settings = e_util_ref_settings ("org.gnome.evolution.mail");
+
+       fixture->prompt_on_composer_mode_switch = g_settings_get_boolean (settings, 
"prompt-on-composer-mode-switch");
+       fixture->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+       fixture->editor = E_HTML_EDITOR (e_html_editor_new ());
+       fixture->undo_stack = NULL;
+
+       g_object_set (G_OBJECT (fixture->editor),
+               "halign", GTK_ALIGN_FILL,
+               "hexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               NULL);
+       gtk_widget_show (GTK_WIDGET (fixture->editor));
+       gtk_container_add (GTK_CONTAINER (fixture->window), GTK_WIDGET (fixture->editor));
+
+       /* Turn this off; it may be left off on test failures */
+       g_settings_set_boolean (settings, "prompt-on-composer-mode-switch", FALSE);
+
+       g_clear_object (&settings);
+
+       cnt_editor = e_html_editor_get_content_editor (fixture->editor);
+       g_object_set (G_OBJECT (cnt_editor),
+               "halign", GTK_ALIGN_FILL,
+               "hexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "height-request", 150,
+               NULL);
+
+       async_data = test_utils_async_call_prepare ();
+
+       g_signal_connect_swapped (cnt_editor, "notify::web-extension",
+               G_CALLBACK (test_utils_async_call_finish), async_data);
+
+       gtk_window_set_focus (GTK_WINDOW (fixture->window), GTK_WIDGET (cnt_editor));
+       gtk_widget_show (fixture->window);
+
+       test_utils_async_call_wait (async_data, 5);
+}
+
+void
+test_utils_fixture_tear_down (TestFixture *fixture,
+                             gconstpointer user_data)
+{
+       GSettings *settings;
+
+       gtk_widget_destroy (GTK_WIDGET (fixture->window));
+       fixture->editor = NULL;
+
+       settings = e_util_ref_settings ("org.gnome.evolution.mail");
+       g_settings_set_boolean (settings, "prompt-on-composer-mode-switch", 
fixture->prompt_on_composer_mode_switch);
+       g_clear_object (&settings);
+
+       g_slist_free_full (fixture->undo_stack, undo_content_free);
+       fixture->undo_stack = NULL;
+}
+
+static void
+test_utils_flush_main_context (void)
+{
+       GMainContext *main_context;
+
+       main_context = g_main_context_default ();
+
+       while (g_main_context_pending (main_context)) {
+               g_main_context_iteration (main_context, FALSE);
+       }
+}
+
+gpointer
+test_utils_async_call_prepare (void)
+{
+       return g_main_loop_new (NULL, FALSE);
+}
+
+typedef struct _AsynCallData {
+       GMainLoop *loop;
+       gboolean timeout_reached;
+} AsyncCallData;
+
+static gboolean
+test_utils_async_call_timeout_reached_cb (gpointer user_data)
+{
+       AsyncCallData *async_call_data = user_data;
+
+       g_return_val_if_fail (async_call_data != NULL, FALSE);
+       g_return_val_if_fail (async_call_data->loop != NULL, FALSE);
+       g_return_val_if_fail (!async_call_data->timeout_reached, FALSE);
+
+       if (!g_source_is_destroyed (g_main_current_source ())) {
+               async_call_data->timeout_reached = TRUE;
+               g_main_loop_quit (async_call_data->loop);
+       }
+
+       return FALSE;
+}
+
+gboolean
+test_utils_async_call_wait (gpointer async_data,
+                           guint timeout_seconds)
+{
+       GMainLoop *loop = async_data;
+       AsyncCallData async_call_data;
+       GSource *source = NULL;
+
+       g_return_val_if_fail (loop != NULL, FALSE);
+
+       async_call_data.loop = loop;
+       async_call_data.timeout_reached = FALSE;
+
+       /* 0 is to wait forever */
+       if (timeout_seconds > 0) {
+               source = g_timeout_source_new_seconds (timeout_seconds);
+               g_source_set_callback (source, test_utils_async_call_timeout_reached_cb, &async_call_data, 
NULL);
+               g_source_attach (source, NULL);
+       }
+
+       g_main_loop_run (loop);
+
+       if (source) {
+               g_source_destroy (source);
+               g_source_unref (source);
+       }
+
+       test_utils_flush_main_context ();
+
+       g_main_loop_unref (loop);
+
+       return !async_call_data.timeout_reached;
+}
+
+gboolean
+test_utils_async_call_finish (gpointer async_data)
+{
+       GMainLoop *loop = async_data;
+
+       g_return_val_if_fail (loop != NULL, FALSE);
+
+       g_main_loop_quit (loop);
+
+       return FALSE;
+}
+
+gboolean
+test_utils_wait_milliseconds (guint milliseconds)
+{
+       gpointer async_data;
+
+       async_data = test_utils_async_call_prepare ();
+       g_timeout_add (milliseconds, test_utils_async_call_finish, async_data);
+
+       return test_utils_async_call_wait (async_data, milliseconds / 1000 + 1);
+}
+
+gboolean
+test_utils_type_text (TestFixture *fixture,
+                     const gchar *text)
+{
+       GtkWidget *widget;
+       GdkKeymap *keymap;
+       GdkEvent event;
+       gboolean none_failed = TRUE;
+
+       g_return_val_if_fail (fixture != NULL, FALSE);
+       g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
+
+       widget = GTK_WIDGET (e_html_editor_get_content_editor (fixture->editor));
+       g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+
+       g_return_val_if_fail (text != NULL, FALSE);
+       g_return_val_if_fail (g_utf8_validate (text, -1, NULL), FALSE);
+
+       event.key.window = gtk_widget_get_window (widget);
+       event.key.send_event = TRUE;
+       event.key.length = 0;
+       event.key.string = NULL;
+       event.key.is_modifier = FALSE;
+
+       keymap = gdk_keymap_get_for_display (gtk_widget_get_display (widget));
+
+       while (*text && none_failed) {
+               GdkKeymapKey *keys = NULL;
+               gint n_keys;
+               gunichar unichar;
+
+               unichar = g_utf8_get_char (text);
+               text = g_utf8_next_char (text);
+
+               event.key.keyval = gdk_unicode_to_keyval (unichar);
+
+               if (gdk_keymap_get_entries_for_keyval (keymap, event.key.keyval, &keys, &n_keys) && n_keys > 
0) {
+                       GdkEvent *nevent;
+
+                       nevent = gdk_event_new (GDK_KEY_PRESS);
+                       nevent->key.keyval = event.key.keyval;
+                       nevent->key.state = event.key.state;
+                       nevent->key.window = g_object_ref (event.key.window);
+                       nevent->key.send_event = event.key.send_event;
+                       nevent->key.length = event.key.length;
+                       nevent->key.string = event.key.string;
+                       nevent->key.is_modifier = event.key.is_modifier;
+                       gdk_event_set_device (nevent, gdk_seat_get_keyboard (gdk_display_get_default_seat 
(gtk_widget_get_display (widget))));
+                       nevent->key.hardware_keycode = keys[0].keycode;
+                       nevent->key.group = keys[0].group;
+
+                       nevent->key.type = GDK_KEY_PRESS;
+                       nevent->key.time = GDK_CURRENT_TIME;
+                       gtk_main_do_event (nevent);
+
+                       test_utils_wait_milliseconds (5);
+
+                       nevent->key.type = GDK_KEY_RELEASE;
+                       nevent->key.time = GDK_CURRENT_TIME;
+                       gtk_main_do_event (nevent);
+
+                       test_utils_wait_milliseconds (5);
+
+                       gdk_event_free (nevent);
+               } else {
+                       none_failed = FALSE;
+                       g_warning ("%s: Nothing found for unichar '%x'", G_STRFUNC, unichar);
+               }
+
+               g_free (keys);
+       }
+
+       test_utils_wait_milliseconds (5);
+
+       return none_failed;
+}
+
+gboolean
+test_utils_html_equal (TestFixture *fixture,
+                      const gchar *html1,
+                      const gchar *html2)
+{
+       EContentEditor *cnt_editor;
+       GDBusProxy *web_extension = NULL;
+       GVariant *result;
+       GError *error = NULL;
+       gboolean html_equal = FALSE;
+
+       g_return_val_if_fail (fixture != NULL, FALSE);
+       g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
+       g_return_val_if_fail (html1 != NULL, FALSE);
+       g_return_val_if_fail (html2 != NULL, FALSE);
+
+       cnt_editor = e_html_editor_get_content_editor (fixture->editor);
+       g_return_val_if_fail (cnt_editor != NULL, FALSE);
+
+       g_object_get (cnt_editor, "web-extension", &web_extension, NULL);
+
+       g_return_val_if_fail (G_IS_DBUS_PROXY (web_extension), FALSE);
+
+       result = g_dbus_proxy_call_sync (
+               web_extension,
+               "TestHtmlEqual",
+               g_variant_new ("(tss)", webkit_web_view_get_page_id (WEBKIT_WEB_VIEW (cnt_editor)), html1, 
html2),
+               G_DBUS_CALL_FLAGS_NONE,
+               -1,
+               NULL,
+               &error);
+       g_assert_no_error (error);
+
+       g_clear_error (&error);
+
+       g_return_val_if_fail (result != NULL, FALSE);
+
+       g_variant_get (result, "(b)", &html_equal);
+       g_variant_unref (result);
+
+       return html_equal;
+}
+
+static gboolean
+test_utils_process_sequence (TestFixture *fixture,
+                            const gchar *sequence)
+{
+       GtkWidget *widget;
+       GdkKeymap *keymap;
+       GdkEvent event;
+       const gchar *seq;
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (fixture != NULL, FALSE);
+       g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
+       g_return_val_if_fail (sequence != NULL, FALSE);
+
+       widget = GTK_WIDGET (e_html_editor_get_content_editor (fixture->editor));
+       g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+
+       keymap = gdk_keymap_get_for_display (gtk_widget_get_display (widget));
+
+       event.key.window = gtk_widget_get_window (widget);
+       event.key.send_event = FALSE;
+       event.key.state = 0;
+       event.key.length = 0;
+       event.key.string = NULL;
+
+       for (seq = sequence; *seq && success; seq++) {
+               gboolean call_press = TRUE, call_release = TRUE;
+               guint32 change_state = event.key.state;
+
+               event.key.is_modifier = FALSE;
+
+               switch (*seq) {
+               case 'S': /* Shift key press */
+                       event.key.keyval = GDK_KEY_Shift_L;
+                       event.key.is_modifier = TRUE;
+
+                       if ((event.key.state & GDK_SHIFT_MASK) != 0) {
+                               success = FALSE;
+                               g_warning ("%s: Shift is already pressed", G_STRFUNC);
+                       } else {
+                               change_state |= GDK_SHIFT_MASK;
+                       }
+                       call_release = FALSE;
+                       break;
+               case 's': /* Shift key release */
+                       event.key.keyval = GDK_KEY_Shift_L;
+                       event.key.is_modifier = TRUE;
+
+                       if ((event.key.state & GDK_SHIFT_MASK) == 0) {
+                               success = FALSE;
+                               g_warning ("%s: Shift is already released", G_STRFUNC);
+                       } else {
+                               change_state &= ~GDK_SHIFT_MASK;
+                       }
+                       call_press = FALSE;
+                       break;
+               case 'C': /* Ctrl key press */
+                       event.key.keyval = GDK_KEY_Control_L;
+                       event.key.is_modifier = TRUE;
+
+                       if ((event.key.state & GDK_CONTROL_MASK) != 0) {
+                               success = FALSE;
+                               g_warning ("%s: Control is already pressed", G_STRFUNC);
+                       } else {
+                               change_state |= GDK_CONTROL_MASK;
+                       }
+                       call_release = FALSE;
+                       break;
+               case 'c': /* Ctrl key release */
+                       event.key.keyval = GDK_KEY_Control_L;
+                       event.key.is_modifier = TRUE;
+
+                       if ((event.key.state & GDK_CONTROL_MASK) == 0) {
+                               success = FALSE;
+                               g_warning ("%s: Control is already released", G_STRFUNC);
+                       } else {
+                               change_state &= ~GDK_CONTROL_MASK;
+                       }
+                       call_press = FALSE;
+                       break;
+               case 'h': /* Home key press + release */
+                       event.key.keyval = GDK_KEY_Home;
+                       break;
+               case 'e': /* End key press + release */
+                       event.key.keyval = GDK_KEY_End;
+                       break;
+               case 'P': /* Page-Up key press + release */
+                       event.key.keyval = GDK_KEY_Page_Up;
+                       break;
+               case 'p': /* Page-Down key press + release */
+                       event.key.keyval = GDK_KEY_Page_Down;
+                       break;
+               case 'l': /* Arrow-Left key press + release */
+                       event.key.keyval = GDK_KEY_Left;
+                       break;
+               case 'r': /* Arrow-Right key press + release */
+                       event.key.keyval = GDK_KEY_Right;
+                       break;
+               case 'u': /* Arrow-Up key press + release */
+                       event.key.keyval = GDK_KEY_Up;
+                       break;
+               case 'd': /* Arrow-Down key press + release */
+                       event.key.keyval = GDK_KEY_Down;
+                       break;
+               case 'D': /* Delete key press + release */
+                       event.key.keyval = GDK_KEY_Delete;
+                       break;
+               case 'b': /* Backspace key press + release */
+                       event.key.keyval = GDK_KEY_BackSpace;
+                       break;
+               case 't': /* Tab key press + release */
+                       event.key.keyval = GDK_KEY_Tab;
+                       break;
+               case 'n': /* Return key press + release */
+                       event.key.keyval = GDK_KEY_Return;
+                       break;
+               default:
+                       success = FALSE;
+                       g_warning ("%s: Unknown sequence command '%c' in sequence '%s'", G_STRFUNC, *seq, 
sequence);
+                       break;
+               }
+
+               if (success && (call_press || call_release)) {
+                       GdkKeymapKey *keys = NULL;
+                       gint n_keys;
+
+                       if (gdk_keymap_get_entries_for_keyval (keymap, event.key.keyval, &keys, &n_keys) && 
n_keys > 0) {
+                               GdkEvent *nevent;
+
+                               nevent = gdk_event_new (GDK_KEY_PRESS);
+                               nevent->key.keyval = event.key.keyval;
+                               nevent->key.state = event.key.state;
+                               nevent->key.window = g_object_ref (event.key.window);
+                               nevent->key.send_event = event.key.send_event;
+                               nevent->key.length = event.key.length;
+                               nevent->key.string = event.key.string;
+                               nevent->key.is_modifier = event.key.is_modifier;
+                               gdk_event_set_device (nevent, gdk_seat_get_keyboard 
(gdk_display_get_default_seat (gtk_widget_get_display (widget))));
+                               nevent->key.hardware_keycode = keys[0].keycode;
+                               nevent->key.group = keys[0].group;
+
+                               if (call_press) {
+                                       nevent->key.type = GDK_KEY_PRESS;
+                                       nevent->key.time = GDK_CURRENT_TIME;
+                                       gtk_main_do_event (nevent);
+
+                                       test_utils_wait_milliseconds (5);
+                               }
+
+                               if (call_release) {
+                                       nevent->key.type = GDK_KEY_RELEASE;
+                                       nevent->key.time = GDK_CURRENT_TIME;
+                                       gtk_main_do_event (nevent);
+
+                                       test_utils_wait_milliseconds (5);
+                               }
+
+                               gdk_event_free (nevent);
+                       } else {
+                               success = FALSE;
+                               g_warning ("%s: Failed to get keymap entries for sequence command '%c'", 
G_STRFUNC, *seq);
+                       }
+
+                       g_free (keys);
+               }
+
+               event.key.state = change_state;
+       }
+
+       test_utils_wait_milliseconds (5);
+
+       return success;
+}
+
+static gboolean
+test_utils_execute_action (TestFixture *fixture,
+                          const gchar *action_name)
+{
+       GtkAction *action;
+
+       g_return_val_if_fail (fixture != NULL, FALSE);
+       g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
+       g_return_val_if_fail (action_name != NULL, FALSE);
+
+       action = e_html_editor_get_action (fixture->editor, action_name);
+       if (action) {
+               gtk_action_activate (action);
+       } else {
+               g_warning ("%s: Failed to find action '%s'", G_STRFUNC, action_name);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+/* Expects only the part like "undo" [ ":" number ] */
+static gint
+test_utils_maybe_extract_undo_number (const gchar *command)
+{
+       const gchar *ptr;
+       gint number;
+
+       g_return_val_if_fail (command != NULL, -1);
+
+       ptr = strchr (command, ':');
+       if (!ptr)
+               return 1;
+
+       number = atoi (ptr + 1);
+       g_return_val_if_fail (number > 0, -1);
+
+       return number;
+}
+
+static const UndoContent *
+test_utils_pick_undo_content (const GSList *undo_stack,
+                             gint number)
+{
+       const GSList *link;
+
+       g_return_val_if_fail (undo_stack != NULL, NULL);
+
+       for (link = undo_stack; link && number > 0; link = g_slist_next (link)) {
+               number--;
+       }
+
+       g_return_val_if_fail (link != NULL, NULL);
+       g_return_val_if_fail (link->data != NULL, NULL);
+
+       return link->data;
+}
+
+/* Each line of 'commands' contains one command.
+
+   commands  = command *("\n" command)
+
+   command   = actioncmd ; Execute an action
+             / modecmd   ; Change editor mode to HTML or Plain Text
+             / seqcmd    ; Sequence of special key strokes
+             / typecmd   ; Type a text
+             / undocmd   ; Undo/redo commands
+
+   actioncmd = "action:" name
+
+   actioncmd = "mode:" ("html" / "plain")
+
+   seqcmd    = "seq:" sequence
+
+   sequence  = "S" ; Shift key press
+             / "s" ; Shift key release
+             / "C" ; Ctrl key press
+             / "c" ; Ctrl key release
+             / "h" ; Home key press + release
+             / "e" ; End key press + release
+             / "P" ; Page-Up key press + release
+             / "p" ; Page-Down key press + release
+             / "l" ; Arrow-Left key press + release
+             / "r" ; Arrow-Right key press + release
+             / "u" ; Arrow-Up key press + release
+             / "d" ; Arrow-Down key press + release
+             / "D" ; Delete key press + release
+             / "b" ; Backspace key press + release
+             / "t" ; Tab key press + release
+             / "n" ; Return key press + release
+
+   typecmd   = "type:" text ; the 'text' can contain escaped letters with a backslash, like "\\n" transforms 
into "\n"
+
+   undocmd   = "undo:" undotype
+
+   undotype  = "undo" [ ":" number ] ; Call 'undo', number-times; if 'number' is not provided, then call it 
exactly once
+             / "redo" [ ":" number ] ; Call 'redo', number-times; if 'number' is not provided, then call it 
exactly once
+            / "save"                ; Save current content of the editor for later tests
+            / "drop" [ ":" number ] ; Forgets saved content, if 'number' is provided, then top number saves 
are forgotten
+            / "test" [ ":" number ] ; Tests current editor content against any previously saved state; the 
optional
+                                     ; 'number' argument can be used to specify which exact previous state 
to use
+ */
+gboolean
+test_utils_process_commands (TestFixture *fixture,
+                            const gchar *commands)
+{
+       gchar **cmds;
+       gint cc;
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (fixture != NULL, FALSE);
+       g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
+       g_return_val_if_fail (commands != NULL, FALSE);
+
+       cmds = g_strsplit (commands, "\n", -1);
+       for (cc = 0; cmds && cmds[cc] && success; cc++) {
+               const gchar *command = cmds[cc];
+
+               if (g_str_has_prefix (command, "action:")) {
+                       test_utils_execute_action (fixture, command + 7);
+               } else if (g_str_has_prefix (command, "mode:")) {
+                       const gchar *mode_change = command + 5;
+
+                       if (g_str_equal (mode_change, "html")) {
+                               test_utils_execute_action (fixture, "mode-html");
+                       } else if (g_str_equal (mode_change, "plain")) {
+                               test_utils_execute_action (fixture, "mode-plain");
+                       } else {
+                               success = FALSE;
+                               g_warning ("%s: Unknown mode '%s'", G_STRFUNC, mode_change);
+                       }
+               } else if (g_str_has_prefix (command, "seq:")) {
+                       success = test_utils_process_sequence (fixture, command + 4);
+               } else if (g_str_has_prefix (command, "type:")) {
+                       gchar *text;
+
+                       text = g_strcompress (command + 5);
+                       success = test_utils_type_text (fixture, text);
+                       if (!success)
+                               g_warning ("%s: Failed to type text '%s'", G_STRFUNC, text);
+                       g_free (text);
+               } else if (g_str_has_prefix (command, "undo:")) {
+                       gint number;
+
+                       command += 5;
+
+                       if (g_str_equal (command, "undo") || g_str_has_prefix (command, "undo:")) {
+                               number = test_utils_maybe_extract_undo_number (command);
+                               while (number > 0 && success) {
+                                       success = test_utils_execute_action (fixture, "undo");
+                                       number--;
+                               }
+                       } else if (g_str_has_prefix (command, "redo") || g_str_has_prefix (command, "redo:")) 
{
+                               number = test_utils_maybe_extract_undo_number (command);
+                               while (number > 0 && success) {
+                                       success = test_utils_execute_action (fixture, "redo");
+                                       number--;
+                               }
+                       } else if (g_str_equal (command, "save")) {
+                               UndoContent *uc;
+
+                               uc = undo_content_new (fixture);
+                               fixture->undo_stack = g_slist_prepend (fixture->undo_stack, uc);
+                       } else if (g_str_equal (command, "drop") || g_str_has_prefix (command, "drop:")) {
+                               number = test_utils_maybe_extract_undo_number (command);
+                               g_warn_if_fail (number <= g_slist_length (fixture->undo_stack));
+
+                               while (number > 0 && fixture->undo_stack) {
+                                       UndoContent *uc = fixture->undo_stack->data;
+
+                                       fixture->undo_stack = g_slist_remove (fixture->undo_stack, uc);
+                                       undo_content_free (uc);
+                                       number--;
+                               }
+                       } else if (g_str_equal (command, "test") || g_str_has_prefix (command, "test:")) {
+                               const UndoContent *uc;
+
+                               number = test_utils_maybe_extract_undo_number (command);
+                               uc = test_utils_pick_undo_content (fixture->undo_stack, number);
+                               success = uc && undo_content_test (fixture, uc);
+                       } else {
+                               g_warning ("%s: Unknown command 'undo:%s'", G_STRFUNC, command);
+                               success = FALSE;
+                       }
+
+                       test_utils_wait_milliseconds (500);
+               } else if (*command) {
+                       g_warning ("%s: Unknown command '%s'", G_STRFUNC, command);
+                       success = FALSE;
+               }
+
+               test_utils_wait_milliseconds (5);
+       }
+
+       g_strfreev (cmds);
+
+       if (success) {
+               /* Give the editor some time to finish any ongoing async operations */
+               test_utils_wait_milliseconds (100);
+       }
+
+       return success;
+}
+
+gboolean
+test_utils_run_simple_test (TestFixture *fixture,
+                           const gchar *commands,
+                           const gchar *expected_html,
+                           const gchar *expected_plain)
+{
+       EContentEditor *cnt_editor;
+       gchar *text;
+
+       g_return_val_if_fail (fixture != NULL, FALSE);
+       g_return_val_if_fail (E_IS_HTML_EDITOR (fixture->editor), FALSE);
+       g_return_val_if_fail (commands != NULL, FALSE);
+
+       cnt_editor = e_html_editor_get_content_editor (fixture->editor);
+
+       if (!test_utils_process_commands (fixture, commands))
+               return FALSE;
+
+       if (expected_html) {
+               text = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_HTML, NULL);
+               g_return_val_if_fail (text != NULL, FALSE);
+
+               if (!test_utils_html_equal (fixture, text, expected_html)) {
+                       g_warning ("%s: returned HTML\n---%s---\n and expected HTML\n---%s---\n do not 
match", G_STRFUNC, text, expected_html);
+                       g_free (text);
+                       return FALSE;
+               }
+
+               g_free (text);
+       }
+
+       if (expected_plain) {
+               text = e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_PROCESSED | 
E_CONTENT_EDITOR_GET_TEXT_PLAIN, NULL);
+               g_return_val_if_fail (text != NULL, FALSE);
+
+               if (!test_utils_html_equal (fixture, text, expected_plain)) {
+                       g_warning ("%s: returned Plain\n---%s---\n and expected Plain\n---%s---\n do not 
match", G_STRFUNC, text, expected_plain);
+                       g_free (text);
+                       return FALSE;
+               }
+
+               g_free (text);
+       }
+
+       return TRUE;
+}
diff --git a/e-util/test-html-editor-units-utils.h b/e-util/test-html-editor-units-utils.h
new file mode 100644
index 0000000..dc1083d
--- /dev/null
+++ b/e-util/test-html-editor-units-utils.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEST_HTML_EDITOR_UNITS_UTILS_H
+#define TEST_HTML_EDITOR_UNITS_UTILS_H
+
+#include <glib.h>
+#include <e-util/e-util.h>
+
+typedef struct _TestFixture {
+       GtkWidget *window;
+       EHTMLEditor *editor;
+       gboolean prompt_on_composer_mode_switch;
+
+       GSList *undo_stack; /* UndoContent * */
+} TestFixture;
+
+void           test_utils_fixture_set_up       (TestFixture *fixture,
+                                                gconstpointer user_data);
+void           test_utils_fixture_tear_down    (TestFixture *fixture,
+                                                gconstpointer user_data);
+gpointer       test_utils_async_call_prepare   (void);
+gboolean       test_utils_async_call_wait      (gpointer async_data,
+                                                guint timeout_seconds);
+gboolean       test_utils_async_call_finish    (gpointer async_data);
+gboolean       test_utils_wait_milliseconds    (guint milliseconds);
+
+gboolean       test_utils_type_text            (TestFixture *fixture,
+                                                const gchar *text);
+gboolean       test_utils_html_equal           (TestFixture *fixture,
+                                                const gchar *html1,
+                                                const gchar *html2);
+gboolean       test_utils_process_commands     (TestFixture *fixture,
+                                                const gchar *commands);
+gboolean       test_utils_run_simple_test      (TestFixture *fixture,
+                                                const gchar *commands,
+                                                const gchar *expected_html,
+                                                const gchar *expected_plain);
+
+#endif /* TEST_HTML_EDITOR_UNITS_UTILS_H */
diff --git a/e-util/test-html-editor-units.c b/e-util/test-html-editor-units.c
new file mode 100644
index 0000000..070c63d
--- /dev/null
+++ b/e-util/test-html-editor-units.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <locale.h>
+#include <e-util/e-util.h>
+
+#include "e-html-editor-private.h"
+#include "test-html-editor-units-utils.h"
+
+#define HTML_PREFIX "<html><head></head><body><p data-evo-paragraph=\"\">"
+#define HTML_SUFFIX "</p></body></html>"
+
+/* The tests do not use the 'user_data' argument, thus the functions avoid them and the typecast is needed. 
*/
+typedef void (* ETestFixtureFunc) (TestFixture *fixture, gconstpointer user_data);
+
+static void
+test_create_editor (TestFixture *fixture)
+{
+       g_assert (fixture->editor != NULL);
+       g_assert_cmpstr (e_html_editor_get_content_editor_name (fixture->editor), ==, 
DEFAULT_CONTENT_EDITOR_NAME);
+
+       /* test of the test function */
+       g_assert (test_utils_html_equal (fixture, "<span>a</span>", "<sPaN>a</spaN>"));
+       g_assert (!test_utils_html_equal (fixture, "<span>A</span>", "<sPaN>a</spaN>"));
+}
+
+static void
+test_style_bold_selection (TestFixture *fixture)
+{
+       if (!test_utils_run_simple_test (fixture,
+               "mode:html\n"
+               "type:some bold text\n"
+               "seq:hCrcrCSrsc\n"
+               "action:bold\n",
+               HTML_PREFIX "some <b>bold</b> text" HTML_SUFFIX,
+               "some bold text"))
+               g_test_fail ();
+}
+
+static void
+test_style_bold_typed (TestFixture *fixture)
+{
+       if (!test_utils_run_simple_test (fixture,
+               "mode:html\n"
+               "type:some \n"
+               "action:bold\n"
+               "type:bold\n"
+               "action:bold\n"
+               "type: text\n",
+               HTML_PREFIX "some <b>bold</b> text" HTML_SUFFIX,
+               "some bold text"))
+               g_test_fail ();
+}
+
+static void
+test_style_italic_selection (TestFixture *fixture)
+{
+       if (!test_utils_run_simple_test (fixture,
+               "mode:html\n"
+               "type:some italic text\n"
+               "seq:hCrcrCSrsc\n"
+               "action:italic\n",
+               HTML_PREFIX "some <i>italic</i> text" HTML_SUFFIX,
+               "some italic text"))
+               g_test_fail ();
+}
+
+static void
+test_style_italic_typed (TestFixture *fixture)
+{
+       if (!test_utils_run_simple_test (fixture,
+               "mode:html\n"
+               "type:some \n"
+               "action:italic\n"
+               "type:italic\n"
+               "action:italic\n"
+               "type: text\n",
+               HTML_PREFIX "some <i>italic</i> text" HTML_SUFFIX,
+               "some italic text"))
+               g_test_fail ();
+}
+
+static void
+test_style_underline_selection (TestFixture *fixture)
+{
+       if (!test_utils_run_simple_test (fixture,
+               "mode:html\n"
+               "type:some underline text\n"
+               "seq:hCrcrCSrsc\n"
+               "action:underline\n",
+               HTML_PREFIX "some <u>underline</u> text" HTML_SUFFIX,
+               "some underline text"))
+               g_test_fail ();
+}
+
+static void
+test_style_underline_typed (TestFixture *fixture)
+{
+       if (!test_utils_run_simple_test (fixture,
+               "mode:html\n"
+               "type:some \n"
+               "action:underline\n"
+               "type:underline\n"
+               "action:underline\n"
+               "type: text\n",
+               HTML_PREFIX "some <u>underline</u> text" HTML_SUFFIX,
+               "some underline text"))
+               g_test_fail ();
+}
+
+static void
+test_style_monospace_selection (TestFixture *fixture)
+{
+       if (!test_utils_run_simple_test (fixture,
+               "mode:html\n"
+               "type:some monospace text\n"
+               "seq:hCrcrCSrsc\n"
+               "action:monospaced\n",
+               HTML_PREFIX "some <font face=\"monospace\" size=\"3\">monospace</font> text" HTML_SUFFIX,
+               "some monospace text"))
+               g_test_fail ();
+}
+
+static void
+test_style_monospace_typed (TestFixture *fixture)
+{
+       if (!test_utils_run_simple_test (fixture,
+               "mode:html\n"
+               "type:some \n"
+               "action:monospaced\n"
+               "type:monospace\n"
+               "action:monospaced\n"
+               "type: text\n",
+               HTML_PREFIX "some <font face=\"monospace\" size=\"3\">monospace</font> text" HTML_SUFFIX,
+               "some monospace text"))
+               g_test_fail ();
+}
+
+static void
+test_undo_text_type (TestFixture *fixture)
+{
+       if (!test_utils_run_simple_test (fixture,
+               "mode:html\n"
+               "undo:save\n"   /* 1 */
+               "type:some text corretC\n"
+               "undo:save\n"   /* 2 */
+               "seq:CSlcsD\n"  /* delete the last word */
+               "undo:save\n"   /* 3 */
+               "type:broken\n" /* and write 'broken' there */
+               "undo:save\n"   /* 4 */
+               "undo:undo:2\n" /* undo the 'broken' word write */
+               "undo:test:2\n"
+               "undo:redo\n" /* redo 'broken' word write */
+               "undo:test\n"
+               "undo:undo\n" /* undo 'broken' word write */
+               "undo:test:2\n"
+               "undo:redo\n" /* redo 'broken' word write */
+               "undo:test\n"
+               "undo:drop:2\n" /* 2 */
+               "undo:undo:2\n" /* undo 'broken' word write and word delete*/
+               "undo:test\n"
+               "undo:undo:3\n" /* undo text typing */
+               "undo:test\n"
+               "undo:drop\n",  /* 1 */
+               HTML_PREFIX "" HTML_SUFFIX,
+               ""))
+               g_test_fail ();
+}
+
+gint
+main (gint argc,
+      gchar *argv[])
+{
+       GList *modules;
+       gint res;
+
+       setlocale (LC_ALL, "");
+
+       g_test_init (&argc, &argv, NULL);
+       g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=";);
+
+       gtk_init (&argc, &argv);
+
+       e_util_init_main_thread (NULL);
+       e_passwords_init ();
+
+       modules = e_module_load_all_in_directory (EVOLUTION_MODULEDIR);
+       g_list_free_full (modules, (GDestroyNotify) g_type_module_unuse);
+
+       #define add_test(_name, _func)  \
+               g_test_add (_name, TestFixture, NULL, \
+                       test_utils_fixture_set_up, (ETestFixtureFunc) _func, test_utils_fixture_tear_down)
+
+       add_test ("/create/editor", test_create_editor);
+       add_test ("/style/bold-selection", test_style_bold_selection);
+       add_test ("/style/bold-typed", test_style_bold_typed);
+       add_test ("/style/italic-selection", test_style_italic_selection);
+       add_test ("/style/italic-typed", test_style_italic_typed);
+       add_test ("/style/underline-selection", test_style_underline_selection);
+       add_test ("/style/underline-typed", test_style_underline_typed);
+       add_test ("/style/monospace-selection", test_style_monospace_selection);
+       add_test ("/style/monospace-typed", test_style_monospace_typed);
+       add_test ("/undo/text-type", test_undo_text_type);
+
+       #undef add_test
+
+       res = g_test_run ();
+
+       e_util_cleanup_settings ();
+       e_spell_checker_free_global_memory ();
+
+       return res;
+}
diff --git a/modules/webkit-editor/e-webkit-editor.c b/modules/webkit-editor/e-webkit-editor.c
index 99ae870..c1eb292 100644
--- a/modules/webkit-editor/e-webkit-editor.c
+++ b/modules/webkit-editor/e-webkit-editor.c
@@ -36,6 +36,7 @@
 
 enum {
        PROP_0,
+       PROP_WEB_EXTENSION, /* for test purposes */
        PROP_CAN_COPY,
        PROP_CAN_CUT,
        PROP_CAN_PASTE,
@@ -447,6 +448,8 @@ web_extension_proxy_created_cb (GDBusProxy *proxy,
 
                wk_editor->priv->emit_load_finished_when_extension_is_ready = FALSE;
        }
+
+       g_object_notify (G_OBJECT (wk_editor), "web-extension");
 }
 
 static void
@@ -4874,6 +4877,14 @@ webkit_editor_on_find_dialog_close (EContentEditor *editor)
        webkit_editor_finish_search (E_WEBKIT_EDITOR (editor));
 }
 
+static GDBusProxy *
+webkit_editor_get_web_extension (EWebKitEditor *editor)
+{
+       g_return_val_if_fail (E_IS_WEBKIT_EDITOR (editor), NULL);
+
+       return editor->priv->web_extension;
+}
+
 static void
 webkit_editor_constructed (GObject *object)
 {
@@ -5189,6 +5200,12 @@ webkit_editor_get_property (GObject *object,
                             GParamSpec *pspec)
 {
        switch (property_id) {
+               case PROP_WEB_EXTENSION:
+                       g_value_set_object (
+                               value, webkit_editor_get_web_extension (
+                               E_WEBKIT_EDITOR (object)));
+                       return;
+
                case PROP_CAN_COPY:
                        g_value_set_boolean (
                                value, webkit_editor_can_copy (
@@ -5443,7 +5460,8 @@ webkit_editor_primary_clipboard_owner_change_cb (GtkClipboard *clipboard,
                                                  GdkEventOwnerChange *event,
                                                  EWebKitEditor *wk_editor)
 {
-       if (!E_IS_WEBKIT_EDITOR (wk_editor))
+       if (!E_IS_WEBKIT_EDITOR (wk_editor) ||
+           !wk_editor->priv->web_extension)
                return;
 
        if (!event->owner || !wk_editor->priv->can_copy)
@@ -5738,6 +5756,17 @@ e_webkit_editor_class_init (EWebKitEditorClass *class)
        widget_class->button_press_event = webkit_editor_button_press_event;
        widget_class->key_press_event = webkit_editor_key_press_event;
 
+       g_object_class_install_property (
+               object_class,
+               PROP_WEB_EXTENSION,
+               g_param_spec_object (
+                       "web-extension",
+                       "Web Extension",
+                       "The Web Extension to use to talk to the WebProcess",
+                       G_TYPE_DBUS_PROXY,
+                       G_PARAM_READABLE |
+                       G_PARAM_STATIC_STRINGS));
+
        g_object_class_override_property (
                object_class, PROP_CAN_COPY, "can-copy");
        g_object_class_override_property (
diff --git a/modules/webkit-editor/web-extension/Makefile.am b/modules/webkit-editor/web-extension/Makefile.am
index 159891d..340392b 100644
--- a/modules/webkit-editor/web-extension/Makefile.am
+++ b/modules/webkit-editor/web-extension/Makefile.am
@@ -12,6 +12,7 @@ libewebkiteditorwebextension_la_SOURCES =     \
        e-html-editor-selection-dom-functions.h         \
        e-html-editor-spell-check-dialog-dom-functions.h\
        e-html-editor-table-dialog-dom-functions.h      \
+       e-html-editor-test-dom-functions.h              \
        e-html-editor-undo-redo-manager.h               \
        e-html-editor-view-dom-functions.h              \
        e-msg-composer-dom-functions.h                  \
@@ -25,6 +26,7 @@ libewebkiteditorwebextension_la_SOURCES =     \
        e-html-editor-selection-dom-functions.c         \
        e-html-editor-spell-check-dialog-dom-functions.c\
        e-html-editor-table-dialog-dom-functions.c      \
+       e-html-editor-test-dom-functions.c              \
        e-html-editor-undo-redo-manager.c               \
        e-html-editor-view-dom-functions.c              \
        e-msg-composer-dom-functions.c                  \
diff --git a/modules/webkit-editor/web-extension/e-html-editor-test-dom-functions.c 
b/modules/webkit-editor/web-extension/e-html-editor-test-dom-functions.c
new file mode 100644
index 0000000..2b231e2
--- /dev/null
+++ b/modules/webkit-editor/web-extension/e-html-editor-test-dom-functions.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-test-dom-functions.h"
+
+gboolean
+dom_test_html_equal (WebKitDOMDocument *document,
+                    const gchar *html1,
+                    const gchar *html2)
+{
+       WebKitDOMElement *elem1, *elem2;
+       gboolean res = FALSE;
+       GError *error = NULL;
+
+       g_return_val_if_fail (WEBKIT_DOM_IS_DOCUMENT (document), FALSE);
+       g_return_val_if_fail (html1 != NULL, FALSE);
+       g_return_val_if_fail (html2 != NULL, FALSE);
+
+       elem1 = webkit_dom_document_create_element (document, "TestHtmlEqual", &error);
+       if (error || !elem1) {
+               g_warning ("%s: Failed to create elem1: %s", G_STRFUNC, error ? error->message : "Unknown 
error");
+               g_clear_error (&error);
+               return FALSE;
+       }
+
+       elem2 = webkit_dom_document_create_element (document, "TestHtmlEqual", &error);
+       if (error || !elem2) {
+               g_warning ("%s: Failed to create elem2: %s", G_STRFUNC, error ? error->message : "Unknown 
error");
+               g_clear_error (&error);
+               return FALSE;
+       }
+
+       webkit_dom_element_set_inner_html (elem1, html1, &error);
+       if (!error) {
+               webkit_dom_element_set_inner_html (elem2, html2, &error);
+
+               if (!error) {
+                       webkit_dom_node_normalize (WEBKIT_DOM_NODE (elem1));
+                       webkit_dom_node_normalize (WEBKIT_DOM_NODE (elem2));
+
+                       res = webkit_dom_node_is_equal_node (WEBKIT_DOM_NODE (elem1), WEBKIT_DOM_NODE 
(elem2));
+               } else {
+                       g_warning ("%s: Failed to set inner html2: %s", G_STRFUNC, error->message);
+               }
+       } else {
+               g_warning ("%s: Failed to set inner html1: %s", G_STRFUNC, error->message);
+       }
+
+       g_clear_error (&error);
+
+       return res;
+}
diff --git a/modules/webkit-editor/web-extension/e-html-editor-test-dom-functions.h 
b/modules/webkit-editor/web-extension/e-html-editor-test-dom-functions.h
new file mode 100644
index 0000000..3c6b273
--- /dev/null
+++ b/modules/webkit-editor/web-extension/e-html-editor-test-dom-functions.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef E_HTML_EDITOR_TEST_DOM_FUNCTIONS_H
+#define E_HTML_EDITOR_TEST_DOM_FUNCTIONS_H
+
+#include <webkitdom/webkitdom.h>
+
+gboolean       dom_test_html_equal             (WebKitDOMDocument *document,
+                                                const gchar *html1,
+                                                const gchar *html2);
+
+#endif /* E_HTML_EDITOR_TEST_DOM_FUNCTIONS_H */
diff --git a/modules/webkit-editor/web-extension/e-html-editor-web-extension.c 
b/modules/webkit-editor/web-extension/e-html-editor-web-extension.c
index c12e917..6596e0d 100644
--- a/modules/webkit-editor/web-extension/e-html-editor-web-extension.c
+++ b/modules/webkit-editor/web-extension/e-html-editor-web-extension.c
@@ -40,6 +40,7 @@
 #include "e-html-editor-selection-dom-functions.h"
 #include "e-html-editor-spell-check-dialog-dom-functions.h"
 #include "e-html-editor-table-dialog-dom-functions.h"
+#include "e-html-editor-test-dom-functions.h"
 #include "e-html-editor-view-dom-functions.h"
 #include "e-msg-composer-dom-functions.h"
 
@@ -129,6 +130,15 @@ static const char introspection_xml[] =
 "<!--                          METHODS                          -->"
 "<!-- ********************************************************* -->"
 "<!-- ********************************************************* -->"
+"<!--                       FOR TESTING ONLY                    -->"
+"<!-- ********************************************************* -->"
+"    <method name='TestHtmlEqual'>"
+"      <arg type='t' name='page_id' direction='in'/>"
+"      <arg type='s' name='html1' direction='in'/>"
+"      <arg type='s' name='html2' direction='in'/>"
+"      <arg type='b' name='equal' direction='out'/>"
+"    </method>"
+"<!-- ********************************************************* -->"
 "<!--                          GENERIC                          -->"
 "<!-- ********************************************************* -->"
 "    <method name='ElementHasAttribute'>"
@@ -675,7 +685,22 @@ handle_method_call (GDBusConnection *connection,
        if (g_strcmp0 (interface_name, E_HTML_EDITOR_WEB_EXTENSION_INTERFACE) != 0)
                return;
 
-       if (g_strcmp0 (method_name, "ElementHasAttribute") == 0) {
+       if (g_strcmp0 (method_name, "TestHtmlEqual") == 0) {
+               gboolean equal = FALSE;
+               const gchar *html1 = NULL, *html2 = NULL;
+
+               g_variant_get (parameters, "(t&s&s)", &page_id, &html1, &html2);
+
+               web_page = get_webkit_web_page_or_return_dbus_error (
+                       invocation, web_extension, page_id);
+               if (!web_page)
+                       goto error;
+
+               document = webkit_web_page_get_dom_document (web_page);
+               equal = dom_test_html_equal (document, html1, html2);
+
+               g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", equal));
+       } else if (g_strcmp0 (method_name, "ElementHasAttribute") == 0) {
                gboolean value = FALSE;
                const gchar *element_id, *attribute;
                WebKitDOMElement *element;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]