[geary/mjog/composer-context-loading-fixes: 10/14] Composer.ApplicationInterface: New composer app interface




commit 37d904b5757031226fcec730884ed847fe103eba
Author: Michael Gratton <mike vee net>
Date:   Mon Aug 10 18:13:09 2020 +1000

    Composer.ApplicationInterface: New composer app interface
    
    Defining an interface for the composer to access application objects
    and services decouples it from Application.Controller, allowing it to be more
    easily unit tested.
    
    Replace use of Application.Client and Application.Controller in the
    composer and add some basic unit tests.

 src/client/application/application-controller.vala |  16 +-
 .../composer/composer-application-interface.vala   |  28 +++
 src/client/composer/composer-widget.vala           | 184 ++++++++++---------
 src/client/meson.build                             |   1 +
 test/client/composer/composer-widget-test.vala     | 203 +++++++++++++++++++++
 test/meson.build                                   |   1 +
 test/test-client.vala                              |   1 +
 7 files changed, 342 insertions(+), 92 deletions(-)
---
diff --git a/src/client/application/application-controller.vala 
b/src/client/application/application-controller.vala
index 9d040b50d..a1e36b412 100644
--- a/src/client/application/application-controller.vala
+++ b/src/client/application/application-controller.vala
@@ -14,7 +14,7 @@
  * when the primary application instance is started.
  */
 internal class Application.Controller :
-    Geary.BaseObject, AccountInterface {
+    Geary.BaseObject, AccountInterface, Composer.ApplicationInterface {
 
 
     private const uint MAX_AUTH_ATTEMPTS = 3;
@@ -331,9 +331,9 @@ internal class Application.Controller :
         Composer.Widget? composer = null;
         if (context != null) {
             composer = new Composer.Widget(
-                this.application,
+                this,
+                this.application.config,
                 context,
-                this.accounts.values.read_only_view,
                 save_to
             );
             register_composer(composer);
@@ -408,9 +408,9 @@ internal class Application.Controller :
         Composer.Widget? composer = null;
         if (account != null) {
             composer = new Composer.Widget(
-                this.application,
+                this,
+                this.application.config,
                 account,
-                this.accounts.values.read_only_view,
                 save_to
             );
             register_composer(composer);
@@ -439,9 +439,9 @@ internal class Application.Controller :
             var context = this.accounts.get(window.selected_account.information);
             if (context != null) {
                 var composer = new Composer.Widget(
-                    this.application,
-                    context,
-                    this.accounts.values.read_only_view
+                    this,
+                    this.application.config,
+                    context
                 );
                 register_composer(composer);
                 show_composer(composer);
diff --git a/src/client/composer/composer-application-interface.vala 
b/src/client/composer/composer-application-interface.vala
new file mode 100644
index 000000000..542fbbedd
--- /dev/null
+++ b/src/client/composer/composer-application-interface.vala
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+
+/**
+ * Provides access to application objects and services to the composer.
+ *
+ * This interface defines the composer's requirements for integrating
+ * with the application and enables the composer to be unit tested
+ * without having to load the complete application.
+ */
+internal interface Composer.ApplicationInterface :
+    GLib.Object, Application.AccountInterface {
+
+
+    public abstract void report_problem(Geary.ProblemReport report);
+
+    internal abstract async void send_composed_email(Composer.Widget composer);
+
+    internal abstract async void save_composed_email(Composer.Widget composer);
+
+    internal abstract async void discard_composed_email(Composer.Widget composer);
+
+}
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index de2213857..8973992ab 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -299,44 +299,50 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
     }
     private bool _can_send = true;
 
-    internal WebView editor { get; private set; }
-
-    internal Headerbar header { get; private set; }
+    /** Currently selected sender mailbox. */
+    public Geary.RFC822.MailboxAddresses from { get; private set; }
 
-    internal bool has_multiple_from_addresses {
-        get {
-            return (
-                this.accounts.size > 1 ||
-                this.sender_context.account.information.has_sender_aliases
-            );
-        }
-    }
-
-    internal string subject {
-        get { return this.subject_entry.get_text(); }
-        private set { this.subject_entry.set_text(value); }
-    }
-
-    private Geary.RFC822.MailboxAddresses from { get; private set; }
-
-    private string to {
+    /** Current text of the `to` entry. */
+    public string to {
         get { return this.to_entry.get_text(); }
-        set { this.to_entry.set_text(value); }
+        private set { this.to_entry.set_text(value); }
     }
 
-    private string cc {
+    /** Current text of the `cc` entry. */
+    public string cc {
         get { return this.cc_entry.get_text(); }
-        set { this.cc_entry.set_text(value); }
+        private set { this.cc_entry.set_text(value); }
     }
 
-    private string bcc {
+    /** Current text of the `bcc` entry. */
+    public string bcc {
         get { return this.bcc_entry.get_text(); }
-        set { this.bcc_entry.set_text(value); }
+        private set { this.bcc_entry.set_text(value); }
     }
 
-    private string reply_to {
+    /** Current text of the `reply-to` entry. */
+    public string reply_to {
         get { return this.reply_to_entry.get_text(); }
-        set { this.reply_to_entry.set_text(value); }
+        private set { this.reply_to_entry.set_text(value); }
+    }
+
+    /** Current text of the `sender` entry. */
+    public string subject {
+        get { return this.subject_entry.get_text(); }
+        private set { this.subject_entry.set_text(value); }
+    }
+
+    internal WebView editor { get; private set; }
+
+    internal Headerbar header { get; private set; }
+
+    internal bool has_multiple_from_addresses {
+        get {
+            return (
+                this.application.get_account_contexts().size > 1 ||
+                this.sender_context.account.information.has_sender_aliases
+            );
+        }
     }
 
     private Gee.Set<Geary.RFC822.MessageID> in_reply_to = new Gee.HashSet<Geary.RFC822.MessageID>();
@@ -462,8 +468,6 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         }
     }
 
-    private Gee.Collection<Application.AccountContext> accounts;
-
     private string? pointer_url = null;
     private string? cursor_url = null;
     private bool is_attachment_overlay_visible = false;
@@ -509,7 +513,9 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         get { return this.parent as Container; }
     }
 
-    private Application.Client application;
+    private ApplicationInterface application;
+
+    private Application.Configuration config;
 
     // Timeout for showing the slow image paste pulsing bar
     private Geary.TimeoutManager show_background_work_timeout = null;
@@ -518,18 +524,18 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
     private Geary.TimeoutManager background_work_pulse;
 
 
-    public Widget(Application.Client application,
-                  Application.AccountContext initial_account,
-                  Gee.Collection<Application.AccountContext> all_accounts,
-                  Geary.Folder? save_to = null) {
+    internal Widget(ApplicationInterface application,
+                    Application.Configuration config,
+                    Application.AccountContext initial_account,
+                    Geary.Folder? save_to = null) {
         components_reflow_box_get_type();
         base_ref();
         this.application = application;
+        this.config = config;
         this.sender_context = initial_account;
-        this.accounts = all_accounts;
         this.save_to = save_to;
 
-        this.header = new Headerbar(application.config);
+        this.header = new Headerbar(config);
         this.header.expand_composer.connect(on_expand_compact_headers);
 
         // Setup drag 'n drop
@@ -576,7 +582,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         );
         update_subject_spell_checker();
 
-        this.editor = new WebView(application.config);
+        this.editor = new WebView(config);
         this.editor.set_hexpand(true);
         this.editor.set_vexpand(true);
         this.editor.content_loaded.connect(on_editor_content_loaded);
@@ -596,10 +602,10 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         this.context_menu_webkit_text_entry = (Menu) builder.get_object("context_menu_webkit_text_entry");
 
         // Listen to account signals to update from menu.
-        this.application.engine.account_available.connect(
+        this.application.account_available.connect(
             on_account_available
         );
-        this.application.engine.account_unavailable.connect(
+        this.application.account_unavailable.connect(
             on_account_unavailable
         );
 
@@ -653,7 +659,6 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         ((Gtk.CellRendererText) cells.data).ellipsize = END;
 
         // Create spellcheck popover
-        Application.Configuration config = this.application.config;
         var spell_check_popover = new SpellCheckPopover(
             this.select_dictionary_button, config
         );
@@ -783,17 +788,21 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
 
         var body = "";
         var complete_quote = "";
+        var body_complete = false;
         switch (type) {
         case EDIT:
             this.saved_id = full_context.id;
             yield restore_reply_to_state();
             fill_in_from_context(full_context);
             Geary.RFC822.Message message = full_context.get_message();
-            body = (
-                message.has_html_body()
-                ? message.get_html_body(null)
-                : message.get_plain_body(true, null)
-            );
+            if (message.has_html_body()) {
+                body = message.get_html_body(null);
+                body_complete = body.contains(
+                    """id="%s"""".printf(WebView.BODY_HTML_ID)
+                );
+            } else {
+                body = message.get_plain_body(true, null);
+            }
             break;
 
         case REPLY_SENDER:
@@ -801,7 +810,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
             add_recipients_and_ids(this.context_type, full_context);
             fill_in_from_context(full_context);
             complete_quote = Util.Email.quote_email_for_reply(
-                full_context, quote, this.application.config.clock_format, HTML
+                full_context, quote, this.config.clock_format, HTML
             );
             if (!Geary.String.is_empty(quote)) {
                 this.top_posting = false;
@@ -836,11 +845,14 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
     /** Detaches the composer and opens it in a new window. */
     public void detach() {
         Gtk.Widget? focused_widget = null;
+        var application = this.container.top_window.application as Application.Client;
+
         if (this.container != null) {
             focused_widget = this.container.top_window.get_focus();
             this.container.close();
         }
-        Window new_window = new Window(this, this.application);
+
+        var new_window = new Window(this, application);
 
         // Workaround a GTK+ crasher, Bug 771812. When the
         // composer is re-parented, its menu_button's popover
@@ -853,7 +865,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         // popover.
         this.composer_actions.change_action_state(
             ACTION_TEXT_FORMAT,
-            this.application.config.compose_as_html ? "html" : "plain"
+            this.config.compose_as_html ? "html" : "plain"
         );
 
         set_mode(DETACHED);
@@ -979,7 +991,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
             try {
                 yield close_draft_manager(KEEP);
             } catch (GLib.Error error) {
-                this.application.controller.report_problem(
+                this.application.report_problem(
                     new Geary.AccountProblemReport(
                         this.sender_context.account.information, error
                     )
@@ -995,10 +1007,10 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
             warning("Draft manager still open on composer destroy");
         }
 
-        this.application.engine.account_available.disconnect(
+        this.application.account_available.disconnect(
             on_account_available
         );
-        this.application.engine.account_unavailable.disconnect(
+        this.application.account_unavailable.disconnect(
             on_account_unavailable
         );
 
@@ -1220,13 +1232,13 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
             );
             entries_users.change_action_state(
                 ACTION_TEXT_FORMAT,
-                this.application.config.compose_as_html ? "html" : "plain"
+                this.config.compose_as_html ? "html" : "plain"
             );
         }
 
         this.composer_actions.change_action_state(
             ACTION_SHOW_FORMATTING,
-            this.application.config.formatting_toolbar_visible
+            this.config.formatting_toolbar_visible
         );
 
         get_action(Action.Edit.UNDO).set_enabled(false);
@@ -1384,13 +1396,13 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         email.img_src_prefix = ClientWebView.INTERNAL_URL_PREFIX;
 
         try {
-            if (!for_draft) {
-                if (this.editor.is_rich_text) {
-                    email.body_html = yield this.editor.get_html();
-                }
-                email.body_text = yield this.editor.get_text();
-            } else {
-                email.body_html = yield this.editor.get_html_for_draft();
+            email.body_text = yield this.editor.get_text();
+            if (this.editor.is_rich_text) {
+                email.body_html = (
+                    for_draft
+                    ? yield this.editor.get_html_for_draft()
+                    : yield this.editor.get_html()
+                );
             }
         } catch (Error error) {
             debug("Error getting composer message body: %s", error.message);
@@ -1423,7 +1435,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
             Util.Email.quote_email_for_reply(
                 referred,
                 to_quote,
-                this.application.config.clock_format,
+                this.config.clock_format,
                 Geary.RFC822.TextFormat.HTML
             )
         );
@@ -1618,14 +1630,14 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
 
         try {
             yield this.editor.clean_content();
-            yield this.application.controller.send_composed_email(this);
+            yield this.application.send_composed_email(this);
             yield close_draft_manager(DISCARD);
 
             if (this.container != null) {
                 this.container.close();
             }
         } catch (GLib.Error error) {
-            this.application.controller.report_problem(
+            this.application.report_problem(
                 new Geary.AccountProblemReport(
                     this.sender_context.account.information, error
                 )
@@ -1795,7 +1807,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
             try {
                 yield save_draft();
             } catch (GLib.Error error) {
-                this.application.controller.report_problem(
+                this.application.report_problem(
                     new Geary.AccountProblemReport(
                         this.sender_context.account.information, error
                     )
@@ -1808,28 +1820,29 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         if (this.container != null) {
             this.container.close();
         }
-        yield this.application.controller.save_composed_email(this);
+        yield this.application.save_composed_email(this);
     }
 
     private async void discard_and_close() {
         set_enabled(false);
 
+        // Pass on to the controller so the discarded email can be
+        // re-opened on undo
+        yield this.application.discard_composed_email(this);
+
         try {
             yield close_draft_manager(DISCARD);
         } catch (GLib.Error error) {
-            this.application.controller.report_problem(
+            this.application.report_problem(
                 new Geary.AccountProblemReport(
                     this.sender_context.account.information, error
                 )
             );
         }
 
-        // Pass on to the controller so the discarded email can be
-        // re-opened on undo
         if (this.container != null) {
             this.container.close();
         }
-        yield this.application.controller.discard_composed_email(this);
     }
 
     private void update_attachments_view() {
@@ -2192,7 +2205,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
                             ClientWebView.INTERNAL_URL_PREFIX + unique_filename
                         );
                     } catch (Error error) {
-                        this.application.controller.report_problem(
+                        this.application.report_problem(
                             new Geary.ProblemReport(error)
                         );
                     }
@@ -2243,7 +2256,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
 
         this.editor.set_rich_text(compose_as_html);
 
-        this.application.config.compose_as_html = compose_as_html;
+        this.config.compose_as_html = compose_as_html;
         this.more_options_button.popover.popdown();
     }
 
@@ -2300,7 +2313,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
 
     private void on_show_formatting(SimpleAction? action, Variant? new_state) {
         bool show_formatting = new_state.get_boolean();
-        this.application.config.formatting_toolbar_visible = show_formatting;
+        this.config.formatting_toolbar_visible = show_formatting;
         action.set_state(new_state);
 
         update_formatting_toolbar();
@@ -2444,7 +2457,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
                     if (!this.editor.is_rich_text)
                         append_menu_section(context_menu, section);
                 } else if (section == this.context_menu_inspector) {
-                    if (this.application.config.enable_inspector)
+                    if (this.config.enable_inspector)
                         append_menu_section(context_menu, section);
                 } else {
                     append_menu_section(context_menu, section);
@@ -2581,10 +2594,13 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
 
         // If there's only one account and it not have any aliases,
         // show nothing.
-        if (this.accounts.size < 1 ||
-            (this.accounts.size == 1 &&
-            !Geary.Collection.first(this.accounts)
-             .account.information.has_sender_aliases)) {
+        Gee.Collection<Application.AccountContext> accounts =
+            this.application.get_account_contexts();
+        if (accounts.size < 1 ||
+            (accounts.size == 1 &&
+            !Geary.Collection.first(
+                accounts
+            ).account.information.has_sender_aliases)) {
             return false;
         }
 
@@ -2599,7 +2615,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         // is set to true if the current message's from address has
         // been set in the ComboBox.
         bool set_active = add_account_emails_to_from_list(this.sender_context);
-        foreach (var account in this.accounts) {
+        foreach (var account in accounts) {
             if (account != this.sender_context) {
                 set_active = add_account_emails_to_from_list(
                     account, set_active
@@ -2635,7 +2651,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
                         try {
                             this.reopen_draft_manager.end(res);
                         } catch (GLib.Error error) {
-                            this.application.controller.report_problem(
+                            this.application.report_problem(
                                 new Geary.AccountProblemReport(
                                     current_account.information, error
                                 )
@@ -2683,7 +2699,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
 
     private void update_subject_spell_checker() {
         Gspell.Language? lang = null;
-        string[] langs = this.application.config.get_spell_check_languages();
+        string[] langs = this.config.get_spell_check_languages();
         if (langs.length == 1) {
             lang = Gspell.Language.lookup(langs[0]);
         } else {
@@ -2805,7 +2821,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         // without the popover immediately appearing and raining on
         // their text selection parade.
         if (this.pointer_url != null &&
-            this.application.config.compose_as_html) {
+            this.config.compose_as_html) {
             Gdk.EventButton? button = (Gdk.EventButton) event;
             Gdk.Rectangle location = Gdk.Rectangle();
             location.x = (int) button.x;
@@ -2863,7 +2879,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
 
     private void on_add_attachment() {
         AttachmentDialog dialog = new AttachmentDialog(
-            this.container.top_window, this.application.config
+            this.container.top_window, this.config
         );
         if (dialog.run() == Gtk.ResponseType.ACCEPT) {
             dialog.hide();
@@ -2889,7 +2905,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
 
     private void on_insert_image(SimpleAction action, Variant? param) {
         AttachmentDialog dialog = new AttachmentDialog(
-            this.container.top_window, this.application.config
+            this.container.top_window, this.config
         );
         Gtk.FileFilter filter = new Gtk.FileFilter();
         // Translators: This is the name of the file chooser filter
@@ -2976,7 +2992,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
                 try {
                     this.save_draft.end(res);
                 } catch (GLib.Error error) {
-                    this.application.controller.report_problem(
+                    this.application.report_problem(
                         new Geary.AccountProblemReport(
                             current_account.information, error
                         )
diff --git a/src/client/meson.build b/src/client/meson.build
index 033aaf14c..c600f674b 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -71,6 +71,7 @@ client_vala_sources = files(
   'components/status-bar.vala',
   'components/stock.vala',
 
+  'composer/composer-application-interface.vala',
   'composer/composer-box.vala',
   'composer/composer-container.vala',
   'composer/composer-email-entry.vala',
diff --git a/test/client/composer/composer-widget-test.vala b/test/client/composer/composer-widget-test.vala
new file mode 100644
index 000000000..b369e7924
--- /dev/null
+++ b/test/client/composer/composer-widget-test.vala
@@ -0,0 +1,203 @@
+/*
+ * Copyright © 2020 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+public class Composer.WidgetTest : TestCase {
+
+
+    private class MockApplicationInterface :
+        GLib.Object,
+        Application.AccountInterface,
+        ApplicationInterface,
+        ValaUnit.TestAssertions,
+        ValaUnit.MockObject {
+
+
+        protected Gee.Queue<ValaUnit.ExpectedCall> expected {
+            get; set; default = new Gee.LinkedList<ValaUnit.ExpectedCall>();
+        }
+
+
+        internal Application.AccountContext? get_context_for_account(Geary.AccountInformation account)  {
+            try {
+                return object_call<Application.AccountContext?>(
+                    "get_account_contexts",
+                    {account},
+                    null
+                );
+            } catch (GLib.Error err) {
+                // fine
+                return null;
+            }
+        }
+
+        internal Gee.Collection<Application.AccountContext> get_account_contexts() {
+            try {
+                return object_call<Gee.Collection<Application.AccountContext>>(
+                    "get_account_contexts",
+                    {},
+                    Gee.Collection.empty()
+                );
+            } catch (GLib.Error err) {
+                // fine
+                return Gee.Collection.empty();
+            }
+        }
+
+        public void report_problem(Geary.ProblemReport report) {
+            try {
+                void_call("report_problem", {report});
+            } catch (GLib.Error err) {
+                // fine
+            }
+        }
+
+        internal async void send_composed_email(Widget composer) {
+            try {
+                void_call("send_composed_email", {composer});
+            } catch (GLib.Error err) {
+                // fine
+            }
+        }
+
+        internal async void save_composed_email(Widget composer) {
+            try {
+                void_call("save_composed_email", {composer});
+            } catch (GLib.Error err) {
+                // fine
+            }
+        }
+
+        internal async void discard_composed_email(Widget composer) {
+            try {
+                void_call("discard_composed_email", {composer});
+            } catch (GLib.Error err) {
+                // fine
+            }
+        }
+
+    }
+
+
+    private Application.AccountContext? account = null;
+    private ApplicationInterface? application = null;
+    private Application.Configuration? config = null;
+
+
+    public WidgetTest() {
+        base("Composer.WidgetTest");
+        add_test("load_empty_body", load_empty_body);
+        add_test("load_empty_body_to", load_empty_body_to);
+        add_test("load_mailto", load_mailto);
+        add_test("load_context_edit", load_context_edit);
+    }
+
+    public override void set_up() {
+        this.application = new MockApplicationInterface();
+
+        this.config = new Application.Configuration(Application.Client.SCHEMA_ID);
+
+        var info = new Geary.AccountInformation(
+            "account_01",
+            Geary.ServiceProvider.OTHER,
+            new Mock.CredentialsMediator(),
+            new Geary.RFC822.MailboxAddress(null, "test1 example com")
+        );
+        var mock_account = new Mock.Account(info);
+        this.account = new Application.AccountContext(
+            mock_account,
+            new Geary.App.SearchFolder(
+                mock_account,
+                new Geary.FolderRoot("", false)
+            ),
+            new Geary.App.EmailStore(mock_account),
+            new Application.ContactStore(
+                mock_account,
+                Folks.IndividualAggregator.dup(),
+                new Application.AvatarStore()
+            )
+        );
+    }
+
+    public override void tear_down() {
+        this.application = null;
+        this.config = null;
+        this.account = null;
+    }
+
+    public void load_empty_body() throws GLib.Error {
+        var widget = new Widget(this.application, this.config, this.account);
+        widget.load_empty_body.begin(null, this.async_completion);
+        widget.load_empty_body.end(async_result());
+        assert_equal(widget.to, "");
+    }
+
+    public void load_empty_body_to() throws GLib.Error {
+        var widget = new Widget(this.application, this.config, this.account);
+        widget.load_empty_body.begin(
+            new Geary.RFC822.MailboxAddress(null, "empty_to example com"),
+            this.async_completion
+        );
+        widget.load_empty_body.end(async_result());
+        assert_equal(widget.to, "empty_to example com");
+    }
+
+    public void load_mailto() throws GLib.Error {
+        var widget = new Widget(this.application, this.config, this.account);
+
+        widget.load_mailto.begin(
+            "mailto:mailto example com", this.async_completion
+        );
+        widget.load_mailto.end(async_result());
+
+        assert_equal(widget.to, "mailto example com");
+    }
+
+    public void load_context_edit() throws GLib.Error {
+        var widget = new Widget(this.application, this.config, this.account);
+
+        widget.load_context.begin(
+            EDIT, load_email(), null, this.async_completion
+        );
+        widget.load_context.end(async_result());
+
+        assert_equal(widget.to, "Charlie <charlie example net>");
+        assert_equal(widget.cc, "Dave <dave example net>");
+        assert_equal(widget.bcc, "Eve <eve example net>");
+        assert_equal(widget.reply_to, "Alice: Personal Account <alice example org>");
+        assert_equal(widget.subject, "Re: Basic text/plain message");
+    }
+
+    private Geary.Email load_email()
+        throws GLib.Error {
+        var message = new Geary.RFC822.Message.from_buffer(
+            new Geary.Memory.StringBuffer(MESSAGE.replace("\n","\r\n"))
+        );
+        return new Geary.Email.from_message(
+            new Mock.EmailIdentifer(0), message
+        );
+    }
+
+    private const string MESSAGE = """From: Alice <alice example net>
+Sender: Bob <bob example net>
+To: Charlie <charlie example net>
+CC: Dave <dave example net>
+BCC: Eve <eve example net>
+Reply-To: "Alice: Personal Account" <alice example org>
+Subject: Re: Basic text/plain message
+Date: Fri, 21 Nov 1997 10:01:10 -0600
+Message-ID: <3456 example net>
+In-Reply-To: <1234@local.machine.example>
+References: <1234@local.machine.example>
+X-Mailer: Geary Test Suite 1.0
+
+This is the first line.
+
+This is the second line.
+
+""";
+
+}
diff --git a/test/meson.build b/test/meson.build
index 21d453fae..a2fc8a624 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -84,6 +84,7 @@ test_client_sources = [
   'client/components/client-web-view-test-case.vala',
   'client/components/components-validator-test.vala',
   'client/composer/composer-web-view-test.vala',
+  'client/composer/composer-widget-test.vala',
   'client/util/util-avatar-test.vala',
   'client/util/util-cache-test.vala',
   'client/util/util-email-test.vala',
diff --git a/test/test-client.vala b/test/test-client.vala
index 02e98ba5a..5e432b709 100644
--- a/test/test-client.vala
+++ b/test/test-client.vala
@@ -53,6 +53,7 @@ int main(string[] args) {
     client.add_suite(new Application.ConfigurationTest().suite);
     client.add_suite(new ClientWebViewTest().suite);
     client.add_suite(new Composer.WebViewTest().suite);
+    client.add_suite(new Composer.WidgetTest().suite);
     client.add_suite(new Components.ValidatorTest().suite);
     client.add_suite(new Util.Avatar.Test().suite);
     client.add_suite(new Util.Cache.Test().suite);


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