diff --git a/css/private_message_slide.css b/css/private_message_slide.css new file mode 100644 index 0000000000000000000000000000000000000000..f6380e1d231e9e79c4ad6cf9b3fa3584fffccf45 --- /dev/null +++ b/css/private_message_slide.css @@ -0,0 +1,14 @@ +.private-message-slide { + overflow: hidden; + transition: + height 300ms ease-out, + opacity 300ms ease-out; +} +.private-message-slide__collapsed { + height: 0; + opacity: 0; +} +.private-message-slide__expanded { + height: auto; + opacity: 1; +} diff --git a/css/private_message_slide.scss b/css/private_message_slide.scss new file mode 100644 index 0000000000000000000000000000000000000000..3fcc7df311204f2054de6307719d0ea3961ae298 --- /dev/null +++ b/css/private_message_slide.scss @@ -0,0 +1,17 @@ +.private-message-slide { + overflow: hidden; + transition: + height 300ms ease-out, + opacity 300ms ease-out; + + &__collapsed { + height: 0; + opacity: 0; + } + + &__expanded { + height: auto; + opacity: 1; + } + +} diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js index 7516f2950d5d68080b62480f1bf8bbe3a08c7b90..e36d9aa7569d5bcf8ff7f25ce797a09fb3a24f8a 100644 --- a/js/private_message_inbox_block.js +++ b/js/private_message_inbox_block.js @@ -3,271 +3,253 @@ * Adds JavaScript functionality to the private message inbox block. */ -Drupal.PrivateMessageInbox = {}; -Drupal.PrivateMessageInbox.updateInbox = {}; - -(($, Drupal, drupalSettings, window, once) => { - let container; - let updateInterval; - let loadingPrev; - let loadingNew; - +((Drupal, drupalSettings, window, once) => { /** - * Used to manually trigger Drupal's JavaScript commands. - * @param {Object} data The data. + * Private message inbox block functionality. */ - function triggerCommands(data) { - const ajaxObject = Drupal.ajax({ - url: '', - base: false, - element: false, - progress: false, - }); + class PrivateMessageInboxBlock { + constructor() { + this.container = null; + this.loadingPrevInProgress = false; + this.loadingNewInProgress = false; + this.updateTimeoutId = null; + } - // Trigger any ajax commands in the response. - ajaxObject.success(data, 'success'); - } + /** + * Initialize the block with default state and handlers. + * + * @param {HTMLElement} blockWrapper + * The inbox block. + */ + init(blockWrapper) { + this.container = blockWrapper; + const threadId = this.container.querySelector('.private-message-thread') + ?.dataset.threadId; + if (threadId) { + Drupal.PrivateMessageUtils.setActiveThread(threadId); + } + this.attachLoadOldButton(); + this.scheduleInboxUpdate(); + } - /** - * Updates the inbox after an Ajax call. - */ - function updateInbox() { - if (!loadingNew) { - loadingNew = true; + /** + * Updates the inbox with new threads. + */ + updateInbox() { + if (this.loadingNewInProgress) { + return; + } + this.loadingNewInProgress = true; const ids = {}; - if (container.length > 0) { - container[0] - .querySelectorAll('.private-message-thread-inbox') - .forEach((el) => { - ids[el.dataset.threadId] = el.dataset.lastUpdate; - }); - } + this.container + .querySelectorAll('.private-message-thread-inbox') + .forEach((el) => { + ids[el.dataset.threadId] = el.dataset.lastUpdate; + }); - $.ajax({ + Drupal.ajax({ url: drupalSettings.privateMessageInboxBlock.loadNewUrl, - method: 'POST', - data: { ids }, - success(data) { - loadingNew = false; - triggerCommands(data); - if (updateInterval) { - window.setTimeout(updateInbox, updateInterval); - } - }, - }); + submit: { ids }, + }) + .execute() + .always(() => { + this.loadingNewInProgress = false; + this.scheduleInboxUpdate(); + }); } - } - - /** - * Reorders the inbox after an Ajax Load, to show newest threads first. - * @param {Array} threadIds The threads IDs. - * @param {Array} newThreads The new Threads. - */ - function reorderInbox(threadIds, newThreads) { - const map = {}; - - container[0] - .querySelectorAll(':scope > .private-message-thread-inbox') - .forEach((el) => { - map[el.dataset.threadId] = $(el); - }); - - threadIds.forEach((threadId) => { - if (newThreads[threadId]) { - if (map[threadId]) { - map[threadId].remove(); - } - $('<div/>').html(newThreads[threadId]).contents().appendTo(container); - } else if (map[threadId]) { - container.append(map[threadId]); + /** + * Sets a timeout for inbox updates. + */ + scheduleInboxUpdate() { + if (this.updateTimeoutId) { + window.clearTimeout(this.updateTimeoutId); } - }); - - Drupal.attachBehaviors(container[0]); - } + const interval = + drupalSettings.privateMessageInboxBlock.ajaxRefreshRate * 1000; + if (interval) { + this.updateTimeoutId = window.setTimeout( + () => this.updateInbox(), + interval, + ); + } + } - /** - * Inserts older threads into the inbox after an Ajax load. - * @param {string} threads The threads HTML. - */ - function insertPreviousThreads(threads) { - const contents = $('<div/>').html(threads).contents(); + /** + * Appends older threads to the inbox. + * + * @param {string} threadsHtml + * HTML content of threads. + */ + insertPreviousThreads(threadsHtml) { + const newNodes = Drupal.PrivateMessageUtils.parseHTML(threadsHtml); - contents.css('display', 'none').appendTo(container).slideDown(300); - Drupal.attachBehaviors(contents[0]); - } + Array.from(newNodes).forEach((node) => { + const appendedElement = this.container.appendChild(node); - /** - * Adds CSS classes to the currently selected thread. - * @param {string} threadId The thread id. - */ - function setActiveThread(threadId) { - container.find('.active-thread:first').removeClass('active-thread'); - container - .find(`.private-message-thread[data-thread-id="${threadId}"]:first`) - .removeClass('unread-thread') - .addClass('active-thread'); - } + Drupal.attachBehaviors(appendedElement); + Drupal.PrivateMessageSlide.toggleSlide(appendedElement, true); + }); + } - /** - * Click handler for the button that loads older threads into the inbox. - * @param {Object} e The event. - */ - function loadOldThreadWatcherHandler(e) { - e.preventDefault(); + /** + * Handles loading older threads. + * + * @param {Event} e + * The click event. + */ + loadOldThreads(e) { + e.preventDefault(); + if (this.loadingPrevInProgress) { + return; + } - if (!loadingPrev) { - loadingPrev = true; + this.loadingPrevInProgress = true; - let oldestTimestamp; - container[0].querySelectorAll('.private-message-thread').forEach((el) => { - const timestamp = parseInt(el.dataset.lastUpdate, 10); - oldestTimestamp = !oldestTimestamp - ? timestamp - : Math.min(timestamp, oldestTimestamp); - }); + const oldestTimestamp = Array.from( + this.container.querySelectorAll('.private-message-thread'), + ).reduce((minTime, el) => { + return Math.min(minTime, parseInt(el.dataset.lastUpdate, 10)); + }, Infinity); - $.ajax({ + Drupal.ajax({ url: drupalSettings.privateMessageInboxBlock.loadPrevUrl, - data: { + submit: { timestamp: oldestTimestamp, count: drupalSettings.privateMessageInboxBlock.threadCount, }, - success(data) { - loadingPrev = false; - triggerCommands(data); - }, - }); + }) + .execute() + .always(() => { + this.loadingPrevInProgress = false; + }); } - } - /** - * Watches the button that loads previous threads into the inbox. - * @param {Object} context The context. - */ - function loadOlderThreadWatcher(context) { - once( - 'load-older-threads-watcher', - '#load-previous-threads-button', - context, - ).forEach((el) => { - el.addEventListener('click', loadOldThreadWatcherHandler); - }); - } - - /** - * Click Handler executed when private message threads are clicked. - * - * Loads the thread into the private message window. - * @param {Event} e The event. - */ - const inboxThreadLinkListenerHandler = (e) => { - if (Drupal.PrivateMessages) { - e.preventDefault(); - const threadId = e.currentTarget.dataset.threadId; - Drupal.PrivateMessages.loadThread(threadId); - setActiveThread(threadId); + /** + * Reorders the inbox to show the newest threads first. + * + * @param {Array} threadIds + * Thread IDs in the desired order. + * @param {Array} newThreads + * HTML content of new threads keyed by thread ID. + */ + reorderInbox(threadIds, newThreads) { + const existingThreads = {}; + + this.container + .querySelectorAll(':scope > .private-message-thread-inbox') + .forEach((el) => { + existingThreads[el.dataset.threadId] = el; + }); + + threadIds.forEach((threadId) => { + if (newThreads[threadId]) { + if (existingThreads[threadId]) { + existingThreads[threadId].remove(); + } + const newThreadContent = Drupal.PrivateMessageUtils.parseHTML( + newThreads[threadId], + ); + Array.from(newThreadContent).forEach((child) => { + const appendedElement = this.container.appendChild(child); + Drupal.attachBehaviors(appendedElement); + }); + } else if (existingThreads[threadId]) { + const appendedElement = this.container.appendChild( + existingThreads[threadId], + ); + Drupal.attachBehaviors(appendedElement); + } + }); } - }; - - /** - * Watches private message threads for clicks, so new threads can be loaded. - * @param {Object} context The context. - */ - function inboxThreadLinkListener(context) { - once( - 'inbox-thread-link-listener', - '.private-message-inbox-thread-link', - context, - ).forEach((el) => { - el.addEventListener('click', inboxThreadLinkListenerHandler); - }); - } - - /** - * Initializes the private message inbox JavaScript. - */ - function init(context) { - $( - once( - 'init-inbox-block', - '.block-private-message-inbox-block .private-message-thread--full-container', - context, - ), - ).each(function () { - container = $(this); - - const threadId = container - .children('.private-message-thread:first') - .attr('data-thread-id'); - setActiveThread(threadId); + /** + * Attaches the "Load Older Threads" button handler. + */ + attachLoadOldButton() { if ( drupalSettings.privateMessageInboxBlock.totalThreads > drupalSettings.privateMessageInboxBlock.itemsToShow ) { - $('<div/>', { id: 'load-previous-threads-button-wrapper' }) - .append( - $('<a/>', { href: '#', id: 'load-previous-threads-button' }).text( - Drupal.t('Load Previous'), - ), - ) - .insertAfter(container); - loadOlderThreadWatcher(document); - } - updateInterval = - drupalSettings.privateMessageInboxBlock.ajaxRefreshRate * 1000; - if (updateInterval) { - window.setTimeout(updateInbox, updateInterval); + Drupal.privateMessageInboxPrevious.displayButton(this.container, (e) => + this.loadOldThreads(e), + ); } - }); + } } + const privateMessageInboxBlock = new PrivateMessageInboxBlock(); + + /** + * Attaches the private message inbox block behavior. + */ Drupal.behaviors.privateMessageInboxBlock = { attach(context) { - init(context); - inboxThreadLinkListener(context); - - Drupal.AjaxCommands.prototype.insertInboxOldPrivateMessageThreads = ( - ajax, - response, - ) => { - if (response.threads) { - insertPreviousThreads(response.threads); - } - if (!response.threads || !response.hasNext) { - $('#load-previous-threads-button') - .parent() - .slideUp(300, function () { - $(this).remove(); - }); - } - }; - - Drupal.AjaxCommands.prototype.privateMessageInboxUpdate = ( - ajax, - response, - ) => reorderInbox(response.threadIds, response.newThreads); - - Drupal.AjaxCommands.prototype.privateMessageTriggerInboxUpdate = () => - updateInbox(); + const containerFormContext = once( + 'private-message-inbox-block', + '.block-private-message-inbox-block .private-message-thread--full-container', + context, + ).shift(); - if (Drupal.PrivateMessages) { - Drupal.PrivateMessages.setActiveThread = (id) => setActiveThread(id); + if (!containerFormContext) { + return; } - Drupal.PrivateMessageInbox.updateInbox = () => updateInbox(); + privateMessageInboxBlock.init(containerFormContext); }, detach(context) { - $(context) - .find('#load-previous-threads-button') - .unbind('click', loadOldThreadWatcherHandler); - $(context) - .find('.private-message-inbox-thread-link') - .unbind('click', inboxThreadLinkListenerHandler); + Drupal.privateMessageInboxPrevious.detachEventListener( + context, + privateMessageInboxBlock.loadOldThreads.bind(privateMessageInboxBlock), + ); }, }; -})(jQuery, Drupal, drupalSettings, window, once); + + /** + * Custom AJAX commands for private message inbox. + * + * @param {Drupal.Ajax} ajax + * The Drupal Ajax object. + * @param {object} response + * Object holding the server response. + */ + Drupal.AjaxCommands.prototype.insertInboxOldPrivateMessageThreads = ( + ajax, + response, + ) => { + if (response.threads) { + privateMessageInboxBlock.insertPreviousThreads(response.threads); + } + + if (!response.threads || !response.hasNext) { + Drupal.privateMessageInboxPrevious.slideDownButton(); + } + }; + + /** + * Custom AJAX command to update the inbox with new threads. + * + * @param {Drupal.Ajax} ajax + * The Drupal Ajax object. + * @param {object} response + * Object holding the server response. + */ + Drupal.AjaxCommands.prototype.privateMessageInboxUpdate = ( + ajax, + response, + ) => { + privateMessageInboxBlock.reorderInbox( + response.threadIds, + response.newThreads, + ); + }; + + /** + * Custom AJAX command to trigger an inbox update. + */ + Drupal.AjaxCommands.prototype.privateMessageTriggerInboxUpdate = () => { + privateMessageInboxBlock.updateInbox(); + }; +})(Drupal, drupalSettings, window, once); diff --git a/js/private_message_inbox_previous.js b/js/private_message_inbox_previous.js new file mode 100644 index 0000000000000000000000000000000000000000..d470b7855d9add499d40bb1f943407b78cb57659 --- /dev/null +++ b/js/private_message_inbox_previous.js @@ -0,0 +1,61 @@ +/** + * @file + * Adds JavaScript functionality to the inbox previous button. + */ + +((Drupal) => { + /** + * Previous functionality. + */ + Drupal.privateMessageInboxPrevious = { + wrapperId: 'load-previous-threads-button-wrapper', + buttonId: 'load-previous-threads-button', + + /** + * Display the previous button. + * + * @param {HTMLElement} blockElement + * The element where the button will be added. + * @param {Function} callback + * The function to call on button click. + */ + displayButton(blockElement, callback) { + blockElement.insertAdjacentHTML( + 'afterend', + `<div id="${this.wrapperId}"> + <a href="#" id="${this.buttonId}" aria-label="${Drupal.t( + 'Load previous threads', + )}">${Drupal.t('Load Previous')}</a> + </div>`, + ); + + const loadPreviousButton = document.getElementById(this.buttonId); + loadPreviousButton.addEventListener('click', callback); + }, + + /** + * Slides done the button. + */ + slideDownButton() { + const buttonWrapper = document.getElementById(this.wrapperId); + if (buttonWrapper) { + Drupal.PrivateMessageSlide.toggleSlide(buttonWrapper, false); + } + }, + + /* + * Detaches event listener. + * + * @param {Document|HTMLElement} context + * The context to search for the button. + * @param {Function} callback + * The function to detach from the click event. + */ + detachEventListener(context, callback) { + const button = context.querySelector(`#${this.buttonId}`); + if (button) { + button.removeEventListener('click', callback); + } + }, + }; +})(Drupal); diff --git a/js/private_message_notification_block.js b/js/private_message_notification_block.js index 9cd4aeb9b48fec5eda1d2d7ea17e7749dd2f72fe..2a0ce56eadb8969729841a1e31986eb036db66b0 100644 --- a/js/private_message_notification_block.js +++ b/js/private_message_notification_block.js @@ -4,8 +4,8 @@ */ ((Drupal, drupalSettings, window) => { - let refreshRate; let checkingCountInProgress = false; + let updateTimeoutId = null; /** * Private message notification block. @@ -87,6 +87,24 @@ pageTitle.textContent = pageTitle.textContent.replace(titlePattern, ''); } }, + + /** + * Sets a timeout for count updates. + */ + scheduleCountUpdate() { + if (updateTimeoutId) { + window.clearTimeout(updateTimeoutId); + } + + const refreshRate = + drupalSettings.privateMessageNotificationBlock.ajaxRefreshRate * 1000; + if (refreshRate) { + updateTimeoutId = window.setTimeout( + Drupal.privateMessageNotificationBlock.triggerCountCallback, + refreshRate, + ); + } + }, }; /** @@ -102,14 +120,8 @@ context, ).shift(); - refreshRate = - drupalSettings.privateMessageNotificationBlock.ajaxRefreshRate * 1000; - - if (notificationWrapper && refreshRate) { - window.setTimeout( - Drupal.privateMessageNotificationBlock.triggerCountCallback, - refreshRate, - ); + if (notificationWrapper) { + Drupal.privateMessageNotificationBlock.scheduleCountUpdate(); } }, }; @@ -131,11 +143,6 @@ ); checkingCountInProgress = false; - if (refreshRate) { - window.setTimeout( - Drupal.privateMessageNotificationBlock.triggerCountCallback, - refreshRate, - ); - } + Drupal.privateMessageNotificationBlock.scheduleCountUpdate(); }; })(Drupal, drupalSettings, window); diff --git a/js/private_message_slide.js b/js/private_message_slide.js new file mode 100644 index 0000000000000000000000000000000000000000..dcd6307601af07ff7c07a21e5ce65cf082b614d4 --- /dev/null +++ b/js/private_message_slide.js @@ -0,0 +1,45 @@ +/** + * @file + * + * Slide functions for Private Message module. + */ + +((Drupal) => { + Drupal.PrivateMessageSlide = Drupal.PrivateMessageSlide || {}; + + /** + * Toggles slide-up and slide-down effects. + * + * @param {HTMLElement} element + * The element to toggle. + * @param {boolean} expand + * True to slide down, false to slide up. + */ + Drupal.PrivateMessageSlide.toggleSlide = (element, expand) => { + // Ensure the element is valid + if (!element) { + return; + } + + // Add the base slide class if not already present + element.classList.add('private-message-slide'); + + if (expand) { + element.style.height = '0'; + element.classList.remove('private-message-slide__collapsed'); + + requestAnimationFrame(() => { + element.classList.add('private-message-slide__expanded'); + element.style.height = `${element.scrollHeight}px`; + }); + } else { + element.style.height = `${element.scrollHeight}px`; + element.classList.remove('private-message-slide__expanded'); + + requestAnimationFrame(() => { + element.classList.add('private-message-slide__collapsed'); + element.style.height = '0'; + }); + } + }; +})(Drupal); diff --git a/js/private_message_thread.js b/js/private_message_thread.js index 288af094bc50abc43b036f82f02be893f4380b38..95922af68bc7a1ced0e8b56ac3a20c8cb2654816 100644 --- a/js/private_message_thread.js +++ b/js/private_message_thread.js @@ -338,8 +338,31 @@ Drupal.PrivateMessages.threadChange = {}; } } + /** + * Click Handler executed when private message threads are clicked. + * + * Loads the thread into the private message window. + * @param {Event} e The event. + */ + function inboxThreadLinkListenerHandler(e) { + e.preventDefault(); + const { threadId } = e.currentTarget.dataset; + if (threadId) { + Drupal.PrivateMessages.loadThread(threadId); + Drupal.PrivateMessageUtils.setActiveThread(threadId); + } + } + Drupal.behaviors.privateMessageThread = { attach(context) { + once( + 'inbox-thread-link-listener', + '.private-message-inbox-thread-link', + context, + ).forEach((el) => { + el.addEventListener('click', inboxThreadLinkListenerHandler); + }); + init(); loadPreviousListener(context); currentThreadId = threadWrapper @@ -414,6 +437,14 @@ Drupal.PrivateMessages.threadChange = {}; $(context) .find('#load-previous-messages') .unbind('click', loadPreviousListenerHandler); + + // Unbind the 'click' event from '.private-message-inbox-thread-link' elements + const inboxThreadLinks = context.querySelectorAll( + '.private-message-inbox-thread-link', + ); + inboxThreadLinks.forEach((link) => { + link.removeEventListener('click', inboxThreadLinkListenerHandler); + }); }, }; diff --git a/js/private_message_utils.js b/js/private_message_utils.js new file mode 100644 index 0000000000000000000000000000000000000000..4ecdf637e95445bb8312df48838f1a84ce9c2c01 --- /dev/null +++ b/js/private_message_utils.js @@ -0,0 +1,46 @@ +/** + * @file + * Shared utility functions for Private Message module. + */ + +((Drupal) => { + Drupal.PrivateMessageUtils = { + /** + * Creates a temporary DOM container. + * + * @param {string} html + * The HTML string to parse. + * @return {NodeListOf<ChildNode>} + * Parsed HTML content as NodeList. + */ + parseHTML(html) { + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = html; + + return document.createDocumentFragment().appendChild(tempDiv).childNodes; + }, + + /** + * Sets the active thread visually. + * + * @param {string} threadId + * The thread ID. + */ + setActiveThread(threadId) { + const activeThread = document.querySelector( + '.private-message-thread--full-container .active-thread', + ); + if (activeThread) { + activeThread.classList.remove('active-thread'); + } + + const targetThread = document.querySelector( + `.private-message-thread--full-container .private-message-thread[data-thread-id="${threadId}"]`, + ); + if (targetThread) { + targetThread.classList.remove('unread-thread'); + targetThread.classList.add('active-thread'); + } + }, + }; +})(Drupal); diff --git a/private_message.libraries.yml b/private_message.libraries.yml index fdc3ed30d0ab5a69aa23f203797d1db4f4b20843..77a12bff50db3ff2ef6d2b41b10d689690b98c52 100644 --- a/private_message.libraries.yml +++ b/private_message.libraries.yml @@ -2,14 +2,31 @@ history_api: js: js/history_api.js: {} +utils: + js: + js/private_message_utils.js: {} + dependencies: + - core/drupal + +slide: + js: + js/private_message_slide.js: { } + css: + theme: + css/private_message_slide.css: { } + dependencies: + - core/drupal + inbox_block_script: js: + js/private_message_inbox_previous.js: {} js/private_message_inbox_block.js: {} dependencies: - - core/jquery - core/once - core/drupal.ajax - core/drupalSettings + - private_message/slide + - private_message/utils inbox_block_style: css: @@ -37,6 +54,7 @@ private_message_thread_script: - core/drupalSettings - core/once - private_message/history_api + - private_message/utils private_message_thread_style: css: diff --git a/tests/src/FunctionalJavascript/InboxBlockTest.php b/tests/src/FunctionalJavascript/InboxBlockTest.php index 4cec4f2131c81bf94a581818a0c71cd4567f66a2..5f68a3d9c9e02e8ed7df9066d2de4bff31e666b5 100644 --- a/tests/src/FunctionalJavascript/InboxBlockTest.php +++ b/tests/src/FunctionalJavascript/InboxBlockTest.php @@ -41,7 +41,7 @@ class InboxBlockTest extends WebDriverTestBase { public function setUp(): void { parent::setUp(); $this->attachFullNameField(); - $this->createTestingUsers(3); + $this->createTestingUsers(4); $this->threads[] = $this->createThreadWithMessages([ $this->users['a'], @@ -85,6 +85,12 @@ class InboxBlockTest extends WebDriverTestBase { * Tests load previous functionality. */ public function testLoadPrevious(): void { + // 2 more threads, together we have 4. + $this->createThreadWithMessages([ + $this->users['a'], + $this->users['d'], + ], $this->users['d']); + $this->createThreadWithMessages([ $this->users['a'], $this->users['b'], @@ -93,7 +99,7 @@ class InboxBlockTest extends WebDriverTestBase { $settings = [ 'thread_count' => 1, - 'ajax_load_count' => 1, + 'ajax_load_count' => 2, 'ajax_refresh_rate' => 100, ]; $this->drupalPlaceBlock('private_message_inbox_block', $settings); @@ -105,13 +111,13 @@ class InboxBlockTest extends WebDriverTestBase { ->getPage() ->clickLink('Load Previous'); $this->assertSession()->assertWaitOnAjaxRequest(); - $this->assertEquals(2, $this->countThreads()); + $this->assertEquals(3, $this->countThreads()); $this->getSession() ->getPage() ->clickLink('Load Previous'); $this->assertSession()->assertWaitOnAjaxRequest(); - $this->assertEquals(3, $this->countThreads()); + $this->assertEquals(4, $this->countThreads()); $this->assertSession() ->pageTextNotContains('Load Previous'); } @@ -127,7 +133,12 @@ class InboxBlockTest extends WebDriverTestBase { $this->drupalPlaceBlock('private_message_inbox_block', $settings); $this->drupalLogin($this->users['a']); - $lastThread = $this->createThreadWithMessages([ + $this->threads[] = $this->createThreadWithMessages([ + $this->users['a'], + $this->users['d'], + ], $this->users['d']); + + $this->threads[] = $this->createThreadWithMessages([ $this->users['a'], $this->users['b'], $this->users['c'], @@ -135,9 +146,23 @@ class InboxBlockTest extends WebDriverTestBase { $this->assertSession()->assertWaitOnAjaxRequest(); - $this->assertSession() - ->elementExists('css', '.private-message-thread-inbox[data-thread-id="' . $lastThread->id() . '"]'); - $this->assertEquals(3, $this->countThreads()); + $this->assertEquals(4, $this->countThreads()); + + $threadElements = $this->getSession() + ->getPage() + ->findAll('css', '.private-message-thread-inbox'); + $threadIds = []; + foreach ($threadElements as $threadElement) { + $threadIds[] = $threadElement->getAttribute('data-thread-id'); + } + + $expectedOrder = []; + foreach ($this->threads as $thread) { + $expectedOrder[] = $thread->id(); + } + $expectedOrder = array_reverse($expectedOrder); + + $this->assertEquals($expectedOrder, $threadIds, 'Threads are not in the expected order.'); } }