Browse Source

feat: Refactor chatbox minimizing to replace 'minimized' with 'hidden' state

Get rid of the `overrides` in the minimize plugin.
JC Brand 10 months ago
parent
commit
2690c0c225

+ 3 - 1
src/headless/shared/chatbox.js

@@ -41,9 +41,11 @@ export default class ChatBoxBase extends ModelWithMessages(Model) {
                 // So before opening a chat, we make sure all other chats are hidden.
                 other_chats.forEach((c) => u.safeSave(c, { 'hidden': true }));
                 u.safeSave(this, { 'hidden': false });
+                this.trigger('show');
             }
-            return;
+            return this;
         }
+        // Overlayed view mode
         u.safeSave(this, { 'hidden': false });
         this.trigger('show');
         return this;

+ 1 - 1
src/plugins/chatboxviews/templates/chats.js

@@ -5,7 +5,7 @@ import { _converse, api, constants } from '@converse/headless';
 const { CONTROLBOX_TYPE, CHATROOMS_TYPE, HEADLINES_TYPE } = constants;
 
 function shouldShowChat(c) {
-    const is_minimized = api.settings.get('view_mode') === 'overlayed' && c.get('minimized');
+    const is_minimized = api.settings.get('view_mode') === 'overlayed' && c.get('hidden');
     return c.get('type') === CONTROLBOX_TYPE || !(c.get('hidden') || is_minimized);
 }
 

+ 0 - 1
src/plugins/chatboxviews/view.js

@@ -11,7 +11,6 @@ class ConverseChats extends CustomElement {
         this.listenTo(this.model, 'change:closed', () => this.requestUpdate());
         this.listenTo(this.model, 'change:hidden', () => this.requestUpdate());
         this.listenTo(this.model, 'change:jid', () => this.requestUpdate());
-        this.listenTo(this.model, 'change:minimized', () => this.requestUpdate());
         this.listenTo(this.model, 'destroy', () => this.requestUpdate());
 
         // Use listenTo instead of api.listen.to so that event handlers

+ 0 - 7
src/plugins/chatview/tests/chatbox.js

@@ -80,7 +80,6 @@ describe("Chatboxes", function () {
             // openControlBox was called earlier, so the controlbox is
             // visible, but no other chat boxes have been created.
             expect(_converse.chatboxes.length).toEqual(1);
-            spyOn(_converse.exports.minimize, 'trimChats');
             expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open
 
             const rosterview = document.querySelector('converse-roster');
@@ -90,11 +89,9 @@ describe("Chatboxes", function () {
             let el = online_contacts[0];
             el.click();
             await u.waitUntil(() => document.querySelectorAll("#conversejs .chatbox").length == 2);
-            expect(_converse.exports.minimize.trimChats).toHaveBeenCalled();
             online_contacts[1].click();
             await u.waitUntil(() => _converse.chatboxes.length == 3);
             el = online_contacts[1];
-            expect(_converse.exports.minimize.trimChats).toHaveBeenCalled();
             // Check that new chat boxes are created to the left of the
             // controlbox (but to the right of all existing chat boxes)
             expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(3);
@@ -163,7 +160,6 @@ describe("Chatboxes", function () {
         it("can be saved to, and retrieved from, browserStorage",
                 mock.initConverse([], {}, async function (_converse) {
 
-            spyOn(_converse.exports.minimize, 'trimChats');
             await mock.waitForRoster(_converse, 'current');
             await mock.openControlBox(_converse);
 
@@ -171,7 +167,6 @@ describe("Chatboxes", function () {
 
             mock.openChatBoxes(_converse, 6);
             await u.waitUntil(() => _converse.chatboxes.length == 7);
-            expect(_converse.exports.minimize.trimChats).toHaveBeenCalled();
             // We instantiate a new ChatBoxes collection, which by default
             // will be empty.
             const newchatboxes = new _converse.ChatBoxes();
@@ -215,7 +210,6 @@ describe("Chatboxes", function () {
 
             await mock.waitForRoster(_converse, 'current');
             await mock.openControlBox(_converse);
-            spyOn(_converse.exports.minimize, 'trimChats');
             const rosterview = document.querySelector('converse-roster');
             await u.waitUntil(() => rosterview.querySelectorAll('.roster-group').length);
             spyOn(_converse.api, "trigger").and.callThrough();
@@ -226,7 +220,6 @@ describe("Chatboxes", function () {
             expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
             mock.openChatBoxes(_converse, 6);
             await u.waitUntil(() => _converse.chatboxes.length == 7)
-            expect(_converse.exports.minimize.trimChats).toHaveBeenCalled();
             expect(_converse.chatboxes.length).toEqual(7);
             expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxViewInitialized', jasmine.any(Object));
             await mock.closeAllChatBoxes(_converse);

+ 4 - 2
src/plugins/controlbox/tests/controlbox.js

@@ -61,6 +61,8 @@ describe("The Controlbox", function () {
         it("shows the number of unread mentions received",
                 mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
 
+            const { minimize, maximize } = converse.env.u;
+
             await mock.waitForRoster(_converse, 'all');
             await mock.openControlBox(_converse);
 
@@ -68,7 +70,7 @@ describe("The Controlbox", function () {
             await mock.openChatBoxFor(_converse, sender_jid);
             await u.waitUntil(() => _converse.chatboxes.length);
             const chatview = _converse.chatboxviews.get(sender_jid);
-            chatview.model.set({'minimized': true});
+            minimize(chatview.model);
 
             const el = document.querySelector('converse-chats');
             expect(el.querySelector('.restore-chat .message-count') === null).toBeTruthy();
@@ -99,7 +101,7 @@ describe("The Controlbox", function () {
             await u.waitUntil(() => chatview.model.handleUnreadMessage.calls.count());
             await u.waitUntil(() => el.querySelector('.restore-chat .message-count')?.textContent === '2');
             expect(rosterview.querySelector('.msgs-indicator').textContent).toBe('2');
-            chatview.model.set({'minimized': false});
+            maximize(chatview.model);
             await u.waitUntil(() => el.querySelector('.restore-chat .message-count') === null);
             await u.waitUntil(() => rosterview.querySelector('.msgs-indicator') === null);
         }));

+ 1 - 0
src/plugins/headlines-view/templates/chat-head.js

@@ -5,6 +5,7 @@ import { getStandaloneButtons, getDropdownButtons } from 'shared/chat/utils.js';
 
 
 export default (o) => {
+    debugger;
     return html`
         <div class="chatbox-title ${ o.status ? '' :  "chatbox-title--no-desc"}">
             <div class="chatbox-title--row">

+ 13 - 64
src/plugins/minimize/index.js

@@ -2,12 +2,9 @@
  * @module converse-minimize
  * @copyright 2022, the Converse.js contributors
  * @license Mozilla Public License (MPLv2)
- *
- * @typedef {import('@converse/headless').MUC} MUC
- * @typedef {import('@converse/headless').ChatBox} ChatBox
  */
 import debounce from 'lodash-es/debounce';
-import { _converse, api, converse, constants } from '@converse/headless';
+import { _converse, api, converse, constants, u } from '@converse/headless';
 import MinimizedChatsToggle from './toggle.js';
 import {
     addMinimizeButtonToChat,
@@ -15,28 +12,27 @@ import {
     initializeChat,
     maximize,
     minimize,
-    onMinimizedChanged,
     trimChats
 } from './utils.js';
 
 import './view.js';
 import './minimized-chat.js';
-
 import './styles/minimize.scss';
 
 const { CHATROOMS_TYPE } = constants;
 
 
 converse.plugins.add('converse-minimize', {
+    /**
+     * @typedef {import('@converse/headless').MUC} MUC
+     * @typedef {import('@converse/headless').ChatBox} ChatBox
+     */
+
     /* Optional dependencies are other plugins which might be
      * overridden or relied upon, and therefore need to be loaded before
      * this plugin. They are called "optional" because they might not be
      * available, in which case any overrides applicable to them will be
      * ignored.
-     *
-     * It's possible however to make optional dependencies non-optional.
-     * If the setting "strict_plugin_dependencies" is set to true,
-     * an error will be raised if the plugin is not found.
      */
     dependencies: [
         "converse-chatview",
@@ -50,46 +46,6 @@ converse.plugins.add('converse-minimize', {
         return _converse.api.settings.get("view_mode") === 'overlayed';
     },
 
-    // Overrides mentioned here will be picked up by converse.js's
-    // plugin architecture they will replace existing methods on the
-    // relevant objects or classes.
-    // New functions which don't exist yet can also be added.
-    overrides: {
-        ChatBox: {
-            maybeShow (force) {
-                if (!force && this.get('minimized')) {
-                    // Must return the chatbox
-                    return this;
-                }
-                return this.__super__.maybeShow.apply(this, arguments);
-            },
-
-            isHidden () {
-                return this.__super__.isHidden.call(this) || this.get('minimized');
-            }
-        },
-
-        ChatBoxView: {
-            isNewMessageHidden () {
-                return this.model.get('minimized') ||
-                    this.__super__.isNewMessageHidden.apply(this, arguments);
-            },
-
-            setChatBoxHeight (height) {
-                if (!this.model.get('minimized')) {
-                    return this.__super__.setChatBoxHeight.call(this, height);
-                }
-            },
-
-            setChatBoxWidth (width) {
-                if (!this.model.get('minimized')) {
-                    return this.__super__.setChatBoxWidth.call(this, width);
-                }
-            }
-        }
-    },
-
-
     initialize () {
         api.settings.extend({'no_trimming': false});
 
@@ -100,20 +56,13 @@ converse.plugins.add('converse-minimize', {
         Object.assign(_converse.exports, exports);
         Object.assign(_converse, { minimize: { trimChats, minimize, maximize }}); // DEPRECATED
         Object.assign(_converse.exports, { minimize: { trimChats, minimize, maximize }});
+        Object.assign(u, { trimChats, minimize, maximize });
 
-        /**
-         * @param { ChatBox|MUC } model
-         */
-        function onChatInitialized (model) {
-            initializeChat(model);
-            model.on( 'change:minimized', () => onMinimizedChanged(model));
-        }
-
-        api.listen.on('chatBoxViewInitialized', view => _converse.exports.minimize.trimChats(view));
-        api.listen.on('chatRoomViewInitialized', view => _converse.exports.minimize.trimChats(view));
-        api.listen.on('controlBoxOpened', view => _converse.exports.minimize.trimChats(view));
-        api.listen.on('chatBoxInitialized', onChatInitialized);
-        api.listen.on('chatRoomInitialized', onChatInitialized);
+        api.listen.on('chatBoxViewInitialized', view => trimChats(view));
+        api.listen.on('chatRoomViewInitialized', view => trimChats(view));
+        api.listen.on('controlBoxOpened', view => trimChats(view));
+        api.listen.on('chatBoxInitialized', initializeChat);
+        api.listen.on('chatRoomInitialized', initializeChat);
 
         api.listen.on('getHeadingButtons', (view, buttons) => {
             if (view.model.get('type') === CHATROOMS_TYPE) {
@@ -123,7 +72,7 @@ converse.plugins.add('converse-minimize', {
             }
         });
 
-        const debouncedTrimChats = debounce(() => _converse.exports.minimize.trimChats(), 250);
+        const debouncedTrimChats = debounce(() => trimChats(), 250);
         api.listen.on('registeredGlobalEventHandlers', () => window.addEventListener("resize", debouncedTrimChats));
         api.listen.on('unregisteredGlobalEventHandlers', () => window.removeEventListener("resize", debouncedTrimChats));
     }

+ 3 - 3
src/plugins/minimize/templates/toggle.js

@@ -5,14 +5,14 @@ import { __ } from 'i18n';
  * @param {import('../view').default} el
  */
 export default (el) => {
-    const chats = el.model.where({'minimized': true});
+    const chats = el.model.where({'hidden': true});
     const num_unread = chats.reduce((acc, chat) => (acc + chat.get('num_unread')), 0);
-    const num_minimized = chats.reduce((acc, chat) => (acc + (chat.get('minimized') ? 1 : 0)), 0);
+    const num_minimized = chats.reduce((acc, chat) => (acc + (chat.get('hidden') ? 1 : 0)), 0);
     const collapsed = el.minchats.get('collapsed');
 
     return html`<div id="minimized-chats" class="${chats.length ? '' : 'hidden'}">
         <button type="button" class="btn btn-primary" @click=${(ev) => el.toggle(ev)}>
-            ${num_minimized} ${__('Minimized')}
+            ${num_minimized} ${__('hidden')}
             <span class="badge bg-secondary unread-message-count ${!num_unread ? 'hidden' : ''}">${num_unread}</span>
         </button>
         <div class="flyout minimized-chats-flyout row g-0 ${collapsed ? 'hidden' : ''}">

+ 46 - 39
src/plugins/minimize/tests/minchats.js

@@ -1,8 +1,6 @@
 /*global mock, converse */
-
 const { $msg, u } = converse.env;
 
-
 describe("A chat message", function () {
 
     it("received for a minimized chat box will increment a counter on its header",
@@ -19,9 +17,9 @@ describe("A chat message", function () {
         await mock.openChatBoxFor(_converse, contact_jid);
         const chatview = _converse.chatboxviews.get(contact_jid);
         expect(u.isVisible(chatview)).toBeTruthy();
-        expect(chatview.model.get('minimized')).toBeFalsy();
+        expect(chatview.model.get('hidden')).toBeFalsy();
         chatview.querySelector('.toggle-chatbox-button').click();
-        expect(chatview.model.get('minimized')).toBeTruthy();
+        expect(chatview.model.get('hidden')).toBeTruthy();
         var message = 'This message is sent to a minimized chatbox';
         var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
         var msg = $msg({
@@ -38,7 +36,7 @@ describe("A chat message", function () {
         expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
         let count = document.querySelector('converse-minimized-chat .message-count');
         expect(u.isVisible(chatview)).toBeFalsy();
-        expect(chatview.model.get('minimized')).toBeTruthy();
+        expect(chatview.model.get('hidden')).toBeTruthy();
 
         expect(u.isVisible(count)).toBeTruthy();
         expect(count.textContent).toBe('1');
@@ -54,12 +52,12 @@ describe("A chat message", function () {
 
         await u.waitUntil(() => (chatview.model.messages.length > 1));
         expect(u.isVisible(chatview)).toBeFalsy();
-        expect(chatview.model.get('minimized')).toBeTruthy();
+        expect(chatview.model.get('hidden')).toBeTruthy();
         count = document.querySelector('converse-minimized-chat .message-count');
         expect(u.isVisible(count)).toBeTruthy();
         expect(count.textContent).toBe('2');
         document.querySelector("converse-minimized-chat a.restore-chat").click();
-        expect(_converse.chatboxes.filter('minimized').length).toBe(0);
+        expect(_converse.chatboxes.filter('hidden').length).toBe(0);
     }));
 
 });
@@ -78,11 +76,11 @@ describe("A Groupchat", function () {
 
         expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
         await u.waitUntil(() => !u.isVisible(view));
-        expect(view.model.get('minimized')).toBeTruthy();
+        expect(view.model.get('hidden')).toBeTruthy();
         const el = await u.waitUntil(() => document.querySelector("converse-minimized-chat a.restore-chat"));
         el.click();
         expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
-        expect(view.model.get('minimized')).toBeFalsy();
+        expect(view.model.get('hidden')).toBeFalsy();
         expect(_converse.api.trigger.calls.count(), 3);
     }));
 });
@@ -107,12 +105,12 @@ describe("A Chatbox", function () {
         expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
         expect(_converse.api.trigger.calls.count(), 2);
         await u.waitUntil(() => !u.isVisible(chatview));
-        expect(chatview.model.get('minimized')).toBeTruthy();
+        expect(chatview.model.get('hidden')).toBeTruthy();
         const restore_el = await u.waitUntil(() => document.querySelector("converse-minimized-chat a.restore-chat"));
         restore_el.click();
         await u.waitUntil(() => _converse.chatboxviews.keys().length);
         expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
-        expect(chatview.model.get('minimized')).toBeFalsy();
+        expect(chatview.model.get('hidden')).toBeFalsy();
     }));
 
 
@@ -121,17 +119,21 @@ describe("A Chatbox", function () {
         const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
         const minimized_chats = await u.waitUntil(() => document.querySelector("converse-minimized-chats"));
         expect(u.isVisible(minimized_chats.firstElementChild)).toBe(false);
-        await _converse.api.chats.create(sender_jid, {'minimized': true});
+        await _converse.api.chats.create(sender_jid, {'hidden': true});
         await u.waitUntil(() => _converse.chatboxes.length > 1);
         expect(_converse.chatboxviews.get(sender_jid)).toBe(undefined);
         expect(u.isVisible(minimized_chats.firstElementChild)).toBe(true);
         expect(minimized_chats.firstElementChild.querySelectorAll('converse-minimized-chat').length).toBe(1);
-        expect(_converse.chatboxes.filter('minimized').length).toBe(1);
+        expect(_converse.chatboxes.filter('hidden').length).toBe(1);
     }));
 
 
-    it("can be trimmed to conserve space", mock.initConverse([], {}, async function (_converse) {
-        spyOn(_converse.exports.minimize, 'trimChats');
+    it("can be trimmed to conserve space",
+            mock.initConverse(
+                [],
+                { no_trimming: false },
+                async function (_converse) {
+
         await mock.waitForRoster(_converse, 'current');
         await mock.openControlBox(_converse);
 
@@ -151,18 +153,18 @@ describe("A Chatbox", function () {
             el.click();
         }
         await u.waitUntil(() => _converse.chatboxes.length == 16);
-        expect(_converse.exports.minimize.trimChats.calls.count()).toBe(16);
-
-        for (i=0; i<online_contacts.length; i++) {
-            const el = online_contacts[i];
-            const jid = el.getAttribute('data-jid');
-            const model = _converse.chatboxes.get(jid);
-            model.set({'minimized': true});
-        }
-        await u.waitUntil(() => _converse.chatboxviews.keys().length === 1);
         const minimized_chats = await u.waitUntil(() => document.querySelector("converse-minimized-chats"));
+
+        const minimized = _converse.chatboxes.models.filter((m) => m.get('hidden'));
+        const num_minimized = minimized.length;
+        expect(num_minimized).toBeGreaterThan(0);
+
+        // If we restore a chat, the number of minimized chats stay the same,
+        // because a previously maximized one is minimized again to
+        // save space.
         minimized_chats.querySelector("a.restore-chat").click();
-        expect(_converse.exports.minimize.trimChats.calls.count()).toBe(16);
+
+
     }));
 });
 
@@ -172,6 +174,7 @@ describe("A minimized chat's Unread Message Count", function () {
     it("is displayed when scrolled up chatbox is minimized after receiving unread messages",
             mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
 
+        const { minimize } = converse.env.u;
         await mock.waitForRoster(_converse, 'current', 1);
         const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
         await mock.openChatBoxFor(_converse, sender_jid);
@@ -181,7 +184,7 @@ describe("A minimized chat's Unread Message Count", function () {
         _converse.handleMessageStanza(msgFactory());
         await u.waitUntil(() => chatbox.messages.length);
         await u.waitUntil(() => chatbox.get('num_unread') === 1);
-        _converse.exports.minimize.minimize(chatbox);
+        minimize(chatbox);
         const minimized_chats = await u.waitUntil(() => document.querySelector("converse-minimized-chats"));
         const unread_count = minimized_chats.querySelector('#minimized-chats .badge');
         expect(u.isVisible(unread_count)).toBeTruthy();
@@ -219,28 +222,29 @@ describe("The Minimized Chats Widget", function () {
         let contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
         await mock.openChatBoxFor(_converse, contact_jid)
         let chatview = _converse.chatboxviews.get(contact_jid);
-        expect(chatview.model.get('minimized')).toBeFalsy();
+        expect(chatview.model.get('hidden')).toBeFalsy();
         expect(u.isVisible(minimized_chats.firstElementChild)).toBe(false);
         chatview.querySelector('.toggle-chatbox-button').click();
-        expect(chatview.model.get('minimized')).toBeTruthy();
+        expect(chatview.model.get('hidden')).toBeTruthy();
         expect(u.isVisible(minimized_chats)).toBe(true);
-        expect(_converse.chatboxes.filter('minimized').length).toBe(1);
-        expect(_converse.chatboxes.models.filter(c => c.get('minimized')).pop().get('jid')).toBe(contact_jid);
+        expect(_converse.chatboxes.filter('hidden').length).toBe(1);
+        expect(_converse.chatboxes.models.filter(c => c.get('hidden')).pop().get('jid')).toBe(contact_jid);
 
         contact_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
         await mock.openChatBoxFor(_converse, contact_jid);
         chatview = _converse.chatboxviews.get(contact_jid);
-        expect(chatview.model.get('minimized')).toBeFalsy();
+        expect(chatview.model.get('hidden')).toBeFalsy();
         chatview.querySelector('.toggle-chatbox-button').click();
-        expect(chatview.model.get('minimized')).toBeTruthy();
+        expect(chatview.model.get('hidden')).toBeTruthy();
         expect(u.isVisible(minimized_chats)).toBe(true);
-        expect(_converse.chatboxes.filter('minimized').length).toBe(2);
-        expect(_converse.chatboxes.filter('minimized').map(c => c.get('jid')).includes(contact_jid)).toBeTruthy();
+        expect(_converse.chatboxes.filter('hidden').length).toBe(2);
+        expect(_converse.chatboxes.filter('hidden').map(c => c.get('jid')).includes(contact_jid)).toBeTruthy();
     }));
 
     it("can be toggled to hide or show minimized chats",
             mock.initConverse([], {}, async function (_converse) {
 
+        const { minimize } = converse.env.u;
         await mock.waitForRoster(_converse, 'current');
         await mock.openControlBox(_converse);
         let minimized_chats = await u.waitUntil(() => document.querySelector("converse-minimized-chats"));
@@ -251,10 +255,10 @@ describe("The Minimized Chats Widget", function () {
         const chatview = _converse.chatboxviews.get(contact_jid);
         expect(u.isVisible(minimized_chats.firstElementChild)).toBe(false);
 
-        chatview.model.set({'minimized': true});
+        minimize(chatview.model);
         expect(u.isVisible(minimized_chats)).toBeTruthy();
-        expect(_converse.chatboxes.filter('minimized').length).toBe(1);
-        expect(_converse.chatboxes.models.filter(c => c.get('minimized')).pop().get('jid')).toBe(contact_jid);
+        expect(_converse.chatboxes.filter('hidden').length).toBe(1);
+        expect(_converse.chatboxes.models.filter(c => c.get('hidden')).pop().get('jid')).toBe(contact_jid);
 
         minimized_chats = await u.waitUntil(() => document.querySelector("converse-minimized-chats"));
         expect(u.isVisible(minimized_chats.querySelector('.minimized-chats-flyout'))).toBeTruthy();
@@ -267,6 +271,8 @@ describe("The Minimized Chats Widget", function () {
     it("shows the number messages received to minimized chats",
             mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
 
+        const { minimize } = converse.env.u;
+
         await mock.waitForRoster(_converse, 'current', 4);
         await mock.openControlBox(_converse);
         const minimized_chats = await u.waitUntil(() => document.querySelector("converse-minimized-chats"));
@@ -286,7 +292,7 @@ describe("The Minimized Chats Widget", function () {
         await u.waitUntil(() => _converse.chatboxes.length == 4);
 
         const chatview = _converse.chatboxviews.get(contact_jid);
-        chatview.model.set({'minimized': true});
+        minimize(chatview.model);
         for (i=0; i<3; i++) {
             const msg = $msg({
                 from: contact_jid,
@@ -342,10 +348,11 @@ describe("The Minimized Chats Widget", function () {
     it("shows the number messages received to minimized groupchats",
             mock.initConverse([], {}, async function (_converse) {
 
+        const { minimize } = converse.env.u;
         const muc_jid = 'kitchen@conference.shakespeare.lit';
         await mock.openAndEnterChatRoom(_converse, 'kitchen@conference.shakespeare.lit', 'fires');
         const view = _converse.chatboxviews.get(muc_jid);
-        view.model.set({'minimized': true});
+        minimize(view.model);
         const message = 'fires: Your attention is required';
         const nick = mock.chatroom_names[0];
         const msg = $msg({

+ 16 - 22
src/plugins/minimize/utils.js

@@ -10,20 +10,20 @@ import { _converse, api, converse, u, constants } from '@converse/headless';
 import { __ } from 'i18n';
 
 const { dayjs } = converse.env;
-const { ACTIVE, INACTIVE } = constants;
+const { ACTIVE } = constants;
 
 /**
- * @param { ChatBox|MUC } chat
+ * @param {ChatBox|MUC} chat
  */
 export function initializeChat (chat) {
-    chat.on('change:hidden', m => !m.get('hidden') && maximize(chat), chat);
+    chat.on('change:hidden', () => onMinimizedChanged(chat));
 
     if (chat.get('id') === 'controlbox') {
         return;
     }
     chat.save({
-        'minimized': chat.get('minimized') || false,
-        'time_minimized': chat.get('time_minimized') || dayjs(),
+        'hidden': !!chat.get('hidden'),
+        'time_minimized': chat.get('time_minimized'),
     });
 }
 
@@ -37,7 +37,7 @@ function getChatBoxWidth (view) {
             const toggle = document.querySelector('converse-controlbox-toggle');
             return toggle ? u.getOuterWidth(toggle, true) : 0;
         }
-    } else if (!view.model.get('minimized') && u.isVisible(view)) {
+    } else if (!view.model.get('hidden') && u.isVisible(view)) {
         return u.getOuterWidth(view, true);
     }
     return 0;
@@ -47,13 +47,13 @@ function getShownChats () {
     return _converse.state.chatboxviews.filter(el =>
         // The controlbox can take a while to close,
         // so we need to check its state. That's why we checked the 'closed' state.
-        !el.model.get('minimized') && !el.model.get('closed') && u.isVisible(el)
+        !el.model.get('hidden') && !el.model.get('closed') && u.isVisible(el)
     );
 }
 
 function getMinimizedWidth () {
     const minimized_el = document.querySelector('converse-minimized-chats');
-    return _converse.state.chatboxes.pluck('minimized').includes(true) ? u.getOuterWidth(minimized_el, true) : 0;
+    return _converse.state.chatboxes.pluck('hidden').includes(true) ? u.getOuterWidth(minimized_el, true) : 0;
 }
 
 function getBoxesWidth (newchat) {
@@ -68,11 +68,10 @@ function getBoxesWidth (newchat) {
  * It checks whether there is enough space on the page to show
  * another chat box. Otherwise it minimizes the oldest chat box
  * to create space.
- * @method _converse.ChatBoxViews#trimChats
- * @param { ChatView|MUCView|ControlBoxView|HeadlinesFeedView } [newchat]
+ * @param {ChatView|MUCView|ControlBoxView|HeadlinesFeedView} [newchat]
  */
 export function trimChats (newchat) {
-    if (u.isTestEnv() || api.settings.get('no_trimming') || api.settings.get("view_mode") !== 'overlayed') {
+    if (api.settings.get('no_trimming') || api.settings.get("view_mode") !== 'overlayed') {
         return;
     }
     const shown_chats = getShownChats();
@@ -107,7 +106,7 @@ function getOldestMaximizedChat (exclude_ids) {
     exclude_ids.push('controlbox');
     let i = 0;
     let model = _converse.state.chatboxes.sort().at(i);
-    while (exclude_ids.includes(model.get('id')) || model.get('minimized') === true) {
+    while (exclude_ids.includes(model.get('id')) || model.get('hidden') === true) {
         i++;
         model = _converse.state.chatboxes.at(i);
         if (!model) {
@@ -156,7 +155,6 @@ export function maximize (ev, chatbox) {
     }
     u.safeSave(chatbox, {
         'hidden': false,
-        'minimized': false,
         'time_opened': new Date().getTime()
     });
 }
@@ -169,17 +167,13 @@ export function minimize (ev, model) {
     }
     u.safeSave(model, {
         'hidden': true,
-        'minimized': true,
         'time_minimized': new Date().toISOString()
     });
 }
 
 /**
- * Handler which gets called when a {@link _converse#ChatBox} has it's
- * `minimized` property set to false.
- *
  * Will trigger {@link _converse#chatBoxMaximized}
- * @param { ChatBox|MUC } model
+ * @param {ChatBox|MUC} model
  */
 function onMaximized (model) {
     if (!model.isScrolledUp()) {
@@ -189,7 +183,7 @@ function onMaximized (model) {
     /**
      * Triggered when a previously minimized chat gets maximized
      * @event _converse#chatBoxMaximized
-     * @type { ChatBox | MUC }
+     * @type {ChatBox|MUC}
      * @example _converse.api.listen.on('chatBoxMaximized', view => { ... });
      */
     api.trigger('chatBoxMaximized', model);
@@ -198,7 +192,7 @@ function onMaximized (model) {
 /**
  * Handler which gets called when a {@link _converse#ChatBox} has it's
  * `minimized` property set to true.
- * @param { ChatBox|MUC } model
+ * @param {ChatBox|MUC} model
  *
  * Will trigger {@link _converse#chatBoxMinimized}
  */
@@ -213,10 +207,10 @@ function onMinimized (model) {
 }
 
 /**
- * @param { ChatBox|MUC } model
+ * @param {ChatBox|MUC} model
  */
 export function onMinimizedChanged (model) {
-    if (model.get('minimized')) {
+    if (model.get('hidden')) {
         onMinimized(model);
     } else {
         onMaximized(model);

+ 1 - 1
src/plugins/minimize/view.js

@@ -13,7 +13,7 @@ export default class MinimizedChats extends CustomElement {
         this.listenTo(this.model, 'add', () => this.requestUpdate())
         this.listenTo(this.model, 'change:fullname', () => this.requestUpdate())
         this.listenTo(this.model, 'change:jid', () => this.requestUpdate())
-        this.listenTo(this.model, 'change:minimized', () => this.requestUpdate())
+        this.listenTo(this.model, 'change:hidden', () => this.requestUpdate())
         this.listenTo(this.model, 'change:name', () => this.requestUpdate())
         this.listenTo(this.model, 'change:num_unread', () => this.requestUpdate())
         this.listenTo(this.model, 'remove', () => this.requestUpdate())

+ 6 - 6
src/plugins/roomslist/tests/roomslist.js

@@ -63,7 +63,7 @@ describe("A list of open groupchats", function () {
         expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
         expect(roomspanel.querySelectorAll('.msgs-indicator').length).toBe(0);
 
-        view.model.set({'minimized': true});
+        u.minimize(view.model);
 
         const nick = mock.chatroom_names[0];
         await view.model.handleMessageStanza($msg({
@@ -87,7 +87,8 @@ describe("A list of open groupchats", function () {
         expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
         expect(roomspanel.querySelectorAll('.msgs-indicator').length).toBe(1);
         expect(roomspanel.querySelector('.msgs-indicator').textContent.trim()).toBe('2');
-        view.model.set({'minimized': false});
+
+        u.maximize(view.model);
         expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
         await u.waitUntil(() => roomspanel.querySelectorAll('.msgs-indicator').length === 0);
     }));
@@ -351,15 +352,14 @@ describe("A groupchat shown in the groupchats list", function () {
             allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
             }, async (_converse) => {
 
-        const { $msg } = converse.env;
-        const u = converse.env.utils;
+        const { $msg, u } = converse.env;
         await mock.openControlBox(_converse);
         const room_jid = 'kitchen@conference.shakespeare.lit';
         const rooms_list = document.querySelector('converse-rooms-list');
         await u.waitUntil(() => rooms_list !== undefined, 500);
         await mock.openAndEnterChatRoom(_converse, room_jid, 'romeo');
         const view = _converse.chatboxviews.get(room_jid);
-        view.model.set({'minimized': true});
+        u.minimize(view.model);
         const nick = mock.chatroom_names[0];
         await view.model.handleMessageStanza(
             $msg({
@@ -401,7 +401,7 @@ describe("A groupchat shown in the groupchats list", function () {
         await u.waitUntil(() => lview.querySelector(".msgs-indicator").textContent === '2', 1000);
 
         // When the chat gets maximized again, the unread indicators are removed
-        view.model.set({'minimized': false});
+        u.maximize(view.model);
         indicator_el = lview.querySelector(".msgs-indicator");
         expect(indicator_el === null);
         room_el = lview.querySelector(".available-chatroom");

+ 5 - 9
src/shared/avatar/avatar.js

@@ -52,15 +52,11 @@ export default class Avatar extends CustomElement {
             font: ${this.width / 2}px Arial;
             line-height: ${this.height}px;`;
 
-        try {
-            const author_style = this.model.getAvatarStyle(css);
-            return html`<div class="avatar-initials" style="${until(author_style, default_bg_css + css)}"
-                aria-label="${this.name}">
-                    ${this.getInitials(this.name)}
-            </div>`;
-        } catch (e) {
-            debugger;
-        }
+        const author_style = this.model.getAvatarStyle(css);
+        return html`<div class="avatar-initials" style="${until(author_style, default_bg_css + css)}"
+            aria-label="${this.name}">
+                ${this.getInitials(this.name)}
+        </div>`;
     }
 
     /**

+ 15 - 18
src/shared/chat/utils.js

@@ -43,29 +43,26 @@ export async function getHeadingStandaloneButton (promise_or_data) {
 }
 
 /**
- * @param {Promise} promise
+ * @param {Promise<Array<object>>} promise
  */
-export function getStandaloneButtons (promise) {
-    return promise.then(
-        btns => btns
-            .filter(b => b.standalone)
-            .map(b => getHeadingStandaloneButton(b))
-            .reverse()
-            .map(b => until(b, '')));
+export async function getStandaloneButtons (promise) {
+    const btns = await promise;
+    return btns
+        .filter((b) => b.standalone)
+        .map((b) => getHeadingStandaloneButton(b))
+        .reverse()
+        .map((b) => until(b, ''));
 }
 
 /**
- * @param {Promise} promise
+ * @param {Promise<Array<object>>} promise
  */
-export function getDropdownButtons (promise) {
-    return promise.then((btns) => {
-        const dropdown_btns = btns.filter((b) => !b.standalone).map((b) => getHeadingDropdownItem(b));
-        return dropdown_btns.length
-            ? html`<converse-dropdown
-                class="chatbox-btn btn-group dropstart"
-                .items=${dropdown_btns}></converse-dropdown>`
-            : '';
-    });
+export async function getDropdownButtons (promise) {
+    const btns = await promise;
+    const dropdown_btns = btns.filter((b) => !b.standalone).map((b) => getHeadingDropdownItem(b));
+    return dropdown_btns.length
+        ? html`<converse-dropdown class="chatbox-btn btn-group dropstart" .items=${dropdown_btns}></converse-dropdown>`
+        : '';
 }
 
 

+ 2 - 1
src/shared/tests/mock.js

@@ -704,7 +704,8 @@ async function _initConverse (settings) {
         play_sounds: false,
         use_emojione: false,
         theme,
-        view_mode
+        view_mode,
+        no_trimming: true,
     }, settings || {}));
 
     window._converse = _converse;