Work on turning chat views into custom elements

The eventual goal is to avoid UI-related stanza processing if the relevant chats
aren't in the DOM.

With the current architecture, chatboxes are created (and the stanzas
related to them processed) even if `#conversejs` isn't in the DOM.

* Initial work on making controlbox an element
* Create a shared base class
* Ceate ChatBoxViews proxy
* Update sass now that certain classes are moved to converse-chats element
This commit is contained in:
JC Brand 2020-12-08 12:54:14 +01:00
parent eb65f75f45
commit 1949356ede
84 changed files with 2400 additions and 2337 deletions

View File

@ -24,6 +24,9 @@ The [afterMessageBodyTransformed](https://conversejs.org/docs/html/api/-_convers
When leaving a MUC, the message history is deleted. This means that decrypted
OMEMO messages are gone and cannot be recovered on that device. See [muc_clear_messages_on_leave](https://conversejs.org/docs/html/configuration.html#muc-clear-messages-on-leave).
Removed events:
* `chatBoxInsertedIntoDOM`
## 7.0.2 (2020-11-23)
- Updated translations: de, nb, gl, tr

38
package-lock.json generated
View File

@ -4483,9 +4483,9 @@
}
},
"@octokit/openapi-types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.0.0.tgz",
"integrity": "sha512-J4bfM7lf8oZvEAdpS71oTvC1ofKxfEZgU5vKVwzZKi4QPiL82udjpseJwxPid9Pu2FNmyRQOX4iEj6W1iOSnPw==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.0.1.tgz",
"integrity": "sha512-9AuC04PUnZrjoLiw3uPtwGh9FE4Q3rTqs51oNlQ0rkwgE8ftYsOC+lsrQyvCvWm85smBbSc0FNRKKumvGyb44Q==",
"dev": true
},
"@octokit/plugin-enterprise-rest": {
@ -4629,12 +4629,12 @@
}
},
"@octokit/types": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.1.1.tgz",
"integrity": "sha512-btm3D6S7VkRrgyYF31etUtVY/eQ1KzrNRqhFt25KSe2mKlXuLXJilglRC6eDA2P6ou94BUnk/Kz5MPEolXgoiw==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.1.2.tgz",
"integrity": "sha512-LPCpcLbcky7fWfHCTuc7tMiSHFpFlrThJqVdaHgowBTMS0ijlZFfonQC/C1PrZOjD4xRCYgBqH9yttEATGE/nw==",
"dev": true,
"requires": {
"@octokit/openapi-types": "^2.0.0",
"@octokit/openapi-types": "^2.0.1",
"@types/node": ">= 8"
}
},
@ -7099,9 +7099,9 @@
"dev": true
},
"conventional-changelog-writer": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.18.tgz",
"integrity": "sha512-mAQDCKyB9HsE8Ko5cCM1Jn1AWxXPYV0v8dFPabZRkvsiWUul2YyAqbIaoMKF88Zf2ffnOPSvKhboLf3fnjo5/A==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz",
"integrity": "sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw==",
"dev": true,
"requires": {
"compare-func": "^2.0.0",
@ -11272,9 +11272,9 @@
}
},
"git-url-parse": {
"version": "11.4.0",
"resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.4.0.tgz",
"integrity": "sha512-KlIa5jvMYLjXMQXkqpFzobsyD/V2K5DRHl5OAf+6oDFPlPLxrGDVQlIdI63c4/Kt6kai4kALENSALlzTGST3GQ==",
"version": "11.4.3",
"resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.4.3.tgz",
"integrity": "sha512-LZTTk0nqJnKN48YRtOpR8H5SEfp1oM2tls90NuZmBxN95PnCvmuXGzqQ4QmVirBgKx2KPYfPGteX3/raWjKenQ==",
"dev": true,
"requires": {
"git-up": "^4.0.0"
@ -13709,9 +13709,9 @@
}
},
"meow": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-8.0.0.tgz",
"integrity": "sha512-nbsTRz2fwniJBFgUkcdISq8y/q9n9VbiHYbfwklFh5V4V2uAcxtKQkDc0yCLPM/kP0d+inZBewn3zJqewHE7kg==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-8.1.0.tgz",
"integrity": "sha512-fNWkgM1UVMey2kf24yLiccxLihc5W+6zVus3/N0b+VfnJgxV99E9u04X6NAiKdg6ED7DAQBX5sy36NM0QJZkWA==",
"dev": true,
"requires": {
"@types/minimist": "^1.2.0",
@ -22460,9 +22460,9 @@
},
"dependencies": {
"ws": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz",
"integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==",
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==",
"optional": true
}
}

View File

@ -100,7 +100,7 @@
transition: .3s cubic-bezier(.4,.2,.5,1.4);
transform-origin: 1.43em -.43em;
}
.suggestion-box > ul[hidden],
.suggestion-box > ul:empty {
opacity: 0;
@ -109,31 +109,33 @@
transition-timing-function: ease;
}
}
.suggestion-box > ul > li[aria-selected="true"] {
background: var(--completion-dark-color);
color: var(--inverse-link-color);
}
.suggestion-box li:hover mark {
background: var(--completion-light-color);
color: var(--inverse-link-color);
}
.suggestion-box li[aria-selected="true"] mark {
background: var(--completion-normal-color);
color: inherit;
}
}
#conversejs.converse-fullscreen {
.suggestion-box__results--above {
bottom: 4.5em;
}
}
#conversejs.converse-overlayed {
.suggestion-box__results--above {
bottom: 3.5em;
converse-chats {
.converse-fullscreen {
.suggestion-box__results--above {
bottom: 4.5em;
}
}
.converse-overlayed {
.suggestion-box__results--above {
bottom: 3.5em;
}
}
}
}

View File

@ -396,8 +396,8 @@
/* ******************* Overlay and embedded styles *************************** */
#conversejs.converse-embedded,
#conversejs.converse-overlayed {
converse-chats.converse-embedded,
converse-chats.converse-overlayed {
.controlbox-head {
padding: 0.5em;
}
@ -416,7 +416,7 @@
}
}
#conversejs.converse-overlayed {
converse-chats.converse-overlayed {
.chat-head, .box-flyout {
border-top-left-radius: var(--chatbox-border-radius);
@ -467,7 +467,7 @@
}
@include media-breakpoint-down(sm) {
#conversejs.converse-overlayed {
converse-chats.converse-overlayed {
> .row {
flex-direction: column;
&.no-gutters {
@ -478,8 +478,8 @@
}
#conversejs.converse-embedded,
#conversejs.converse-fullscreen {
converse-chats.converse-embedded,
converse-chats.converse-fullscreen {
.flyout {
border-radius: 0;
border:none;
@ -527,7 +527,7 @@
}
}
#conversejs.converse-embedded {
converse-chats.converse-embedded {
.chat-head {
font-size: var(--font-size-huge);
}
@ -558,7 +558,7 @@
/* ******************* Fullpage styles *************************** */
#conversejs.converse-fullscreen {
converse-chats.converse-fullscreen {
.chatbox-btn {
font-size: var(--fullpage-chatbox-button-size);
margin: 0 0.3em;
@ -595,7 +595,7 @@
}
@include media-breakpoint-down(sm) {
#conversejs:not(.converse-embedded) {
converse-chats:not(.converse-embedded) {
> .row {
flex-direction: row-reverse;
}
@ -617,9 +617,9 @@
}
}
#conversejs.converse-mobile,
#conversejs.converse-overlayed,
#conversejs.converse-fullscreen {
converse-chats.converse-mobile,
converse-chats.converse-overlayed,
converse-chats.converse-fullscreen {
.chatbox {
.box-flyout {
.chatbox-navback {

View File

@ -396,126 +396,128 @@
/* ******************* Overlay styles *************************** */
#conversejs.converse-overlayed {
.chatbox {
&.chatroom {
min-width: var(--chatroom-width) !important;
width: var(--chatroom-width);
.box-flyout {
converse-chats {
.converse-overlayed {
.chatbox {
&.chatroom {
min-width: var(--chatroom-width) !important;
width: var(--chatroom-width);
}
.chatbox-title__text {
@include make-col(7);
}
.chatbox-title__buttons {
@include make-col(5);
}
.box-flyout {
min-width: var(--chatroom-width) !important;
width: var(--chatroom-width);
}
.chatbox-title__text {
@include make-col(7);
}
.chatbox-title__buttons {
@include make-col(5);
}
.chat-head__desc {
font-size: 80%;
margin-bottom: 1em;
}
.chatroom-body {
.occupants {
.occupants-heading {
padding: 0;
}
.occupant-list {
border-bottom: none;
}
ul {
.occupant {
.occupant-nick-badge {
.occupant-badges {
display: none;
.chat-head__desc {
font-size: 80%;
margin-bottom: 1em;
}
.chatroom-body {
.occupants {
.occupants-heading {
padding: 0;
}
.occupant-list {
border-bottom: none;
}
ul {
.occupant {
.occupant-nick-badge {
.occupant-badges {
display: none;
}
}
.occupant-status {
margin-top: 6px;
}
}
.occupant-status {
margin-top: 6px;
}
}
}
}
.chat-area {
min-width: var(--overlayed-chat-width);
.chat-area {
min-width: var(--overlayed-chat-width);
}
}
}
}
}
}
#conversejs.converse-embedded,
#conversejs.converse-fullscreen,
#conversejs.converse-mobile {
.converse-embedded,
.converse-fullscreen,
.converse-mobile {
.chatroom {
.box-flyout {
width: 100%;
.chatroom {
.box-flyout {
width: 100%;
.chatroom-body {
.chat-area {
&.full {
.new-msgs-indicator {
max-width: 100%;
.chatroom-body {
.chat-area {
&.full {
.new-msgs-indicator {
max-width: 100%;
}
}
}
.occupants {
padding: var(--occupants-padding);
.occupants-heading {
font-size: var(--font-size-large);
}
ul {
&.occupant-list {
li {
font-size: var(--font-size-small);
}
}
}
}
}
}
.room-invite {
span {
.invited-contact {
margin: 0 0 0.5em -1px;
}
}
}
}
}
.converse-embedded {
.chatroom {
margin: 0;
width: 100%;
.box-flyout {
.occupants-heading {
font-size: 120%;
}
.chat-content {
.chat-message {
margin: 0.5em;
font-size: 120%;
}
}
.sendXMPPMessage {
.chat-textarea {
padding: 0.5em;
font-size: 110%;
}
}
.chatroom-body {
height: 100%;
.chatroom-form-container {
height: 100%;
position: relative;
}
}
.occupants {
padding: var(--occupants-padding);
.occupants-heading {
font-size: var(--font-size-large);
.occupant-list {
padding-left: 0.3em;
}
ul {
&.occupant-list {
li {
font-size: var(--font-size-small);
}
}
}
}
}
}
.room-invite {
span {
.invited-contact {
margin: 0 0 0.5em -1px;
}
}
}
}
}
#conversejs.converse-embedded {
.chatroom {
margin: 0;
width: 100%;
.box-flyout {
.occupants-heading {
font-size: 120%;
}
.chat-content {
.chat-message {
margin: 0.5em;
font-size: 120%;
}
}
.sendXMPPMessage {
.chat-textarea {
padding: 0.5em;
font-size: 110%;
}
}
.chatroom-body {
height: 100%;
.chatroom-form-container {
height: 100%;
position: relative;
}
}
.occupants {
.occupant-list {
padding-left: 0.3em;
}
}
}
@ -524,9 +526,9 @@
@include media-breakpoint-down(sm) {
#conversejs.converse-mobile,
#conversejs.converse-overlayed,
#conversejs.converse-fullscreen {
converse-chats.converse-mobile,
converse-chats.converse-overlayed,
converse-chats.converse-fullscreen {
.chatbox {
.box-flyout {
.chat-head-chatroom {

View File

@ -384,192 +384,218 @@
}
}
#conversejs.converse-overlayed {
.toggle-controlbox {
order: -1;
text-align: center;
background-color: var(--link-color);
border-top-left-radius: var(--button-border-radius);
border-top-right-radius: var(--button-border-radius);
color: #0a0a0a;
float: right;
height: 100%;
margin: 0 var(--chat-gutter);
padding: 1em;
span {
color: var(--inverse-link-color);
converse-chats {
.converse-overlayed {
.toggle-controlbox {
order: -1;
text-align: center;
background-color: var(--link-color);
border-top-left-radius: var(--button-border-radius);
border-top-right-radius: var(--button-border-radius);
color: #0a0a0a;
float: right;
height: 100%;
margin: 0 var(--chat-gutter);
padding: 1em;
span {
color: var(--inverse-link-color);
}
}
}
#controlbox {
min-width: var(--controlbox-width) !important;
width: var(--controlbox-width);
.box-flyout {
#controlbox {
min-width: var(--controlbox-width) !important;
width: var(--controlbox-width);
}
.box-flyout {
min-width: var(--controlbox-width) !important;
width: var(--controlbox-width);
}
.login-trusted {
white-space: nowrap;
font-size: 90%;
}
.login-trusted {
white-space: nowrap;
font-size: 90%;
}
#converse-login-trusted {
margin-top: 0.5em;
}
&:not(.logged-out) {
.controlbox-head {
height: 15px;
}
}
#converse-login-trusted {
margin-top: 0.5em;
}
&:not(.logged-out) {
.controlbox-head {
height: 15px;
}
}
display: flex;
flex-direction: row-reverse;
flex-wrap: nowrap;
justify-content: space-between;
min-height: 0;
.controlbox-head {
display: flex;
flex-direction: row-reverse;
flex-wrap: nowrap;
justify-content: space-between;
min-height: 0;
.brand-heading {
color: var(--controlbox-text-color);
font-size: 2em;
}
.chatbox-btn {
color: var(--controlbox-head-color);
margin: 0;
}
}
#converse-register, #converse-login {
@include make-col(12);
padding-bottom: 0;
}
#converse-register {
.button-cancel {
font-size: 90%;
}
}
.controlbox-panes {
border-radius: var(--chatbox-border-radius);
}
}
}
#conversejs.converse-embedded,
#conversejs.converse-fullscreen{
.toggle-controlbox {
display: none;
}
}
#conversejs.converse-embedded,
#conversejs.converse-fullscreen,
#conversejs.converse-mobile {
#controlbox {
@include make-col-ready();
@include media-breakpoint-up(md) {
@include make-col(4);
}
@include media-breakpoint-up(lg) {
@include make-col(3);
}
@include media-breakpoint-up(xl) {
@include make-col(2);
}
&.logged-out {
@include make-col(12);
}
margin: 0;
.controlbox-pane {
border-radius: 0;
}
.flyout {
border-radius: 0;
}
#converse-login-panel {
border-radius: 0;
.converse-form {
padding: 3em 2em 3em;
}
}
.toggle-register-login {
line-height: var(--line-height-huge);
}
converse-brand-logo {
@include make-col(12);
margin-top: 5em;
margin-bottom: 1em;
.brand-heading {
width: 100%;
font-size: 500%;
padding: 0.7em 0 0 0;
opacity: 0.8;
color: var(--brand-heading-color);
}
.brand-subtitle {
font-size: 90%;
padding: 0.5em;
}
@media screen and (max-width: $mobile-portrait-length) {
.brand-heading {
font-size: 300%;
color: var(--controlbox-text-color);
font-size: 2em;
}
.chatbox-btn {
color: var(--controlbox-head-color);
margin: 0;
}
}
#converse-register, #converse-login {
@include make-col(12);
padding-bottom: 0;
}
#converse-register {
.button-cancel {
font-size: 90%;
}
}
.controlbox-panes {
border-radius: var(--chatbox-border-radius);
}
}
}
.converse-embedded,
.converse-fullscreen{
.toggle-controlbox {
display: none;
}
}
.converse-embedded,
.converse-fullscreen,
.converse-mobile {
#controlbox {
@include make-col-ready();
@include media-breakpoint-up(md) {
@include make-col(4);
}
@include media-breakpoint-up(lg) {
@include make-col(3);
}
@include media-breakpoint-up(xl) {
@include make-col(2);
}
&.logged-out {
@include make-col(12);
}
margin: 0;
.controlbox-pane {
border-radius: 0;
}
.flyout {
border-radius: 0;
}
#converse-login-panel {
border-radius: 0;
.converse-form {
padding: 3em 2em 3em;
}
}
.toggle-register-login {
line-height: var(--line-height-huge);
}
converse-brand-logo {
@include make-col(12);
margin-top: 5em;
margin-bottom: 1em;
.brand-heading {
width: 100%;
font-size: 500%;
padding: 0.7em 0 0 0;
opacity: 0.8;
color: var(--brand-heading-color);
}
.brand-subtitle {
font-size: 90%;
padding: 0.5em;
}
@media screen and (max-width: $mobile-portrait-length) {
.brand-heading {
font-size: 300%;
}
}
}
&.logged-out {
@include make-col(12);
@include fade-in;
width: 100%;
.box-flyout {
width: 100%;
}
}
.box-flyout {
border: 0;
width: 100%;
z-index: 1;
background-color: var(--controlbox-head-color);
.controlbox-head {
display: none;
}
}
#converse-register, #converse-login {
@include make-col-ready();
@include make-col(8);
@include make-col-offset(2);
@include media-breakpoint-up(sm) {
@include make-col(8);
@include make-col-offset(2);
}
@include media-breakpoint-up(md) {
@include make-col(8);
@include make-col-offset(2);
}
@include media-breakpoint-up(lg) {
@include make-col(6);
@include make-col-offset(3);
}
.title, .instructions {
margin: 1em 0;
}
input[type=submit],
input[type=button] {
width: auto;
}
}
}
}
&.logged-out {
@include make-col(12);
@include fade-in;
width: 100%;
.box-flyout {
width: 100%;
}
.converse-fullscreen {
.controlbox-panes {
padding-top: 1em;
}
.box-flyout {
border: 0;
}
.converse-overlayed {
.brand-heading {
padding-top: 0.8rem;
padding-left: 0.8rem;
width: 100%;
z-index: 1;
background-color: var(--controlbox-head-color);
.controlbox-head {
display: none;
}
}
#converse-register, #converse-login {
@include make-col-ready();
@include make-col(8);
@include make-col-offset(2);
@include media-breakpoint-up(sm) {
@include make-col(8);
@include make-col-offset(2);
.converse-svg-logo {
height: 1em;
}
#controlbox {
#converse-login-panel {
height: 100%;
}
@include media-breakpoint-up(md) {
@include make-col(8);
@include make-col-offset(2);
}
@include media-breakpoint-up(lg) {
@include make-col(6);
@include make-col-offset(3);
}
.title, .instructions {
margin: 1em 0;
}
input[type=submit],
input[type=button] {
width: auto;
.controlbox-panes {
margin-top: 0.5em;
}
}
}
@ -628,28 +654,3 @@
}
}
}
#conversejs.converse-fullscreen {
.controlbox-panes {
padding-top: 1em;
}
}
#conversejs.converse-overlayed {
.brand-heading {
padding-top: 0.8rem;
padding-left: 0.8rem;
width: 100%;
}
.converse-svg-logo {
height: 1em;
}
#controlbox {
#converse-login-panel {
height: 100%;
}
.controlbox-panes {
margin-top: 0.5em;
}
}
}

View File

@ -114,32 +114,32 @@ body.converse-fullscreen {
overflow-y: none;
}
&.converse-overlayed {
> .row {
flex-direction: row-reverse;
}
}
converse-chats {
.converse-overlayed {
height: 3em;
> .row {
flex-direction: row-reverse;
}
}
&.converse-fullscreen,
&.converse-mobile {
.converse-chatboxes {
width: 100vw;
left: -15px; // Hack due to padding added by bootstrap
}
}
&.converse-overlayed {
height: 3em;
}
&.converse-embedded {
box-sizing: border-box;
*, *:before, *:after {
box-sizing: border-box;
}
bottom: auto;
height: 100%; // When embedded, it fills the containing element
position: relative;
right: auto;
width: 100%;
.converse-fullscreen,
.converse-mobile {
.converse-chatboxes {
width: 100vw;
left: -15px; // Hack due to padding added by bootstrap
}
}
.converse-embedded {
box-sizing: border-box;
*, *:before, *:after {
box-sizing: border-box;
}
bottom: auto;
height: 100%; // When embedded, it fills the containing element
position: relative;
right: auto;
width: 100%;
}
}
converse-brand-heading {

View File

@ -185,73 +185,74 @@
}
}
#conversejs.converse-overlayed {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 18em;
}
}
.chatbox {
.emoji-picker__header {
.emoji-category {
img {
height: var(--font-size) !important;
width: var(--font-size) !important;
}
converse-chats {
.converse-overlayed {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 18em;
}
}
converse-emoji-picker {
.emoji-picker {
.insert-emoji {
a {
font-size: var(--font-size);
line-height: calc(var(--font-size) * 1.5);
padding: 0;
height: calc(var(--font-size) * 1.5);
width: calc(var(--font-size) * 1.5);
}
img {
height: var(--font-size);
width: var(--font-size);
}
}
}
.emoji-skintone-picker {
font-size: var(--font-size-small);
}
.chatbox {
.emoji-picker__header {
.emoji-category {
font-size: var(--font-size-small);
img {
height: var(--font-size) !important;
width: var(--font-size) !important;
}
}
}
.emoji-picker__lists {
height: 7em;
converse-emoji-picker {
.emoji-picker {
.insert-emoji {
a {
font-size: var(--font-size);
line-height: calc(var(--font-size) * 1.5);
padding: 0;
height: calc(var(--font-size) * 1.5);
width: calc(var(--font-size) * 1.5);
}
img {
height: var(--font-size);
width: var(--font-size);
}
}
}
.emoji-skintone-picker {
font-size: var(--font-size-small);
}
.emoji-picker__header {
.emoji-category {
font-size: var(--font-size-small);
}
}
.emoji-picker__lists {
height: 7em;
}
}
}
}
}
#conversejs.converse-embedded {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 20em;
.converse-embedded {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 20em;
}
}
}
}
#conversejs.converse-fullscreen {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 22em;
.converse-fullscreen {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 22em;
}
}
}
.chatbox {
.toggle-smiley {
}
converse-emoji-picker {
.emoji-picker__lists {
height: 12em;
.chatbox {
.toggle-smiley {
}
converse-emoji-picker {
.emoji-picker__lists {
height: 12em;
}
}
}
}
@ -266,4 +267,3 @@
}
}
}

View File

@ -383,22 +383,25 @@
border-bottom: var(--chatroom-separator-border-bottom);
}
}
}
#conversejs.converse-overlayed {
.message {
&.chat-msg {
&.chat-msg--followup {
.chat-msg__content {
margin-left: 0;
converse-chats {
.converse-overlayed {
.message {
&.chat-msg {
&.chat-msg--followup {
.chat-msg__content {
margin-left: 0;
}
}
}
}
}
}
}
@media screen and (max-width: 767px) {
#conversejs:not(.converse-embedded) {
converse-chats:not(.converse-embedded) {
.message {
&.chat-msg {
.chat-msg__author {

View File

@ -1,90 +1,92 @@
#conversejs.converse-overlayed {
#minimized-chats {
order: 100;
converse-chats {
.converse-overlayed {
#minimized-chats {
order: 100;
width: var(--minimized-chats-width);
margin-bottom: 0;
border-top-left-radius: var(--chatbox-border-radius);
border-top-right-radius: var(--chatbox-border-radius);
color: var(--inverse-link-color);
margin-right: var(--chat-gutter);
padding: 0;
.badge {
bottom: 8px;
border: 1px solid var(--overlayed-badge-color);
}
#toggle-minimized-chats {
width: var(--minimized-chats-width);
margin-bottom: 0;
border-top-left-radius: var(--chatbox-border-radius);
border-top-right-radius: var(--chatbox-border-radius);
background-color: var(--link-color);
padding: 1em 0 0 0;
text-align: center;
color: white;
white-space: nowrap;
overflow-y: hidden;
text-overflow: ellipsis;
display: block;
height: 45px;
width: 9em;
}
color: var(--inverse-link-color);
margin-right: var(--chat-gutter);
padding: 0;
a.restore-chat {
cursor: pointer;
padding: 1px 0 1px 5px;
color: var(--chat-head-text-color);
line-height: 15px;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
text-decoration: none;
.badge {
bottom: 8px;
border: 1px solid var(--overlayed-badge-color);
}
}
a.restore-chat:visited {
color: var(--chat-head-text-color);
}
.minimized-chats-flyout {
flex-direction: column-reverse;
bottom: 45px;
width: var(--minimized-chats-width);
.chat-head {
min-height: 0;
padding: 0.3em;
border-radius: var(--chatbox-border-radius);
height: 35px;
margin-bottom: 0.2em;
width: 100%;
max-width: 9em;
flex-wrap: nowrap;
#toggle-minimized-chats {
border-top-left-radius: var(--chatbox-border-radius);
border-top-right-radius: var(--chatbox-border-radius);
background-color: var(--link-color);
padding: 1em 0 0 0;
text-align: center;
color: white;
white-space: nowrap;
overflow-y: hidden;
text-overflow: ellipsis;
display: block;
height: 45px;
width: 9em;
}
&.minimized {
height: auto;
}
}
.unread-message-count {
font-weight: bold;
background-color: white;
border: 1px solid;
text-shadow: 1px 1px 0 var(--text-shadow-color);
color: var(--warning-color);
border-radius: 5px;
padding: 2px 4px;
font-size: 16px;
text-align: center;
position: absolute;
right: 116px;
bottom: 10px;
}
.unread-message-count-hidden,
.chat-head-message-count-hidden {
display: none;
a.restore-chat {
cursor: pointer;
padding: 1px 0 1px 5px;
color: var(--chat-head-text-color);
line-height: 15px;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
text-decoration: none;
}
}
a.restore-chat:visited {
color: var(--chat-head-text-color);
}
.minimized-chats-flyout {
flex-direction: column-reverse;
bottom: 45px;
width: var(--minimized-chats-width);
.chat-head {
min-height: 0;
padding: 0.3em;
border-radius: var(--chatbox-border-radius);
height: 35px;
margin-bottom: 0.2em;
width: 100%;
max-width: 9em;
flex-wrap: nowrap;
}
&.minimized {
height: auto;
}
}
.unread-message-count {
font-weight: bold;
background-color: white;
border: 1px solid;
text-shadow: 1px 1px 0 var(--text-shadow-color);
color: var(--warning-color);
border-radius: 5px;
padding: 2px 4px;
font-size: 16px;
text-align: center;
position: absolute;
right: 116px;
bottom: 10px;
}
.unread-message-count-hidden,
.chat-head-message-count-hidden {
display: none;
}
}
}
}

View File

@ -187,21 +187,23 @@
}
}
}
}
#conversejs.converse-overlayed {
.chat-toolbar {
li {
.toolbar-menu {
min-width: 235px;
converse-chats {
.converse-overlayed {
.chat-toolbar {
li {
.toolbar-menu {
min-width: 235px;
}
}
}
}
}
.chatroom {
.chat-toolbar {
li {
.toolbar-menu {
min-width: 280px;
.chatroom {
.chat-toolbar {
li {
.toolbar-menu {
min-width: 280px;
}
}
}
}
}

View File

@ -41,7 +41,7 @@ describe("The nickname autocomplete feature", function () {
await u.waitUntil(() => view.model.messages.last()?.get('received'));
// Test that pressing @ brings up all options
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
const at_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
@ -53,11 +53,11 @@ describe("The nickname autocomplete feature", function () {
textarea.value = '@';
view.onKeyUp(at_event);
await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 4);
expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 4);
expect(view.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
expect(view.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
expect(view.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
expect(view.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
done();
}));
@ -95,7 +95,7 @@ describe("The nickname autocomplete feature", function () {
await u.waitUntil(() => view.model.messages.last()?.get('received'));
// Test that pressing @ brings up all options
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
const at_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
@ -108,11 +108,11 @@ describe("The nickname autocomplete feature", function () {
textarea.value = '\n@';
view.onKeyUp(at_event);
await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 4);
expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 4);
expect(view.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
expect(view.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
expect(view.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
expect(view.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
done();
}));
@ -150,7 +150,7 @@ describe("The nickname autocomplete feature", function () {
await u.waitUntil(() => view.model.messages.last()?.get('received'));
// Test that pressing @ brings up all options
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
const at_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
@ -163,11 +163,11 @@ describe("The nickname autocomplete feature", function () {
textarea.value = '(@';
view.onKeyUp(at_event);
await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 4);
expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 4);
expect(view.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
expect(view.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
expect(view.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
expect(view.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
done();
}));
@ -191,7 +191,7 @@ describe("The nickname autocomplete feature", function () {
})));
});
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
const at_event = {
'target': textarea,
'preventDefault': function preventDefault() { },
@ -204,17 +204,17 @@ describe("The nickname autocomplete feature", function () {
view.onKeyDown(at_event);
textarea.value = '@ber';
view.onKeyUp(at_event);
await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 3);
expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('bernard');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('naber');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('helberlo');
await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 3);
expect(view.querySelector('.suggestion-box__results li:first-child').textContent).toBe('bernard');
expect(view.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('naber');
expect(view.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('helberlo');
// Test that when the query index is equal, results should be sorted by length
textarea.value = '@jo';
view.onKeyUp(at_event);
await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 2);
expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('john');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('jones');
await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 2);
expect(view.querySelector('.suggestion-box__results li:first-child').textContent).toBe('john');
expect(view.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('jones');
done();
}));
@ -239,7 +239,7 @@ describe("The nickname autocomplete feature", function () {
_converse.connection._dataRecv(mock.createRequest(presence));
expect(view.model.occupants.length).toBe(2);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = "hello som";
// Press tab
@ -252,9 +252,9 @@ describe("The nickname autocomplete feature", function () {
}
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(1);
expect(view.el.querySelector('.suggestion-box__results li').textContent).toBe('some1');
await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === false);
expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(1);
expect(view.querySelector('.suggestion-box__results li').textContent).toBe('some1');
const backspace_event = {
'target': textarea,
@ -267,7 +267,7 @@ describe("The nickname autocomplete feature", function () {
textarea.value = textarea.value.slice(0, textarea.value.length-1)
view.onKeyUp(backspace_event);
}
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === true);
await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === true);
presence = $pres({
'to': 'romeo@montague.lit/orchard',
@ -284,8 +284,8 @@ describe("The nickname autocomplete feature", function () {
textarea.value = "hello s s";
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === false);
expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(2);
const up_arrow_event = {
'target': textarea,
@ -295,9 +295,9 @@ describe("The nickname autocomplete feature", function () {
}
view.onKeyDown(up_arrow_event);
view.onKeyUp(up_arrow_event);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
expect(view.el.querySelector('.suggestion-box__results li[aria-selected="false"]').textContent).toBe('some1');
expect(view.el.querySelector('.suggestion-box__results li[aria-selected="true"]').textContent).toBe('some2');
expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(2);
expect(view.querySelector('.suggestion-box__results li[aria-selected="false"]').textContent).toBe('some1');
expect(view.querySelector('.suggestion-box__results li[aria-selected="true"]').textContent).toBe('some2');
view.onKeyDown({
'target': textarea,
@ -322,7 +322,7 @@ describe("The nickname autocomplete feature", function () {
textarea.value = "hello z";
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === false);
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
@ -351,7 +351,7 @@ describe("The nickname autocomplete feature", function () {
_converse.connection._dataRecv(mock.createRequest(presence));
expect(view.model.occupants.length).toBe(2);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = "hello @some1 ";
// Press backspace
@ -365,9 +365,9 @@ describe("The nickname autocomplete feature", function () {
view.onKeyDown(backspace_event);
textarea.value = "hello @some1"; // Mimic backspace
view.onKeyUp(backspace_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(1);
expect(view.el.querySelector('.suggestion-box__results li').textContent).toBe('some1');
await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === false);
expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(1);
expect(view.querySelector('.suggestion-box__results li').textContent).toBe('some1');
done();
}));
});

View File

@ -28,13 +28,13 @@ describe("A chat room", function () {
spyOn(view, 'renderBookmarkForm').and.callThrough();
spyOn(view, 'closeForm').and.callThrough();
await u.waitUntil(() => view.el.querySelector('.toggle-bookmark') !== null);
const toggle = view.el.querySelector('.toggle-bookmark');
await u.waitUntil(() => view.querySelector('.toggle-bookmark') !== null);
const toggle = view.querySelector('.toggle-bookmark');
expect(toggle.title).toBe('Bookmark this groupchat');
toggle.click();
expect(view.renderBookmarkForm).toHaveBeenCalled();
view.el.querySelector('.button-cancel').click();
view.querySelector('.button-cancel').click();
expect(view.closeForm).toHaveBeenCalled();
expect(u.hasClass('on-button', toggle), false);
expect(toggle.title).toBe('Bookmark this groupchat');
@ -74,13 +74,13 @@ describe("A chat room", function () {
* </iq>
*/
expect(view.model.get('bookmarked')).toBeFalsy();
const form = view.el.querySelector('.chatroom-form');
const form = view.querySelector('.chatroom-form');
form.querySelector('input[name="name"]').value = 'Play&apos;s the Thing';
form.querySelector('input[name="autojoin"]').checked = 'checked';
form.querySelector('input[name="nick"]').value = 'JC';
const IQ_stanzas = _converse.connection.IQ_stanzas;
view.el.querySelector('.muc-bookmark-form .btn-primary').click();
view.querySelector('.muc-bookmark-form .btn-primary').click();
const sent_stanza = await u.waitUntil(
() => IQ_stanzas.filter(s => sizzle('iq publish[node="storage:bookmarks"]', s).length).pop());
@ -124,8 +124,8 @@ describe("A chat room", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.model.get('bookmarked'));
expect(view.model.get('bookmarked')).toBeTruthy();
await u.waitUntil(() => view.el.querySelector('.toggle-bookmark')?.title === 'Unbookmark this groupchat');
expect(u.hasClass('on-button', view.el.querySelector('.toggle-bookmark')), true);
await u.waitUntil(() => view.querySelector('.toggle-bookmark')?.title === 'Unbookmark this groupchat');
expect(u.hasClass('on-button', view.querySelector('.toggle-bookmark')), true);
// We ignore this IQ stanza... (unless it's an error stanza), so
// nothing to test for here.
done();
@ -211,7 +211,7 @@ describe("A chat room", function () {
);
await _converse.api.rooms.open(`lounge@montague.lit`);
const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.el.querySelector('.chatbox-title__text .fa-bookmark')).toBe(null);
expect(view.querySelector('.chatbox-title__text .fa-bookmark')).toBe(null);
_converse.bookmarks.create({
'jid': view.model.get('jid'),
'autojoin': false,
@ -219,9 +219,9 @@ describe("A chat room", function () {
'nick': ' some1'
});
view.model.set('bookmarked', true);
await u.waitUntil(() => view.el.querySelector('.chatbox-title__text .fa-bookmark') !== null);
await u.waitUntil(() => view.querySelector('.chatbox-title__text .fa-bookmark') !== null);
view.model.set('bookmarked', false);
await u.waitUntil(() => view.el.querySelector('.chatbox-title__text .fa-bookmark') === null);
await u.waitUntil(() => view.querySelector('.chatbox-title__text .fa-bookmark') === null);
done();
}));
@ -233,7 +233,7 @@ describe("A chat room", function () {
const muc_jid = 'theplay@conference.shakespeare.lit';
await _converse.api.rooms.open(muc_jid);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
await u.waitUntil(() => view.querySelector('.toggle-bookmark'));
spyOn(view, 'toggleBookmark').and.callThrough();
spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
@ -249,12 +249,12 @@ describe("A chat room", function () {
expect(_converse.bookmarks.length).toBe(1);
await u.waitUntil(() => _converse.chatboxes.length >= 1);
expect(view.model.get('bookmarked')).toBeTruthy();
await u.waitUntil(() => view.el.querySelector('.chatbox-title__text .fa-bookmark') !== null);
await u.waitUntil(() => view.querySelector('.chatbox-title__text .fa-bookmark') !== null);
spyOn(_converse.connection, 'getUniqueId').and.callThrough();
const bookmark_icon = view.el.querySelector('.toggle-bookmark');
const bookmark_icon = view.querySelector('.toggle-bookmark');
bookmark_icon.click();
expect(view.toggleBookmark).toHaveBeenCalled();
await u.waitUntil(() => view.el.querySelector('.chatbox-title__text .fa-bookmark') === null);
await u.waitUntil(() => view.querySelector('.chatbox-title__text .fa-bookmark') === null);
expect(_converse.bookmarks.length).toBe(0);
// Check that an IQ stanza is sent out, containing no
@ -594,20 +594,20 @@ describe("Bookmarks", function () {
'jid': 'first@conference.shakespeare.lit'
}).c('nick').t('JC');
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.el.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length);
expect(view.el.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length).toBe(2);
view.el.querySelector('.bookmarks.rooms-list .open-room').click();
await u.waitUntil(() => view.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length);
expect(view.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length).toBe(2);
view.querySelector('.bookmarks.rooms-list .open-room').click();
await u.waitUntil(() => _converse.chatboxes.length === 2);
expect((await api.rooms.get('first@conference.shakespeare.lit')).get('hidden')).toBe(false);
await u.waitUntil(() => view.el.querySelectorAll('.list-container--bookmarks .available-chatroom:not(.hidden)').length === 1);
view.el.querySelector('.list-container--bookmarks .available-chatroom:not(.hidden) .open-room').click();
await u.waitUntil(() => view.querySelectorAll('.list-container--bookmarks .available-chatroom:not(.hidden)').length === 1);
view.querySelector('.list-container--bookmarks .available-chatroom:not(.hidden) .open-room').click();
await u.waitUntil(() => _converse.chatboxes.length === 3);
expect((await api.rooms.get('first@conference.shakespeare.lit')).get('hidden')).toBe(true);
expect((await api.rooms.get('theplay@conference.shakespeare.lit')).get('hidden')).toBe(false);
view.el.querySelector('.list-container--openrooms .open-room:first-child').click();
await u.waitUntil(() => view.el.querySelector('.list-item.open').getAttribute('data-room-jid') === 'first@conference.shakespeare.lit');
view.querySelector('.list-container--openrooms .open-room:first-child').click();
await u.waitUntil(() => view.querySelector('.list-item.open').getAttribute('data-room-jid') === 'first@conference.shakespeare.lit');
expect((await api.rooms.get('first@conference.shakespeare.lit')).get('hidden')).toBe(false);
expect((await api.rooms.get('theplay@conference.shakespeare.lit')).get('hidden')).toBe(true);
done();
@ -689,19 +689,19 @@ describe("When hide_open_bookmarks is true and a bookmarked room is opened", fun
const u = converse.env.utils;
const bmarks_view = _converse.bookmarksview;
await u.waitUntil(() => bmarks_view.el.querySelectorAll(".open-room").length, 500);
const room_els = bmarks_view.el.querySelectorAll(".open-room");
await u.waitUntil(() => bmarks_view.querySelectorAll(".open-room").length, 500);
const room_els = bmarks_view.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
const bookmark = _converse.bookmarksview.el.querySelector(".open-room");
const bookmark = _converse.bookmarksview.querySelector(".open-room");
bookmark.click();
await u.waitUntil(() => _converse.chatboxviews.get(jid));
expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeTruthy();
expect(u.hasClass('hidden', _converse.bookmarksview.querySelector(".available-chatroom"))).toBeTruthy();
// Check that it reappears once the room is closed
const view = _converse.chatboxviews.get(jid);
view.close();
await u.waitUntil(() => !u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom")));
await u.waitUntil(() => !u.hasClass('hidden', _converse.bookmarksview.querySelector(".available-chatroom")));
done();
}));
});

View File

@ -40,7 +40,7 @@ describe("Chatboxes", function () {
await _converse.handleMessageStanza(msg);
await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
const msg_txt_sel = 'converse-chat-message:last-child .chat-msg__body';
await u.waitUntil(() => view.el.querySelector(msg_txt_sel).textContent.trim() === 'hello world');
await u.waitUntil(() => view.querySelector(msg_txt_sel).textContent.trim() === 'hello world');
done();
}));
@ -58,7 +58,7 @@ describe("Chatboxes", function () {
}
await u.waitUntil(() => sizzle('converse-chat-message', view.el).length === 10);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = '/clear';
view.onKeyDown({
target: textarea,
@ -84,8 +84,8 @@ describe("Chatboxes", function () {
spyOn(_converse.minimize, 'trimChats');
expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group li').length, 700);
const online_contacts = _converse.rosterview.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
expect(online_contacts.length).toBe(17);
let el = online_contacts[0];
el.click();
@ -153,7 +153,7 @@ describe("Chatboxes", function () {
const view = await mock.openChatBoxFor(_converse, contact_jid);
const el = sizzle('a.open-chat:contains("'+view.model.getDisplayName()+'")', _converse.rosterview.el).pop();
await u.waitUntil(() => u.isVisible(el));
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
await u.waitUntil(() => u.isVisible(textarea));
textarea.blur();
spyOn(view.model, 'maybeShow').and.callThrough();
@ -209,14 +209,14 @@ describe("Chatboxes", function () {
await mock.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, contact_jid);
const chatview = _converse.chatboxviews.get(contact_jid);
spyOn(chatview, 'close').and.callThrough();
spyOn(_converse.api, "trigger").and.callThrough();
// We need to rebind all events otherwise our spy won't be called
chatview.delegateEvents();
chatview.el.querySelector('.close-chatbox-button').click();
chatview.querySelector('.close-chatbox-button').click();
expect(chatview.close).toHaveBeenCalled();
await new Promise(resolve => _converse.api.listen.once('chatBoxClosed', resolve));
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
@ -231,7 +231,7 @@ describe("Chatboxes", function () {
spyOn(_converse.minimize, 'trimChats');
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
spyOn(_converse.api, "trigger").and.callThrough();
mock.closeControlBox();
@ -274,19 +274,19 @@ describe("Chatboxes", function () {
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
const toolbar = view.el.querySelector('.chat-toolbar');
const toolbar = view.querySelector('.chat-toolbar');
const counter = toolbar.querySelector('.message-limit');
expect(counter.textContent).toBe('200');
view.insertIntoTextArea('hello world');
expect(counter.textContent).toBe('188');
toolbar.querySelector('.toggle-emojis').click();
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__lists'));
const picker = await u.waitUntil(() => view.querySelector('.emoji-picker__lists'));
const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji a'));
item.click()
expect(counter.textContent).toBe('179');
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
const ev = {
target: textarea,
preventDefault: function preventDefault () {},
@ -314,7 +314,7 @@ describe("Chatboxes", function () {
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
const counter = view.el.querySelector('.chat-toolbar .message-limit');
const counter = view.querySelector('.chat-toolbar .message-limit');
expect(counter).toBe(null);
done();
}));
@ -336,7 +336,7 @@ describe("Chatboxes", function () {
_converse.visible_toolbar_buttons.call = false;
await mock.openChatBoxFor(_converse, contact_jid);
let view = _converse.chatboxviews.get(contact_jid);
toolbar = view.el.querySelector('.chat-toolbar');
toolbar = view.querySelector('.chat-toolbar');
call_button = toolbar.querySelector('.toggle-call');
expect(call_button === null).toBeTruthy();
view.close();
@ -345,7 +345,7 @@ describe("Chatboxes", function () {
_converse.visible_toolbar_buttons.call = true; // enable the button
await mock.openChatBoxFor(_converse, contact_jid);
view = _converse.chatboxviews.get(contact_jid);
toolbar = view.el.querySelector('.chat-toolbar');
toolbar = view.querySelector('.chat-toolbar');
call_button = toolbar.querySelector('.toggle-call');
call_button.click();
expect(_converse.api.trigger).toHaveBeenCalledWith('callButtonClicked', jasmine.any(Object));
@ -390,7 +390,7 @@ describe("Chatboxes", function () {
await mock.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openControlBox(_converse);
u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
spyOn(_converse.connection, 'send');
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
@ -413,7 +413,7 @@ describe("Chatboxes", function () {
await mock.openControlBox(_converse);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
view.model.minimize();
@ -447,14 +447,14 @@ describe("Chatboxes", function () {
await mock.openControlBox(_converse);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid);
expect(view.model.get('chat_state')).toBe('active');
spyOn(_converse.connection, 'send');
spyOn(_converse.api, "trigger").and.callThrough();
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
target: view.querySelector('textarea.chat-textarea'),
keyCode: 1
});
expect(view.model.get('chat_state')).toBe('composing');
@ -469,7 +469,7 @@ describe("Chatboxes", function () {
// The notification is not sent again
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
target: view.querySelector('textarea.chat-textarea'),
keyCode: 1
});
expect(view.model.get('chat_state')).toBe('composing');
@ -486,14 +486,14 @@ describe("Chatboxes", function () {
await mock.openControlBox(_converse);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid);
expect(view.model.get('chat_state')).toBe('active');
spyOn(_converse.connection, 'send');
spyOn(_converse.api, "trigger").and.callThrough();
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
target: view.querySelector('textarea.chat-textarea'),
keyCode: 1
});
expect(view.model.get('chat_state')).toBe('composing');
@ -511,7 +511,7 @@ describe("Chatboxes", function () {
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, sender_jid);
// <composing> state
@ -525,7 +525,7 @@ describe("Chatboxes", function () {
_converse.connection._dataRecv(mock.createRequest(msg));
const view = _converse.chatboxviews.get(sender_jid);
let csn = mock.cur_names[1] + ' is typing';
await u.waitUntil( () => view.el.querySelector('.chat-content__notifications').innerText === csn);
await u.waitUntil( () => view.querySelector('.chat-content__notifications').innerText === csn);
expect(view.model.messages.length).toEqual(0);
// <paused> state
@ -537,7 +537,7 @@ describe("Chatboxes", function () {
}).c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.connection._dataRecv(mock.createRequest(msg));
csn = mock.cur_names[1] + ' has stopped typing';
await u.waitUntil( () => view.el.querySelector('.chat-content__notifications').innerText === csn);
await u.waitUntil( () => view.querySelector('.chat-content__notifications').innerText === csn);
msg = $msg({
from: sender_jid,
@ -547,7 +547,7 @@ describe("Chatboxes", function () {
}).c('body').t('hello world').tree();
await _converse.handleMessageStanza(msg);
const msg_el = await u.waitUntil(() => view.content.querySelector('.chat-msg'));
await u.waitUntil( () => view.el.querySelector('.chat-content__notifications').innerText === '');
await u.waitUntil( () => view.querySelector('.chat-content__notifications').innerText === '');
expect(msg_el.querySelector('.chat-msg__text').textContent).toBe('hello world');
done();
}));
@ -584,7 +584,7 @@ describe("Chatboxes", function () {
await u.waitUntil(() => u.shouldCreateMessage.calls.count());
expect(view.model.messages.length).toEqual(0);
const el = view.el.querySelector('.chat-content__notifications');
const el = view.querySelector('.chat-content__notifications');
expect(el.textContent).toBe('');
done();
}));
@ -600,7 +600,7 @@ describe("Chatboxes", function () {
await mock.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group li').length, 700);
_converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
@ -608,7 +608,7 @@ describe("Chatboxes", function () {
spyOn(view.model, 'setChatState').and.callThrough();
expect(view.model.get('chat_state')).toBe('active');
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
target: view.querySelector('textarea.chat-textarea'),
keyCode: 1
});
expect(view.model.get('chat_state')).toBe('composing');
@ -632,14 +632,14 @@ describe("Chatboxes", function () {
// out if the user simply types longer than the
// timeout.
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
target: view.querySelector('textarea.chat-textarea'),
keyCode: 1
});
expect(view.model.setChatState).toHaveBeenCalled();
expect(view.model.get('chat_state')).toBe('composing');
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
target: view.querySelector('textarea.chat-textarea'),
keyCode: 1
});
expect(view.model.get('chat_state')).toBe('composing');
@ -653,7 +653,7 @@ describe("Chatboxes", function () {
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
// TODO: only show paused state if the previous state was composing
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
spyOn(_converse.api, "trigger").and.callThrough();
@ -669,7 +669,7 @@ describe("Chatboxes", function () {
_converse.connection._dataRecv(mock.createRequest(msg));
const csn = mock.cur_names[1] + ' has stopped typing';
await u.waitUntil( () => view.el.querySelector('.chat-content__notifications').innerText === csn);
await u.waitUntil( () => view.querySelector('.chat-content__notifications').innerText === csn);
expect(view.model.messages.length).toEqual(0);
done();
}));
@ -703,7 +703,7 @@ describe("Chatboxes", function () {
_converse.connection._dataRecv(mock.createRequest(msg));
await u.waitUntil(() => u.shouldCreateMessage.calls.count());
expect(view.model.messages.length).toEqual(0);
const el = view.el.querySelector('.chat-content__notifications');
const el = view.querySelector('.chat-content__notifications');
expect(el.textContent).toBe('');
done();
done();
@ -725,7 +725,7 @@ describe("Chatboxes", function () {
await mock.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 1000);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length, 1000);
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
await u.waitUntil(() => view.model.get('chat_state') === 'active');
@ -733,7 +733,7 @@ describe("Chatboxes", function () {
expect(messages.length).toBe(1);
expect(view.model.get('chat_state')).toBe('active');
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
target: view.querySelector('textarea.chat-textarea'),
keyCode: 1
});
await u.waitUntil(() => view.model.get('chat_state') === 'composing', 600);
@ -804,7 +804,7 @@ describe("Chatboxes", function () {
await mock.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
const view = await mock.openChatBoxFor(_converse, contact_jid);
expect(view.model.get('chat_state')).toBe('active');
spyOn(_converse.connection, 'send');
@ -831,7 +831,7 @@ describe("Chatboxes", function () {
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
await mock.openChatBoxFor(_converse, sender_jid);
const view = _converse.chatboxviews.get(sender_jid);
expect(view.el.querySelectorAll('.chat-event').length).toBe(0);
expect(view.querySelectorAll('.chat-event').length).toBe(0);
// Insert <composing> message, to also check that
// text messages are inserted correctly with
// temporary chat events in the chat contents.
@ -843,7 +843,7 @@ describe("Chatboxes", function () {
.c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
.tree();
_converse.connection._dataRecv(mock.createRequest(msg));
const csntext = await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent);
const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent);
expect(csntext).toEqual(mock.cur_names[1] + ' is typing');
expect(view.model.messages.length).toBe(0);
@ -855,7 +855,7 @@ describe("Chatboxes", function () {
}).c('inactive', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.connection._dataRecv(mock.createRequest(msg));
await u.waitUntil(() => !view.el.querySelector('.chat-content__notifications').textContent);
await u.waitUntil(() => !view.querySelector('.chat-content__notifications').textContent);
done();
}));
});
@ -881,7 +881,7 @@ describe("Chatboxes", function () {
_converse.connection._dataRecv(mock.createRequest(msg));
const view = _converse.chatboxviews.get(sender_jid);
const csntext = await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent);
const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent);
expect(csntext).toEqual(mock.cur_names[1] + ' has gone away');
done();
}));
@ -899,7 +899,7 @@ describe("Chatboxes", function () {
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, sender_jid);
// Original message
@ -928,7 +928,7 @@ describe("Chatboxes", function () {
}).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.connection._dataRecv(mock.createRequest(msg));
const csntext = await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent);
const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent);
expect(csntext).toEqual(mock.cur_names[1] + ' is typing');
// Edited message
@ -943,7 +943,7 @@ describe("Chatboxes", function () {
.c('replace', {'xmlns': Strophe.NS.MESSAGE_CORRECT, 'id': original_id }).tree();
await _converse.handleMessageStanza(edited);
await u.waitUntil(() => !view.el.querySelector('.chat-content__notifications').textContent);
await u.waitUntil(() => !view.querySelector('.chat-content__notifications').textContent);
done();
}));
});
@ -970,16 +970,16 @@ describe("Chatboxes", function () {
expect(view.model.messages.length === 1).toBeTruthy();
let stored_messages = await view.model.messages.browserStorage.findAll();
expect(stored_messages.length).toBe(1);
await u.waitUntil(() => view.el.querySelector('.chat-msg'));
await u.waitUntil(() => view.querySelector('.chat-msg'));
message = '/clear';
spyOn(view, 'clearMessages').and.callThrough();
spyOn(window, 'confirm').and.callFake(function () {
return true;
});
view.el.querySelector('.chat-textarea').value = message;
view.querySelector('.chat-textarea').value = message;
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
target: view.querySelector('textarea.chat-textarea'),
preventDefault: function preventDefault () {},
keyCode: 13
});
@ -1158,7 +1158,7 @@ describe("Chatboxes", function () {
await mock.waitForRoster(_converse, 'current', 1);
let msg, indicator_el;
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length, 500);
await mock.openChatBoxFor(_converse, sender_jid);
const chatbox = _converse.chatboxes.get(sender_jid);
chatbox.save('scrolled', true);
@ -1185,7 +1185,7 @@ describe("Chatboxes", function () {
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let indicator_el, msg;
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length, 500);
await mock.openChatBoxFor(_converse, sender_jid);
const chatbox = _converse.chatboxes.get(sender_jid);
var chatboxview = _converse.chatboxviews.get(sender_jid);
@ -1214,7 +1214,7 @@ describe("Chatboxes", function () {
await mock.waitForRoster(_converse, 'current', 1);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const msgFactory = () => mock.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length, 500);
await mock.openChatBoxFor(_converse, sender_jid);
const chatbox = _converse.chatboxes.get(sender_jid);
const view = _converse.chatboxviews.get(sender_jid);
@ -1240,7 +1240,7 @@ describe("Chatboxes", function () {
await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current', 1);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length, 500);
await mock.openChatBoxFor(_converse, sender_jid);
const chatbox = _converse.chatboxes.get(sender_jid);
const msgFactory = () => mock.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
@ -1264,7 +1264,7 @@ describe("Chatboxes", function () {
await mock.waitForRoster(_converse, 'current', 1);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length, 500);
await mock.openChatBoxFor(_converse, sender_jid);
const chatbox = _converse.chatboxes.get(sender_jid);
const view = _converse.chatboxviews.get(sender_jid);

View File

@ -45,7 +45,7 @@ describe("The Controlbox", function () {
// We need to rebind all events otherwise our spy won't be called
controlview.delegateEvents();
controlview.el.querySelector('.close-chatbox-button').click();
controlview.querySelector('.close-chatbox-button').click();
expect(controlview.close).toHaveBeenCalled();
await new Promise(resolve => _converse.api.listen.once('chatBoxClosed', resolve));
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
@ -76,9 +76,9 @@ describe("The Controlbox", function () {
ask: 'subscribe',
fullname: mock.pend_names[0]
});
await u.waitUntil(() => _.filter(_converse.rosterview.el.querySelectorAll('.roster-group li'), u.isVisible).length, 700);
await u.waitUntil(() => _.filter(_converse.rosterview.querySelectorAll('.roster-group li'), u.isVisible).length, 700);
// Checking that only one entry is created because both JID is same (Case sensitive check)
expect(_.filter(_converse.rosterview.el.querySelectorAll('li'), u.isVisible).length).toBe(1);
expect(_.filter(_converse.rosterview.querySelectorAll('li'), u.isVisible).length).toBe(1);
expect(_converse.rosterview.update).toHaveBeenCalled();
done();
}));
@ -98,7 +98,7 @@ describe("The Controlbox", function () {
chatview.model.set({'minimized': true});
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count') === null).toBeTruthy();
expect(_converse.rosterview.el.querySelector('.msgs-indicator') === null).toBeTruthy();
expect(_converse.rosterview.querySelector('.msgs-indicator') === null).toBeTruthy();
let msg = $msg({
from: sender_jid,
@ -108,10 +108,10 @@ describe("The Controlbox", function () {
}).c('body').t('hello').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.handleMessageStanza(msg);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll(".msgs-indicator").length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll(".msgs-indicator").length);
spyOn(chatview.model, 'handleUnreadMessage').and.callThrough();
await u.waitUntil(() => _converse.chatboxviews.el.querySelector('.restore-chat .message-count')?.textContent === '1');
expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1');
expect(_converse.rosterview.querySelector('.msgs-indicator').textContent).toBe('1');
msg = $msg({
from: sender_jid,
@ -123,10 +123,10 @@ describe("The Controlbox", function () {
_converse.handleMessageStanza(msg);
await u.waitUntil(() => chatview.model.handleUnreadMessage.calls.count());
await u.waitUntil(() => _converse.chatboxviews.el.querySelector('.restore-chat .message-count')?.textContent === '2');
expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('2');
expect(_converse.rosterview.querySelector('.msgs-indicator').textContent).toBe('2');
chatview.model.set({'minimized': false});
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count')).toBe(null);
await u.waitUntil(() => _converse.rosterview.el.querySelector('.msgs-indicator') === null);
await u.waitUntil(() => _converse.rosterview.querySelector('.msgs-indicator') === null);
done();
}));
});
@ -140,8 +140,8 @@ describe("The Controlbox", function () {
mock.openControlBox(_converse);
var view = _converse.xmppstatusview;
expect(u.hasClass('online', view.el.querySelector('.xmpp-status span:first-child'))).toBe(true);
expect(view.el.querySelector('.xmpp-status span.online').textContent.trim()).toBe('I am online');
expect(u.hasClass('online', view.querySelector('.xmpp-status span:first-child'))).toBe(true);
expect(view.querySelector('.xmpp-status span.online').textContent.trim()).toBe('I am online');
done();
}));
@ -152,7 +152,7 @@ describe("The Controlbox", function () {
await mock.openControlBox(_converse);
var cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click()
cbview.querySelector('.change-status').click()
const modal = _converse.api.modal.get('modal-status-change');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const view = _converse.xmppstatusview;
@ -166,10 +166,10 @@ describe("The Controlbox", function () {
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="PxXfr6uz8ClMWIga0OB/MhKNH/M=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`);
const first_child = view.el.querySelector('.xmpp-status span:first-child');
const first_child = view.querySelector('.xmpp-status span:first-child');
expect(u.hasClass('online', first_child)).toBe(false);
expect(u.hasClass('dnd', first_child)).toBe(true);
expect(view.el.querySelector('.xmpp-status span:first-child').textContent.trim()).toBe('I am busy');
expect(view.querySelector('.xmpp-status span:first-child').textContent.trim()).toBe('I am busy');
done();
}));
@ -180,7 +180,7 @@ describe("The Controlbox", function () {
await mock.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click()
cbview.querySelector('.change-status').click()
const modal = _converse.api.modal.get('modal-status-change');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
@ -197,9 +197,9 @@ describe("The Controlbox", function () {
`<c hash="sha-1" node="https://conversejs.org" ver="PxXfr6uz8ClMWIga0OB/MhKNH/M=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`);
const first_child = view.el.querySelector('.xmpp-status span:first-child');
const first_child = view.querySelector('.xmpp-status span:first-child');
expect(u.hasClass('online', first_child)).toBe(true);
expect(view.el.querySelector('.xmpp-status span:first-child').textContent.trim()).toBe(msg);
expect(view.querySelector('.xmpp-status span:first-child').textContent.trim()).toBe(msg);
done();
}));
});
@ -216,7 +216,7 @@ describe("The 'Add Contact' widget", function () {
await mock.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
cbview.querySelector('.add-contact').click()
const modal = _converse.api.modal.get('add-contact-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
expect(modal.el.querySelector('form.add-xmpp-contact')).not.toBe(null);
@ -249,7 +249,7 @@ describe("The 'Add Contact' widget", function () {
await mock.waitForRoster(_converse, 'all', 0);
mock.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
cbview.querySelector('.add-contact').click()
const modal = _converse.api.modal.get('add-contact-modal');
expect(modal.jid_auto_complete).toBe(undefined);
expect(modal.name_auto_complete).toBe(undefined);
@ -296,7 +296,7 @@ describe("The 'Add Contact' widget", function () {
XMLHttpRequest.and.callFake(() => xhr);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
cbview.querySelector('.add-contact').click()
const modal = _converse.api.modal.get('add-contact-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
@ -366,7 +366,7 @@ describe("The 'Add Contact' widget", function () {
XMLHttpRequest.and.callFake(() => xhr);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
cbview.querySelector('.add-contact').click()
modal = _converse.api.modal.get('add-contact-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000);

View File

@ -292,7 +292,7 @@ describe("Converse", function() {
expect(chat.get('box_id')).toBe(`box-${jid}`);
const view = _converse.chatboxviews.get(jid);
await u.waitUntil(() => u.isVisible(view.el));
await u.waitUntil(() => u.isVisible(view));
// Test for multiple JIDs
mock.openChatBoxFor(_converse, jid2);
await u.waitUntil(() => _converse.chatboxes.length == 3);
@ -325,7 +325,7 @@ describe("Converse", function() {
['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
);
const view = _converse.chatboxviews.get(jid);
await u.waitUntil(() => u.isVisible(view.el));
await u.waitUntil(() => u.isVisible(view));
// Test for multiple JIDs
const list = await _converse.api.chats.open([jid, jid2]);
expect(Array.isArray(list)).toBeTruthy();

View File

@ -15,7 +15,7 @@ describe("A Chat Message", function () {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
const view = _converse.api.chatviews.get(contact_jid);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
expect(textarea.value).toBe('');
view.onKeyDown({
target: textarea,
@ -30,8 +30,8 @@ describe("A Chat Message", function () {
keyCode: 13 // Enter
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelector('.chat-msg__text').textContent)
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?');
const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
@ -42,8 +42,8 @@ describe("A Chat Message", function () {
});
expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')), 500);
spyOn(_converse.connection, 'send');
textarea.value = 'But soft, what light through yonder window breaks?';
@ -76,8 +76,8 @@ describe("A Chat Message", function () {
expect(keys.length).toBe(1);
expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500);
// Test that pressing the down arrow cancels message correction
await u.waitUntil(() => textarea.value === '')
@ -87,8 +87,8 @@ describe("A Chat Message", function () {
});
expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')), 500);
expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
view.onKeyDown({
target: textarea,
@ -96,8 +96,8 @@ describe("A Chat Message", function () {
});
expect(textarea.value).toBe('');
expect(view.model.messages.at(0).get('correcting')).toBe(false);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500);
textarea.value = 'It is the east, and Juliet is the one.';
view.onKeyDown({
@ -106,7 +106,7 @@ describe("A Chat Message", function () {
keyCode: 13 // Enter
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
expect(view.querySelectorAll('.chat-msg').length).toBe(2);
textarea.value = 'Arise, fair sun, and kill the envious moon';
view.onKeyDown({
@ -115,7 +115,7 @@ describe("A Chat Message", function () {
keyCode: 13 // Enter
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
expect(view.querySelectorAll('.chat-msg').length).toBe(3);
view.onKeyDown({
target: textarea,
@ -147,7 +147,7 @@ describe("A Chat Message", function () {
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => textarea.value === '');
const messages = view.el.querySelectorAll('.chat-msg');
const messages = view.querySelectorAll('.chat-msg');
expect(messages.length).toBe(3);
expect(messages[0].querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder window breaks?');
@ -173,7 +173,7 @@ describe("A Chat Message", function () {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.api.chatviews.get(contact_jid);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = 'But soft, what light through yonder airlock breaks?';
view.onKeyDown({
@ -183,14 +183,14 @@ describe("A Chat Message", function () {
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelector('.chat-msg__text').textContent)
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?');
expect(textarea.value).toBe('');
const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg .chat-msg__action').length === 2);
let action = view.el.querySelector('.chat-msg .chat-msg__action');
await u.waitUntil(() => view.querySelectorAll('.chat-msg .chat-msg__action').length === 2);
let action = view.querySelector('.chat-msg .chat-msg__action');
expect(action.textContent.trim()).toBe('Edit');
action.style.opacity = 1;
@ -198,8 +198,8 @@ describe("A Chat Message", function () {
expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')));
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')));
spyOn(_converse.connection, 'send');
textarea.value = 'But soft, what light through yonder window breaks?';
@ -231,26 +231,26 @@ describe("A Chat Message", function () {
expect(keys.length).toBe(1);
expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')) === false);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
// Test that clicking the pencil icon a second time cancels editing.
action = view.el.querySelector('.chat-msg .chat-msg__action');
action = view.querySelector('.chat-msg .chat-msg__action');
action.style.opacity = 1;
action.click();
expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')) === true);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')) === true);
action = view.el.querySelector('.chat-msg .chat-msg__action');
action = view.querySelector('.chat-msg .chat-msg__action');
action.style.opacity = 1;
action.click();
expect(textarea.value).toBe('');
expect(view.model.messages.at(0).get('correcting')).toBe(false);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500);
// Test that messages from other users don't have the pencil icon
_converse.handleMessageStanza(
@ -263,12 +263,12 @@ describe("A Chat Message", function () {
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(2);
expect(view.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(2);
// Test confirmation dialog
spyOn(window, 'confirm').and.returnValue(true);
textarea.value = 'But soft, what light through yonder airlock breaks?';
action = view.el.querySelector('.chat-msg .chat-msg__action');
action = view.querySelector('.chat-msg .chat-msg__action');
action.style.opacity = 1;
action.click();
expect(window.confirm).toHaveBeenCalledWith(
@ -307,8 +307,8 @@ describe("A Chat Message", function () {
'id': msg_id,
}).c('body').t('But soft, what light through yonder airlock breaks?').tree());
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelector('.chat-msg__text').textContent)
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?');
_converse.handleMessageStanza($msg({
@ -320,10 +320,10 @@ describe("A Chat Message", function () {
.c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelector('.chat-msg__text').textContent)
expect(view.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder chimney breaks?');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
expect(view.model.messages.models.length).toBe(1);
_converse.handleMessageStanza($msg({
@ -335,11 +335,11 @@ describe("A Chat Message", function () {
.c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelector('.chat-msg__text').textContent)
expect(view.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder window breaks?');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
view.el.querySelector('.chat-msg__content .fa-edit').click();
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
view.querySelector('.chat-msg__content .fa-edit').click();
const modal = _converse.api.modal.get('message-versions-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
@ -382,9 +382,9 @@ describe("A Groupchat Message", function () {
'id': msg_id,
}).c('body').t('But soft, what light through yonder airlock breaks?').tree());
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelector('.chat-msg__text').textContent)
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?');
await view.model.handleMessageStanza($msg({
@ -394,10 +394,10 @@ describe("A Groupchat Message", function () {
'id': u.getUniqueId(),
}).c('body').t('But soft, what light through yonder chimney breaks?').up()
.c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent ===
'But soft, what light through yonder chimney breaks?', 500);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => view.el.querySelector('.chat-msg__content .fa-edit'));
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => view.querySelector('.chat-msg__content .fa-edit'));
await view.model.handleMessageStanza($msg({
'from': 'lounge@montague.lit/newguy',
@ -407,11 +407,11 @@ describe("A Groupchat Message", function () {
}).c('body').t('But soft, what light through yonder window breaks?').up()
.c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent ===
'But soft, what light through yonder window breaks?', 500);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
const edit = await u.waitUntil(() => view.el.querySelector('.chat-msg__content .fa-edit'));
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
const edit = await u.waitUntil(() => view.querySelector('.chat-msg__content .fa-edit'));
edit.click();
const modal = _converse.api.modal.get('message-versions-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
@ -461,11 +461,11 @@ describe("A Groupchat Message", function () {
'id': msg_id,
}).c('body').t('But soft, what light through yonder airlock breaks?').tree());
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
expect(view.el.querySelectorAll('.chat-msg__text')[0].textContent)
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
expect(view.querySelectorAll('.chat-msg').length).toBe(2);
expect(view.querySelectorAll('.chat-msg__text')[0].textContent)
.toBe('But soft, what light through yonder airlock breaks?');
expect(view.el.querySelectorAll('.chat-msg__text')[1].textContent)
expect(view.querySelectorAll('.chat-msg__text')[1].textContent)
.toBe('But soft, what light through yonder airlock breaks?');
// First message correction
@ -477,10 +477,10 @@ describe("A Groupchat Message", function () {
}).c('body').t('But soft, what light through yonder chimney breaks?').up()
.c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent ===
'But soft, what light through yonder chimney breaks?', 500);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
await u.waitUntil(() => view.el.querySelector('.chat-msg__content .fa-edit'));
expect(view.querySelectorAll('.chat-msg').length).toBe(2);
await u.waitUntil(() => view.querySelector('.chat-msg__content .fa-edit'));
// Second message correction
await view.model.handleMessageStanza($msg({
@ -499,15 +499,15 @@ describe("A Groupchat Message", function () {
'id': u.getUniqueId(),
}).c('body').t('But soft, what light through yonder window breaks?').tree());
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text')[0].textContent ===
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text')[0].textContent ===
'But soft, what light through yonder window breaks?', 500);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text')[2].textContent ===
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3);
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text')[2].textContent ===
'But soft, what light through yonder window breaks?', 500);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
const edit = await u.waitUntil(() => view.el.querySelector('.chat-msg__content .fa-edit'));
expect(view.querySelectorAll('.chat-msg').length).toBe(3);
expect(view.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
const edit = await u.waitUntil(() => view.querySelector('.chat-msg__content .fa-edit'));
edit.click();
const modal = _converse.api.modal.get('message-versions-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
@ -528,7 +528,7 @@ describe("A Groupchat Message", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
expect(textarea.value).toBe('');
view.onKeyDown({
target: textarea,
@ -542,8 +542,8 @@ describe("A Groupchat Message", function () {
preventDefault: function preventDefault () {},
keyCode: 13 // Enter
});
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
expect(view.el.querySelector('.chat-msg__text').textContent)
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
expect(view.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?');
const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
@ -554,8 +554,8 @@ describe("A Groupchat Message", function () {
});
expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')));
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')));
spyOn(_converse.connection, 'send');
textarea.value = 'But soft, what light through yonder window breaks?';
@ -588,8 +588,8 @@ describe("A Groupchat Message", function () {
expect(keys.length).toBe(1);
expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(u.hasClass('correcting', view.querySelector('.chat-msg'))).toBe(false);
// Check that messages from other users are skipped
await view.model.handleMessageStanza($msg({
@ -599,7 +599,7 @@ describe("A Groupchat Message", function () {
'type': 'groupchat'
}).c('body').t('Hello world').tree());
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
expect(view.querySelectorAll('.chat-msg').length).toBe(2);
// Test that pressing the down arrow cancels message correction
expect(textarea.value).toBe('');
@ -609,8 +609,8 @@ describe("A Groupchat Message", function () {
});
expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
expect(view.querySelectorAll('.chat-msg').length).toBe(2);
await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')), 500);
expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
view.onKeyDown({
target: textarea,
@ -618,8 +618,8 @@ describe("A Groupchat Message", function () {
});
expect(textarea.value).toBe('');
expect(view.model.messages.at(0).get('correcting')).toBe(false);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
await u.waitUntil(() => !u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
expect(view.querySelectorAll('.chat-msg').length).toBe(2);
await u.waitUntil(() => !u.hasClass('correcting', view.querySelector('.chat-msg')), 500);
done();
}));
});

View File

@ -21,12 +21,12 @@ describe("Emojis", function () {
await mock.openControlBox(_converse);
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
const toolbar = await u.waitUntil(() => view.el.querySelector('converse-chat-toolbar'));
const toolbar = await u.waitUntil(() => view.querySelector('converse-chat-toolbar'));
toolbar.querySelector('.toggle-emojis').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')), 1000);
const item = view.el.querySelector('.emoji-picker li.insert-emoji a');
await u.waitUntil(() => u.isVisible(view.querySelector('.emoji-picker__lists')), 1000);
const item = view.querySelector('.emoji-picker li.insert-emoji a');
item.click()
expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
expect(view.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
toolbar.querySelector('.toggle-emojis').click(); // Close the panel again
done();
}));
@ -39,8 +39,8 @@ describe("Emojis", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => view.el.querySelector('converse-emoji-dropdown'));
const textarea = view.el.querySelector('textarea.chat-textarea');
await u.waitUntil(() => view.querySelector('converse-emoji-dropdown'));
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = ':gri';
// Press tab
@ -52,14 +52,14 @@ describe("Emojis", function () {
'key': 'Tab'
}
view.onKeyDown(tab_event);
await u.waitUntil(() => view.el.querySelector('converse-emoji-picker .emoji-search').value === ':gri');
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji', view.el).length === 3, 1000);
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', view.el);
await u.waitUntil(() => view.querySelector('converse-emoji-picker .emoji-search').value === ':gri');
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji', view).length === 3, 1000);
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', view);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':grimacing:');
expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':grin:');
expect(visible_emojis[2].getAttribute('data-emoji')).toBe(':grinning:');
const picker = view.el.querySelector('converse-emoji-picker');
const picker = view.querySelector('converse-emoji-picker');
const input = picker.querySelector('.emoji-search');
// Test that TAB autocompletes the to first match
input.dispatchEvent(new KeyboardEvent('keydown', tab_event));
@ -92,7 +92,7 @@ describe("Emojis", function () {
textarea.value = ':use';
view.onKeyDown(tab_event);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
await u.waitUntil(() => u.isVisible(view.querySelector('.emoji-picker__lists')));
await u.waitUntil(() => input.value === ':use');
visible_emojis = sizzle('.insert-emoji:not(.hidden)', picker);
expect(visible_emojis.length).toBe(0);
@ -107,8 +107,8 @@ describe("Emojis", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => view.el.querySelector('converse-emoji-dropdown'));
const textarea = view.el.querySelector('textarea.chat-textarea');
await u.waitUntil(() => view.querySelector('converse-emoji-dropdown'));
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = ':';
// Press tab
const tab_event = {
@ -119,9 +119,9 @@ describe("Emojis", function () {
'key': 'Tab'
}
view.onKeyDown(tab_event);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
await u.waitUntil(() => u.isVisible(view.querySelector('.emoji-picker__lists')));
const picker = view.el.querySelector('converse-emoji-picker');
const picker = view.querySelector('converse-emoji-picker');
const input = picker.querySelector('.emoji-search');
expect(input.value).toBe(':');
input.value = ':gri';
@ -131,8 +131,8 @@ describe("Emojis", function () {
'stopPropagation': function stopPropagation () {}
};
input.dispatchEvent(new KeyboardEvent('keydown', event));
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji', view.el).length === 3, 1000);
let emoji = sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden) a', view.el).pop();
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji', view).length === 3, 1000);
let emoji = sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden) a', view).pop();
emoji.click();
await u.waitUntil(() => textarea.value === ':grinning: ');
textarea.value = ':grinning: :';
@ -141,8 +141,8 @@ describe("Emojis", function () {
await u.waitUntil(() => input.value === ':');
input.value = ':grimacing';
input.dispatchEvent(new KeyboardEvent('keydown', event));
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji', view.el).length === 1, 1000);
emoji = sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden) a', view.el).pop();
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji', view).length === 1, 1000);
emoji = sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden) a', view).pop();
emoji.click();
await u.waitUntil(() => textarea.value === ':grinning: :grimacing: ');
done();
@ -157,8 +157,8 @@ describe("Emojis", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => view.el.querySelector('converse-emoji-dropdown'));
const textarea = view.el.querySelector('textarea.chat-textarea');
await u.waitUntil(() => view.querySelector('converse-emoji-dropdown'));
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = ':gri';
// Press tab
@ -171,8 +171,8 @@ describe("Emojis", function () {
}
textarea.value = ':';
view.onKeyDown(tab_event);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
const picker = view.el.querySelector('converse-emoji-picker');
await u.waitUntil(() => u.isVisible(view.querySelector('.emoji-picker__lists')));
const picker = view.querySelector('converse-emoji-picker');
const input = picker.querySelector('.emoji-search');
input.dispatchEvent(new KeyboardEvent('keydown', tab_event));
await u.waitUntil(() => input.value === ':100:');
@ -182,12 +182,12 @@ describe("Emojis", function () {
textarea.value = ':';
view.onKeyDown(tab_event);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
await u.waitUntil(() => u.isVisible(view.querySelector('.emoji-picker__lists')));
await u.waitUntil(() => input.value === ':');
input.dispatchEvent(new KeyboardEvent('keydown', tab_event));
await u.waitUntil(() => input.value === ':100:');
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view.el).length === 1, 1000);
const emoji = sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden) a', view.el).pop();
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view).length === 1, 1000);
const emoji = sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden) a', view).pop();
emoji.click();
expect(textarea.value).toBe(':100: ');
done();
@ -202,13 +202,13 @@ describe("Emojis", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => view.el.querySelector('converse-emoji-dropdown'));
const toolbar = view.el.querySelector('converse-chat-toolbar');
await u.waitUntil(() => view.querySelector('converse-emoji-dropdown'));
const toolbar = view.querySelector('converse-chat-toolbar');
toolbar.querySelector('.toggle-emojis').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
await u.waitUntil(() => sizzle('converse-chat-toolbar .insert-emoji:not(.hidden)', view.el).length === 1589);
await u.waitUntil(() => u.isVisible(view.querySelector('.emoji-picker__lists')));
await u.waitUntil(() => sizzle('converse-chat-toolbar .insert-emoji:not(.hidden)', view).length === 1589);
const input = view.el.querySelector('.emoji-search');
const input = view.querySelector('.emoji-search');
input.value = 'smiley';
const event = {
'target': input,
@ -217,8 +217,8 @@ describe("Emojis", function () {
};
input.dispatchEvent(new KeyboardEvent('keydown', event));
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view.el).length === 2, 1000);
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view.el);
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view).length === 2, 1000);
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':smiley_cat:');
@ -230,25 +230,25 @@ describe("Emojis", function () {
// Check that search results update when chars are deleted
input.value = 'sm';
input.dispatchEvent(new KeyboardEvent('keydown', event));
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view.el).length === 25, 1000);
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view).length === 25, 1000);
input.value = 'smiley';
input.dispatchEvent(new KeyboardEvent('keydown', event));
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view.el).length === 2, 1000);
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view).length === 2, 1000);
// Test that TAB autocompletes the to first match
const tab_event = Object.assign({}, event, {'keyCode': 9, 'key': 'Tab'});
input.dispatchEvent(new KeyboardEvent('keydown', tab_event));
await u.waitUntil(() => input.value === ':smiley:');
await u.waitUntil(() => sizzle(".emojis-lists__container--search .insert-emoji:not('.hidden')", view.el).length === 1, 1000);
visible_emojis = sizzle(".emojis-lists__container--search .insert-emoji:not('.hidden')", view.el);
await u.waitUntil(() => sizzle(".emojis-lists__container--search .insert-emoji:not('.hidden')", view).length === 1, 1000);
visible_emojis = sizzle(".emojis-lists__container--search .insert-emoji:not('.hidden')", view);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
// Check that ENTER now inserts the match
input.dispatchEvent(new KeyboardEvent('keydown', enter_event));
await u.waitUntil(() => input.value === '');
expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
expect(view.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
done();
}));
});
@ -288,7 +288,7 @@ describe("Emojis", function () {
// Test that a modified message that no longer contains only
// emojis now renders normally again.
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = ':poop: :innocent:';
view.onKeyDown({
target: textarea,
@ -296,7 +296,7 @@ describe("Emojis", function () {
keyCode: 13 // Enter
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
expect(view.querySelectorAll('.chat-msg').length).toBe(3);
const last_msg_sel = 'converse-chat-message:last-child .chat-msg__text';
await u.waitUntil(() => view.content.querySelector(last_msg_sel).textContent === '💩 😇');
@ -308,7 +308,7 @@ describe("Emojis", function () {
expect(textarea.value).toBe('💩 😇');
expect(view.model.messages.at(2).get('correcting')).toBe(true);
sel = 'converse-chat-message:last-child .chat-msg'
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector(sel)), 500);
await u.waitUntil(() => u.hasClass('correcting', view.querySelector(sel)), 500);
textarea.value = textarea.value += 'This is no longer an emoji-only message';
view.onKeyDown({
target: textarea,
@ -368,7 +368,7 @@ describe("Emojis", function () {
expect(imgs.length).toBe(1);
expect(imgs[0].src).toBe(_converse.api.settings.get('emoji_image_path')+'/72x72/1f607.png');
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = ':poop: :innocent:';
view.onKeyDown({
target: textarea,
@ -411,15 +411,15 @@ describe("Emojis", function () {
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.api.chatviews.get(contact_jid);
const toolbar = await u.waitUntil(() => view.el.querySelector('.chat-toolbar'));
const toolbar = await u.waitUntil(() => view.querySelector('.chat-toolbar'));
toolbar.querySelector('.toggle-emojis').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')), 1000);
const picker = await u.waitUntil(() => view.el.querySelector('converse-emoji-picker'), 1000);
await u.waitUntil(() => u.isVisible(view.querySelector('.emoji-picker__lists')), 1000);
const picker = await u.waitUntil(() => view.querySelector('converse-emoji-picker'), 1000);
const custom_category = picker.querySelector('.pick-category[data-category="custom"]');
expect(custom_category.innerHTML.replace(/<!---->/g, '').trim()).toBe(
'<img class="emoji" draggable="false" title=":xmpp:" alt=":xmpp:" src="/dist/images/custom_emojis/xmpp.png">');
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = 'Running tests for :converse:';
view.onKeyDown({
target: textarea,
@ -427,7 +427,7 @@ describe("Emojis", function () {
keyCode: 13 // Enter
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
const body = view.el.querySelector('converse-chat-message-body');
const body = view.querySelector('converse-chat-message-body');
await u.waitUntil(() => body.innerHTML.replace(/<!---->/g, '').trim() ===
'Running tests for <img class="emoji" draggable="false" title=":converse:" alt=":converse:" src="/dist/images/custom_emojis/converse.png">');
done();

View File

@ -25,7 +25,7 @@ describe("A XEP-0317 MUC Hat", function () {
</hats>
</presence>
`)));
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.trim() ===
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"romeo and Terry have entered the groupchat");
let hats = view.model.getOccupant("Terry").get('hats');
@ -38,7 +38,7 @@ describe("A XEP-0317 MUC Hat", function () {
</message>
`)));
const msg_el = await u.waitUntil(() => view.el.querySelector('.chat-msg'));
const msg_el = await u.waitUntil(() => view.querySelector('.chat-msg'));
let badges = Array.from(msg_el.querySelectorAll('.badge'));
expect(badges.length).toBe(2);
expect(badges.map(b => b.textContent.trim()).join(' ' )).toBe("Teacher's Assistant Dark Mage");
@ -60,8 +60,8 @@ describe("A XEP-0317 MUC Hat", function () {
await u.waitUntil(() => view.model.getOccupant("Terry").get('hats').length === 3);
hats = view.model.getOccupant("Terry").get('hats');
expect(hats.map(h => h.title).join(' ')).toBe("Teacher's Assistant Dark Mage Mad hatter");
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg .badge').length === 3, 1000);
badges = Array.from(view.el.querySelectorAll('.chat-msg .badge'));
await u.waitUntil(() => view.querySelectorAll('.chat-msg .badge').length === 3, 1000);
badges = Array.from(view.querySelectorAll('.chat-msg .badge'));
expect(badges.map(b => b.textContent.trim()).join(' ' )).toBe("Teacher's Assistant Dark Mage Mad hatter");
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
@ -72,7 +72,7 @@ describe("A XEP-0317 MUC Hat", function () {
</presence>
`)));
await u.waitUntil(() => view.model.getOccupant("Terry").get('hats').length === 0);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg .badge').length === 0);
await u.waitUntil(() => view.querySelectorAll('.chat-msg .badge').length === 0);
done();
}));
})

View File

@ -62,7 +62,7 @@ describe("A headlines box", function () {
await u.waitUntil(() => _converse.chatboxviews.keys().includes('notify.example.com'));
const view = _converse.chatboxviews.get('notify.example.com');
expect(view.model.get('show_avatar')).toBeFalsy();
expect(view.el.querySelector('img.avatar')).toBe(null);
expect(view.querySelector('img.avatar')).toBe(null);
done();
}));
@ -96,9 +96,9 @@ describe("A headlines box", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
const view = _converse.chatboxviews.get('controlbox');
await u.waitUntil(() => view.el.querySelectorAll(".open-headline").length);
expect(view.el.querySelectorAll('.open-headline').length).toBe(1);
expect(view.el.querySelector('.open-headline').text).toBe('notify.example.com');
await u.waitUntil(() => view.querySelectorAll(".open-headline").length);
expect(view.querySelectorAll('.open-headline').length).toBe(1);
expect(view.querySelector('.open-headline').text).toBe('notify.example.com');
done();
}));
@ -133,13 +133,13 @@ describe("A headlines box", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
const cbview = _converse.chatboxviews.get('controlbox');
await u.waitUntil(() => cbview.el.querySelectorAll(".open-headline").length);
await u.waitUntil(() => cbview.querySelectorAll(".open-headline").length);
const hlview = _converse.chatboxviews.get('notify.example.com');
await u.isVisible(hlview.el);
const close_el = await u.waitUntil(() => hlview.el.querySelector('.close-chatbox-button'));
await u.isVisible(hlview);
const close_el = await u.waitUntil(() => hlview.querySelector('.close-chatbox-button'));
close_el.click();
await u.waitUntil(() => cbview.el.querySelectorAll(".open-headline").length === 0);
expect(cbview.el.querySelectorAll('.open-headline').length).toBe(0);
await u.waitUntil(() => cbview.querySelectorAll(".open-headline").length === 0);
expect(cbview.querySelectorAll('.open-headline').length).toBe(0);
done();
}));

View File

@ -158,7 +158,7 @@ describe("XEP-0363: HTTP File Upload", function () {
await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], [], 'items');
const view = _converse.chatboxviews.get(contact_jid);
expect(view.el.querySelector('.chat-toolbar .fileupload')).toBe(null);
expect(view.querySelector('.chat-toolbar .fileupload')).toBe(null);
done();
}));
@ -174,7 +174,7 @@ describe("XEP-0363: HTTP File Upload", function () {
await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], [], 'items');
const view = _converse.chatboxviews.get('lounge@montague.lit');
await u.waitUntil(() => view.el.querySelector('.chat-toolbar .fileupload') === null);
await u.waitUntil(() => view.querySelector('.chat-toolbar .fileupload') === null);
expect(1).toBe(1);
done();
}));
@ -198,7 +198,7 @@ describe("XEP-0363: HTTP File Upload", function () {
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
const el = await u.waitUntil(() => view.el.querySelector('.chat-toolbar .fileupload'));
const el = await u.waitUntil(() => view.querySelector('.chat-toolbar .fileupload'));
expect(el).not.toEqual(null);
done();
}));
@ -217,7 +217,7 @@ describe("XEP-0363: HTTP File Upload", function () {
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
await u.waitUntil(() => _converse.chatboxviews.get('lounge@montague.lit').el.querySelector('.fileupload'));
const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.el.querySelector('.chat-toolbar .fileupload')).not.toBe(null);
expect(view.querySelector('.chat-toolbar .fileupload')).not.toBe(null);
done();
}));
@ -283,12 +283,12 @@ describe("XEP-0363: HTTP File Upload", function () {
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
const message = view.model.messages.at(0);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
expect(view.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
message.set('progress', 0.5);
u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5')
.then(() => {
message.set('progress', 1);
u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '1')
}).then(() => {
message.save({
'upload': _converse.SUCCESS,
@ -317,13 +317,13 @@ describe("XEP-0363: HTTP File Upload", function () {
`</x>`+
`<origin-id id="${sent_stanza.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
`</message>`);
const img_link_el = await u.waitUntil(() => view.el.querySelector('converse-chat-message-body .chat-image__link'), 1000);
const img_link_el = await u.waitUntil(() => view.querySelector('converse-chat-message-body .chat-image__link'), 1000);
// Check that the image renders
expect(img_link_el.outerHTML.replace(/<!---->/g, '').trim()).toEqual(
`<a class="chat-image__link" target="_blank" rel="noopener" href="${base_url}/logo/conversejs-filled.svg">`+
`<img class="chat-image img-thumbnail" src="${base_url}/logo/conversejs-filled.svg"></a>`);
expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.replace(/<!---->/g, '').trim()).toEqual(
expect(view.querySelector('.chat-msg .chat-msg__media').innerHTML.replace(/<!---->/g, '').trim()).toEqual(
`<a target="_blank" rel="noopener" href="${base_url}/logo/conversejs-filled.svg">`+
`Download image file "conversejs-filled.svg"</a>`);
XMLHttpRequest.prototype.send = send_backup;
@ -391,12 +391,12 @@ describe("XEP-0363: HTTP File Upload", function () {
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
const message = view.model.messages.at(0);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
expect(view.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
message.set('progress', 0.5);
u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5')
.then(() => {
message.set('progress', 1);
u.waitUntil(() => view.el.querySelector('.chat-content progress')?.getAttribute('value') === '1')
u.waitUntil(() => view.querySelector('.chat-content progress')?.getAttribute('value') === '1')
}).then(() => {
message.save({
'upload': _converse.SUCCESS,
@ -425,13 +425,13 @@ describe("XEP-0363: HTTP File Upload", function () {
`</x>`+
`<origin-id id="${sent_stanza.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
`</message>`);
const img_link_el = await u.waitUntil(() => view.el.querySelector('converse-chat-message-body .chat-image__link'), 1000);
const img_link_el = await u.waitUntil(() => view.querySelector('converse-chat-message-body .chat-image__link'), 1000);
// Check that the image renders
expect(img_link_el.outerHTML.replace(/<!---->/g, '').trim()).toEqual(
`<a class="chat-image__link" target="_blank" rel="noopener" href="${base_url}/logo/conversejs-filled.svg">`+
`<img class="chat-image img-thumbnail" src="${base_url}/logo/conversejs-filled.svg"></a>`);
expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.replace(/<!---->/g, '').trim()).toEqual(
expect(view.querySelector('.chat-msg .chat-msg__media').innerHTML.replace(/<!---->/g, '').trim()).toEqual(
`<a target="_blank" rel="noopener" href="${base_url}/logo/conversejs-filled.svg">`+
`Download image file "conversejs-filled.svg"</a>`);
@ -548,8 +548,8 @@ describe("XEP-0363: HTTP File Upload", function () {
'name': "my-juliet.jpg"
};
view.model.sendFiles([file]);
await u.waitUntil(() => view.el.querySelectorAll('.message').length)
const messages = view.el.querySelectorAll('.message.chat-error');
await u.waitUntil(() => view.querySelectorAll('.message').length)
const messages = view.querySelectorAll('.message.chat-error');
expect(messages.length).toBe(1);
expect(messages[0].textContent.trim()).toBe(
'The size of your file, my-juliet.jpg, exceeds the maximum allowed by your server, which is 5 MB.');
@ -618,12 +618,12 @@ describe("XEP-0363: HTTP File Upload", function () {
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(async () => {
const message = view.model.messages.at(0);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
expect(view.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
message.set('progress', 0.5);
await u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5');
await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5');
message.set('progress', 1);
await u.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1');
expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB');
await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '1');
expect(view.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB');
done();
});
_converse.connection._dataRecv(mock.createRequest(stanza));

View File

@ -13,25 +13,25 @@ describe("The Login Form", function () {
const cbview = await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
mock.toggleControlBox();
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
const checkboxes = cbview.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1);
const checkbox = checkboxes[0];
const label = cbview.el.querySelector(`label[for="${checkbox.getAttribute('id')}"]`);
const label = cbview.querySelector(`label[for="${checkbox.getAttribute('id')}"]`);
expect(label.textContent).toBe('This is a trusted device');
expect(checkbox.checked).toBe(true);
cbview.el.querySelector('input[name="jid"]').value = 'romeo@montague.lit';
cbview.el.querySelector('input[name="password"]').value = 'secret';
cbview.querySelector('input[name="jid"]').value = 'romeo@montague.lit';
cbview.querySelector('input[name="password"]').value = 'secret';
expect(_converse.config.get('trusted')).toBe(true);
expect(_converse.getDefaultStore()).toBe('persistent');
cbview.el.querySelector('input[type="submit"]').click();
cbview.querySelector('input[type="submit"]').click();
expect(_converse.config.get('trusted')).toBe(true);
expect(_converse.getDefaultStore()).toBe('persistent');
checkbox.click();
cbview.el.querySelector('input[type="submit"]').click();
cbview.querySelector('input[type="submit"]').click();
expect(_converse.config.get('trusted')).toBe(false);
expect(_converse.getDefaultStore()).toBe('session');
done();
@ -48,23 +48,23 @@ describe("The Login Form", function () {
await u.waitUntil(() => _converse.chatboxviews.get('controlbox'))
const cbview = _converse.chatboxviews.get('controlbox');
mock.toggleControlBox();
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
const checkboxes = cbview.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1);
const checkbox = checkboxes[0];
const label = cbview.el.querySelector(`label[for="${checkbox.getAttribute('id')}"]`);
const label = cbview.querySelector(`label[for="${checkbox.getAttribute('id')}"]`);
expect(label.textContent).toBe('This is a trusted device');
expect(checkbox.checked).toBe(false);
cbview.el.querySelector('input[name="jid"]').value = 'romeo@montague.lit';
cbview.el.querySelector('input[name="password"]').value = 'secret';
cbview.querySelector('input[name="jid"]').value = 'romeo@montague.lit';
cbview.querySelector('input[name="password"]').value = 'secret';
cbview.el.querySelector('input[type="submit"]').click();
cbview.querySelector('input[type="submit"]').click();
expect(_converse.config.get('trusted')).toBe(false);
expect(_converse.getDefaultStore()).toBe('session');
checkbox.click();
cbview.el.querySelector('input[type="submit"]').click();
cbview.querySelector('input[type="submit"]').click();
expect(_converse.config.get('trusted')).toBe(true);
expect(_converse.getDefaultStore()).toBe('persistent');
done();

View File

@ -1175,7 +1175,7 @@ describe("Chatboxes", function () {
expect(view.model.messages.at(0).get('type')).toBe('error');
expect(view.model.messages.at(0).get('message')).toBe('Timeout while trying to fetch archived messages.');
let err_message = await u.waitUntil(() => view.el.querySelector('.message.chat-error'));
let err_message = await u.waitUntil(() => view.querySelector('.message.chat-error'));
err_message.querySelector('.retry').click();
while (_converse.connection.IQ_stanzas.length) {
@ -1226,7 +1226,7 @@ describe("Chatboxes", function () {
.c('count').t('2');
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.model.messages.length === 2, 500);
err_message = view.el.querySelector('.message.chat-error');
err_message = view.querySelector('.message.chat-error');
expect(err_message).toBe(null);
done();
}));

View File

@ -98,7 +98,7 @@ describe("A XEP-0333 Chat Marker", function () {
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.model.messages.length).toBe(1);
stanza = u.toStanza(
@ -116,7 +116,7 @@ describe("A XEP-0333 Chat Marker", function () {
spyOn(_converse.api, "trigger").and.callThrough();
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => _converse.api.trigger.calls.count(), 500);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.model.messages.length).toBe(1);
done();
}));
@ -131,7 +131,7 @@ describe("A XEP-0333 Chat Marker", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = 'But soft, what light through yonder airlock breaks?';
view.onKeyDown({
target: textarea,
@ -139,8 +139,8 @@ describe("A XEP-0333 Chat Marker", function () {
keyCode: 13 // Enter
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelector('.chat-msg .chat-msg__body').textContent.trim())
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelector('.chat-msg .chat-msg__body').textContent.trim())
.toBe("But soft, what light through yonder airlock breaks?");
const msg_obj = view.model.messages.at(0);
@ -150,8 +150,8 @@ describe("A XEP-0333 Chat Marker", function () {
<received xmlns="urn:xmpp:chat-markers:0" id="${msg_obj.get('msgid')}"/>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
expect(view.querySelectorAll('.chat-msg__receipt').length).toBe(0);
stanza = u.toStanza(`
<message xml:lang="en" to="romeo@montague.lit/orchard"
@ -159,8 +159,8 @@ describe("A XEP-0333 Chat Marker", function () {
<displayed xmlns="urn:xmpp:chat-markers:0" id="${msg_obj.get('msgid')}"/>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg__receipt').length).toBe(0);
stanza = u.toStanza(`
<message xml:lang="en" to="romeo@montague.lit/orchard"
@ -169,8 +169,8 @@ describe("A XEP-0333 Chat Marker", function () {
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg__receipt').length).toBe(0);
stanza = u.toStanza(`
<message xml:lang="en" to="romeo@montague.lit/orchard"
@ -179,8 +179,8 @@ describe("A XEP-0333 Chat Marker", function () {
<markable xmlns="urn:xmpp:chat-markers:0"/>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
expect(view.querySelectorAll('.chat-msg__receipt').length).toBe(0);
done();
}));
});

View File

@ -14,7 +14,7 @@ describe("A Groupchat Message", function () {
await mock.waitForRoster(_converse, 'current');
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) {
if (!view.querySelectorAll('.chat-area').length) {
view.renderChatArea();
}
let message = '/me is tired';
@ -27,8 +27,8 @@ describe("A Groupchat Message", function () {
}).c('body').t(message).tree();
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => sizzle('.chat-msg:last .chat-msg__text', view.content).pop());
expect(view.el.querySelector('.chat-msg__author').textContent.includes('**Dyon van de Wege')).toBeTruthy();
expect(view.el.querySelector('.chat-msg__text').textContent.trim()).toBe('is tired');
expect(view.querySelector('.chat-msg__author').textContent.includes('**Dyon van de Wege')).toBeTruthy();
expect(view.querySelector('.chat-msg__text').textContent.trim()).toBe('is tired');
message = '/me is as well';
msg = $msg({
@ -38,9 +38,9 @@ describe("A Groupchat Message", function () {
type: 'groupchat'
}).c('body').t(message).tree();
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
expect(sizzle('.chat-msg__author:last', view.el).pop().textContent.includes('**Romeo Montague')).toBeTruthy();
expect(sizzle('.chat-msg__text:last', view.el).pop().textContent.trim()).toBe('is as well');
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
expect(sizzle('.chat-msg__author:last', view).pop().textContent.includes('**Romeo Montague')).toBeTruthy();
expect(sizzle('.chat-msg__text:last', view).pop().textContent.trim()).toBe('is as well');
// Check rendering of a mention inside a me message
const msg_text = "/me mentions romeo";
@ -52,8 +52,8 @@ describe("A Groupchat Message", function () {
}).c('body').t(msg_text).up()
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'13', 'end':'19', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).nodeTree;
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3);
await u.waitUntil(() => sizzle('.chat-msg__text:last', view.el).pop().innerHTML.replace(/<!---->/g, '') ===
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3);
await u.waitUntil(() => sizzle('.chat-msg__text:last', view).pop().innerHTML.replace(/<!---->/g, '') ===
'mentions <span class="mention mention--self badge badge-info">romeo</span>');
done();
}));
@ -79,16 +79,16 @@ describe("A Message", function () {
await _converse.handleMessageStanza(msg);
const view = _converse.chatboxviews.get(sender_jid);
await u.waitUntil(() => view.el.querySelector('.chat-msg__text'));
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(1);
expect(view.el.querySelector('.chat-msg__author').textContent.includes('**Mercutio')).toBeTruthy();
expect(view.el.querySelector('.chat-msg__text').textContent).toBe('is tired');
await u.waitUntil(() => view.querySelector('.chat-msg__text'));
expect(view.querySelectorAll('.chat-msg--action').length).toBe(1);
expect(view.querySelector('.chat-msg__author').textContent.includes('**Mercutio')).toBeTruthy();
expect(view.querySelector('.chat-msg__text').textContent).toBe('is tired');
message = '/me is as well';
await mock.sendMessage(view, message);
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2);
await u.waitUntil(() => sizzle('.chat-msg__author:last', view.el).pop().textContent.trim() === '**Romeo Montague');
const last_el = sizzle('.chat-msg__text:last', view.el).pop();
expect(view.querySelectorAll('.chat-msg--action').length).toBe(2);
await u.waitUntil(() => sizzle('.chat-msg__author:last', view).pop().textContent.trim() === '**Romeo Montague');
const last_el = sizzle('.chat-msg__text:last', view).pop();
await u.waitUntil(() => last_el.textContent === 'is as well');
expect(u.hasClass('chat-msg--followup', last_el)).toBe(false);
@ -97,18 +97,18 @@ describe("A Message", function () {
message = 'This a normal message';
await mock.sendMessage(view, message);
const msg_txt_sel = 'converse-chat-message:last-child .chat-msg__text';
await u.waitUntil(() => view.el.querySelector(msg_txt_sel).textContent.trim() === message);
let el = view.el.querySelector('converse-chat-message:last-child .chat-msg__body');
await u.waitUntil(() => view.querySelector(msg_txt_sel).textContent.trim() === message);
let el = view.querySelector('converse-chat-message:last-child .chat-msg__body');
expect(u.hasClass('chat-msg--followup', el)).toBeFalsy();
message = '/me wrote a 3rd person message';
await mock.sendMessage(view, message);
await u.waitUntil(() => view.el.querySelector(msg_txt_sel).textContent.trim() === message.replace('/me ', ''));
el = view.el.querySelector('converse-chat-message:last-child .chat-msg__body');
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(3);
await u.waitUntil(() => view.querySelector(msg_txt_sel).textContent.trim() === message.replace('/me ', ''));
el = view.querySelector('converse-chat-message:last-child .chat-msg__body');
expect(view.querySelectorAll('.chat-msg--action').length).toBe(3);
expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe('wrote a 3rd person message');
expect(u.isVisible(sizzle('.chat-msg__author:last', view.el).pop())).toBeTruthy();
expect(sizzle('.chat-msg__text:last', view).pop().textContent).toBe('wrote a 3rd person message');
expect(u.isVisible(sizzle('.chat-msg__author:last', view).pop())).toBeTruthy();
done();
}));
});

View File

@ -14,7 +14,7 @@ describe("An incoming groupchat message", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
if (!view.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
const message = 'romeo: Your attention is required';
const nick = mock.chatroom_names[0],
msg = $msg({
@ -25,7 +25,7 @@ describe("An incoming groupchat message", function () {
}).c('body').t(message).tree();
await view.model.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(u.hasClass('mentioned', view.el.querySelector('.chat-msg'))).toBeTruthy();
expect(u.hasClass('mentioned', view.querySelector('.chat-msg'))).toBeTruthy();
done();
}));
@ -62,7 +62,7 @@ describe("An incoming groupchat message", function () {
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up()
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree;
await view.model.handleMessageStanza(msg);
let message = await u.waitUntil(() => view.el.querySelector('.chat-msg__text'));
let message = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
expect(message.classList.length).toEqual(1);
expect(message.innerHTML.replace(/<!---->/g, '')).toBe(
'hello <span class="mention">z3r0</span> '+
@ -77,7 +77,7 @@ describe("An incoming groupchat message", function () {
}).c('body').t('https://conversejs.org/@gibson').up()
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'23', 'end':'29', 'type':'mention', 'uri':'xmpp:gibson@montague.lit'}).nodeTree;
await view.model.handleMessageStanza(msg);
message = await u.waitUntil(() => view.el.querySelector('.chat-msg__text'));
message = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
expect(message.classList.length).toEqual(1);
expect(message.innerHTML.replace(/<!---->/g, '')).toBe(
'hello <span class="mention">z3r0</span> '+
@ -119,7 +119,7 @@ describe("An incoming groupchat message", function () {
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'16', 'end':'24', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree;
await view.model.handleMessageStanza(msg);
const message = await u.waitUntil(() => view.el.querySelector('.chat-msg__text'));
const message = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
expect(message.classList.length).toEqual(1);
expect(message.innerHTML.replace(/<!---->/g, '')).toBe(
'<blockquote>hello <span class="mention">z3r0</span> <span class="mention mention--self badge badge-info">tom</span> <span class="mention">mr.robot</span>, how are you?</blockquote>');
@ -318,7 +318,7 @@ describe("A sent groupchat message", function () {
})));
await u.waitUntil(() => view.model.occupants.length === 2);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = 'hello @Link Mauve'
const enter_event = {
'target': textarea,
@ -379,7 +379,7 @@ describe("A sent groupchat message", function () {
});
await u.waitUntil(() => view.model.occupants.length === 5);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = 'hello @z3r0 @gibson @mr.robot, how are you?'
const enter_event = {
'target': textarea,
@ -410,19 +410,19 @@ describe("A sent groupchat message", function () {
`<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
`</message>`);
const action = await u.waitUntil(() => view.el.querySelector('.chat-msg .chat-msg__action'));
const action = await u.waitUntil(() => view.querySelector('.chat-msg .chat-msg__action'));
action.style.opacity = 1;
action.click();
expect(textarea.value).toBe('hello @z3r0 @gibson @mr.robot, how are you?');
expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')), 500);
await u.waitUntil(() => _converse.connection.send.calls.count() === 2);
textarea.value = 'hello @z3r0 @gibson @sw0rdf1sh, how are you?';
view.onKeyDown(enter_event);
await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent ===
'hello z3r0 gibson sw0rdf1sh, how are you?', 500);
const correction = _converse.connection.send.calls.all()[2].args[0];
@ -465,7 +465,7 @@ describe("A sent groupchat message", function () {
await u.waitUntil(() => view.model.occupants.length === 5);
spyOn(_converse.connection, 'send');
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = 'hello @z3r0 @gibson @mr.robot, how are you?'
const enter_event = {
'target': textarea,
@ -501,7 +501,7 @@ describe("A sent groupchat message", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'tom', [], members);
const view = _converse.api.chatviews.get(muc_jid);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = "Welcome @gibson 💩 We have a guide on how to do that here: https://conversejs.org/docs/html/index.html";
const enter_event = {
'target': textarea,
@ -510,7 +510,7 @@ describe("A sent groupchat message", function () {
'keyCode': 13 // Enter
}
view.onKeyDown(enter_event);
const message = await u.waitUntil(() => view.el.querySelector('.chat-msg__text'));
const message = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
expect(message.innerHTML.replace(/<!---->/g, '')).toEqual(
`Welcome <span class="mention">gibson</span> <span title=":poop:">💩</span> `+
`We have a guide on how to do that here: `+

View File

@ -15,7 +15,7 @@ describe("A Chat Message", function () {
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.api.chatviews.get(contact_jid);
await _converse.handleMessageStanza(mock.createChatMessage(_converse, contact_jid, 'This message will be read'));
const msg_el = await u.waitUntil(() => view.el.querySelector('converse-chat-message'));
const msg_el = await u.waitUntil(() => view.querySelector('converse-chat-message'));
expect(msg_el.querySelector('.chat-msg__text').textContent).toBe('This message will be read');
expect(view.model.get('num_unread')).toBe(0);
@ -26,8 +26,8 @@ describe("A Chat Message", function () {
expect(view.model.get('num_unread')).toBe(1);
expect(view.model.get('first_unread_id')).toBe(view.model.messages.last().get('id'));
await u.waitUntil(() => view.el.querySelectorAll('converse-chat-message').length === 2);
const last_msg_el = view.el.querySelector('converse-chat-message:last-child');
await u.waitUntil(() => view.querySelectorAll('converse-chat-message').length === 2);
const last_msg_el = view.querySelector('converse-chat-message:last-child');
expect(last_msg_el.firstElementChild?.textContent).toBe('New messages');
done();
}));
@ -87,7 +87,7 @@ describe("A Chat Message", function () {
await mock.openControlBox(_converse);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length)
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length)
_converse.filter_by_resource = true;
let msg = $msg({
@ -174,7 +174,7 @@ describe("A Chat Message", function () {
.c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
.tree();
_converse.handleMessageStanza(msg);
const csntext = await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent);
const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent);
expect(csntext.trim()).toEqual('Mercutio is typing');
msg = $msg({
@ -365,7 +365,7 @@ describe("A Chat Message", function () {
expect(msg_obj.get('sender')).toEqual('me');
expect(msg_obj.get('is_delayed')).toEqual(false);
// Now check that the message appears inside the chatbox in the DOM
const msg_el = await u.waitUntil(() => view.el.querySelector('.chat-content .chat-msg .chat-msg__text'));
const msg_el = await u.waitUntil(() => view.querySelector('.chat-content .chat-msg .chat-msg__text'));
expect(msg_el.textContent).toEqual(msgtext);
done();
}));
@ -428,7 +428,7 @@ describe("A Chat Message", function () {
const contact_name = mock.cur_names[1];
const contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, contact_jid);
const one_day_ago = dayjs().subtract(1, 'day');
@ -646,7 +646,7 @@ describe("A Chat Message", function () {
const view = _converse.api.chatviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
mock.sendMessage(view, message);
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000)
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length, 1000)
expect(view.model.sendMessage).toHaveBeenCalled();
let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.innerHTML.replace(/<!---->/g, '').trim()).toEqual(
@ -656,7 +656,7 @@ describe("A Chat Message", function () {
message += "?param1=val1&param2=val2";
mock.sendMessage(view, message);
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 2, 1000);
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 2, 1000);
expect(view.model.sendMessage).toHaveBeenCalled();
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.innerHTML.replace(/<!---->/g, '').trim()).toEqual(
@ -667,7 +667,7 @@ describe("A Chat Message", function () {
// Test now with two images in one message
message += ' hello world '+base_url+"/logo/conversejs-filled.svg";
mock.sendMessage(view, message);
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 4, 1000);
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 4, 1000);
expect(view.model.sendMessage).toHaveBeenCalled();
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.textContent.trim()).toEqual('hello world');
@ -677,11 +677,11 @@ describe("A Chat Message", function () {
_converse.api.settings.set('image_urls_regex', /^https?:\/\/(?:www.)?(?:imgur\.com\/\w{7})\/?$/i);
message = 'https://imgur.com/oxymPax';
mock.sendMessage(view, message);
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 5, 1000);
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 5, 1000);
expect(view.content.querySelectorAll('.chat-content .chat-image').length).toBe(5);
// Check that the Imgur URL gets a .png attached to make it render
await u.waitUntil(() => Array.from(view.el.querySelectorAll('.chat-content .chat-image')).pop().src.endsWith('png'), 1000);
await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-content .chat-image')).pop().src.endsWith('png'), 1000);
done();
}));
@ -698,12 +698,12 @@ describe("A Chat Message", function () {
const view = _converse.api.chatviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
mock.sendMessage(view, message);
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg').length === 1);
message = base_url+"/logo/conversejs-filled.svg";
mock.sendMessage(view, message);
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg').length === 2, 1000);
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 1, 1000)
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg').length === 2, 1000);
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 1, 1000)
expect(view.content.querySelectorAll('.chat-content .chat-image').length).toBe(1);
done();
@ -722,7 +722,7 @@ describe("A Chat Message", function () {
const view = _converse.api.chatviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
mock.sendMessage(view, message);
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000)
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length, 1000)
expect(view.model.sendMessage).toHaveBeenCalled();
const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
await u.waitUntil(() => msg.innerHTML.replace(/<!---->/g, '').trim() ==
@ -770,10 +770,10 @@ describe("A Chat Message", function () {
expect(chatbox.messages.models.length, 1);
const msg_object = chatbox.messages.models[0];
const msg_author = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__author');
const msg_author = view.querySelector('.chat-content .chat-msg:last-child .chat-msg__author');
expect(msg_author.textContent.trim()).toBe('Romeo Montague');
const msg_time = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__time');
const msg_time = view.querySelector('.chat-content .chat-msg:last-child .chat-msg__time');
const time = dayjs(msg_object.get('time')).format(_converse.time_format);
expect(msg_time.textContent).toBe(time);
done();
@ -790,7 +790,7 @@ describe("A Chat Message", function () {
const base_time = new Date();
const ONE_MINUTE_LATER = 60000;
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length, 300);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.filter_by_resource = true;
@ -992,7 +992,7 @@ describe("A Chat Message", function () {
const include_nick = false;
await mock.waitForRoster(_converse, 'current', 1, include_nick);
await mock.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length, 300);
spyOn(_converse.api, "trigger").and.callThrough();
const message = 'This is a received message';
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -1036,7 +1036,7 @@ describe("A Chat Message", function () {
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current', 1, false);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length, 300);
const message = '\n\n This is a received message \n\n';
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await _converse.handleMessageStanza(
@ -1100,13 +1100,13 @@ describe("A Chat Message", function () {
const chatbox = await _converse.api.chats.get(sender_jid);
expect(chatbox.get('fullname') === sender_jid);
await u.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
let author_el = view.el.querySelector('.chat-msg__author');
await u.waitUntil(() => view.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
let author_el = view.querySelector('.chat-msg__author');
expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy();
await u.waitUntil(() => vcard_fetched, 100);
expect(_converse.api.vcard.get).toHaveBeenCalled();
await u.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0])
author_el = view.el.querySelector('.chat-msg__author');
author_el = view.querySelector('.chat-msg__author');
expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy();
done();
}));
@ -1159,7 +1159,7 @@ describe("A Chat Message", function () {
expect(msg_obj.get('sender')).toEqual('them');
expect(msg_obj.get('is_delayed')).toEqual(false);
await u.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
await u.waitUntil(() => view.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
// Now check that the message appears inside the chatbox in the DOM
expect(view.content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
expect(view.content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
@ -1363,7 +1363,7 @@ describe("A Chat Message", function () {
}
await Promise.all(promises);
const indicator_el = view.el.querySelector('.new-msgs-indicator');
const indicator_el = view.querySelector('.new-msgs-indicator');
expect(u.isVisible(indicator_el)).toBeTruthy();
expect(view.model.get('scrolled')).toBe(true);
@ -1380,7 +1380,7 @@ describe("A Chat Message", function () {
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current');
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length)
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length)
// Send a message from a different resource
spyOn(converse.env.log, 'error');
spyOn(_converse.api.chatboxes, 'create').and.callThrough();
@ -1444,12 +1444,12 @@ describe("A Chat Message", function () {
</message>`)
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg audio').length, 1000);
let msg = view.el.querySelector('.chat-msg .chat-msg__text');
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg audio').length, 1000);
let msg = view.querySelector('.chat-msg .chat-msg__text');
expect(msg.classList.length).toEqual(1);
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
expect(msg.textContent).toEqual('Have you heard this funny audio?');
let media = view.el.querySelector('.chat-msg .chat-msg__media');
let media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/<!---->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
`<audio controls="" src="https://montague.lit/audio.mp3"></audio> `+
`<a target="_blank" rel="noopener" href="https://montague.lit/audio.mp3">Download audio file "audio.mp3"</a>`);
@ -1464,9 +1464,9 @@ describe("A Chat Message", function () {
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text');
msg = view.querySelector('.chat-msg:last-child .chat-msg__text');
expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual('Have you heard this funny audio?'); // Emtpy
media = view.el.querySelector('.chat-msg:last-child .chat-msg__media');
media = view.querySelector('.chat-msg:last-child .chat-msg__media');
expect(media.innerHTML.replace(/<!---->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
`<audio controls="" src="https://montague.lit/audio.mp3"></audio> `+
`<a target="_blank" rel="noopener" href="https://montague.lit/audio.mp3">`+
@ -1493,11 +1493,11 @@ describe("A Chat Message", function () {
<x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg video').length, 2000)
let msg = view.el.querySelector('.chat-msg .chat-msg__text');
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg video').length, 2000)
let msg = view.querySelector('.chat-msg .chat-msg__text');
expect(msg.classList.length).toBe(1);
expect(msg.textContent).toEqual('Have you seen this funny video?');
let media = view.el.querySelector('.chat-msg .chat-msg__media');
let media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
`<!----><video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video><!---->`);
@ -1512,9 +1512,9 @@ describe("A Chat Message", function () {
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text');
msg = view.querySelector('.chat-msg:last-child .chat-msg__text');
expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual('Have you seen this funny video?');
media = view.el.querySelector('.chat-msg:last-child .chat-msg__media');
media = view.querySelector('.chat-msg:last-child .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
`<!----><video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video><!---->`);
done();
@ -1539,11 +1539,11 @@ describe("A Chat Message", function () {
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg a').length, 1000);
const msg = view.el.querySelector('.chat-msg .chat-msg__text');
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg a').length, 1000);
const msg = view.querySelector('.chat-msg .chat-msg__text');
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
expect(msg.textContent).toEqual('Have you downloaded this funny file?');
const media = view.el.querySelector('.chat-msg .chat-msg__media');
const media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
`<!----><a target="_blank" rel="noopener" href="https://montague.lit/funny.pdf"><!---->Download file "funny.pdf"<!----></a><!---->`);
done();
@ -1572,11 +1572,11 @@ describe("A Chat Message", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg a').length, 1000);
const msg = view.el.querySelector('.chat-msg .chat-msg__text');
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg a').length, 1000);
const msg = view.querySelector('.chat-msg .chat-msg__text');
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
expect(msg.textContent).toEqual('Have you seen this funny image?');
const media = view.el.querySelector('.chat-msg .chat-msg__media');
const media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/<!---->/g, '').replace(/(\r\n|\n|\r)/gm, "")).toEqual(
`<a target="_blank" rel="noopener" href="${base_url}/logo/conversejs-filled.svg">`+
`Download image file "conversejs-filled.svg"</a>`);

View File

@ -21,12 +21,12 @@ describe("A chat message", function () {
await mock.openControlBox(_converse);
spyOn(_converse.api, "trigger").and.callThrough();
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, contact_jid);
const chatview = _converse.api.chatviews.get(contact_jid);
expect(u.isVisible(chatview.el)).toBeTruthy();
expect(u.isVisible(chatview)).toBeTruthy();
expect(chatview.model.get('minimized')).toBeFalsy();
chatview.el.querySelector('.toggle-chatbox-button').click();
chatview.querySelector('.toggle-chatbox-button').click();
expect(chatview.model.get('minimized')).toBeTruthy();
var message = 'This message is sent to a minimized chatbox';
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -44,7 +44,7 @@ describe("A chat message", function () {
expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
const trimmed_chatboxes = _converse.minimized_chats;
let count = trimmed_chatboxes.el.querySelector('converse-minimized-chat .message-count');
expect(u.isVisible(chatview.el)).toBeFalsy();
expect(u.isVisible(chatview)).toBeFalsy();
expect(chatview.model.get('minimized')).toBeTruthy();
expect(u.isVisible(count)).toBeTruthy();
@ -60,7 +60,7 @@ describe("A chat message", function () {
);
await u.waitUntil(() => (chatview.model.messages.length > 1));
expect(u.isVisible(chatview.el)).toBeFalsy();
expect(u.isVisible(chatview)).toBeFalsy();
expect(chatview.model.get('minimized')).toBeTruthy();
count = trimmed_chatboxes.el.querySelector('converse-minimized-chat .message-count');
expect(u.isVisible(count)).toBeTruthy();
@ -85,12 +85,12 @@ describe("A Groupcaht", function () {
spyOn(view, 'onMaximized').and.callThrough();
spyOn(_converse.api, "trigger").and.callThrough();
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
const button = await u.waitUntil(() => view.el.querySelector('.toggle-chatbox-button'));
const button = await u.waitUntil(() => view.querySelector('.toggle-chatbox-button'));
button.click();
expect(view.onMinimized).toHaveBeenCalled();
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
expect(u.isVisible(view.el)).toBeFalsy();
expect(u.isVisible(view)).toBeFalsy();
expect(view.model.get('minimized')).toBeTruthy();
expect(view.onMinimized).toHaveBeenCalled();
const el = await u.waitUntil(() => _converse.minimized_chats.el.querySelector("a.restore-chat"));
@ -115,21 +115,21 @@ describe("A Chatbox", function () {
await mock.openControlBox(_converse);
const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, contact_jid);
const chatview = _converse.chatboxviews.get(contact_jid);
spyOn(chatview, 'minimize').and.callThrough();
spyOn(_converse.api, "trigger").and.callThrough();
// We need to rebind all events otherwise our spy won't be called
chatview.delegateEvents();
chatview.el.querySelector('.toggle-chatbox-button').click();
chatview.querySelector('.toggle-chatbox-button').click();
expect(chatview.minimize).toHaveBeenCalled();
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
expect(_converse.api.trigger.calls.count(), 2);
expect(u.isVisible(chatview.el)).toBeFalsy();
expect(u.isVisible(chatview)).toBeFalsy();
expect(chatview.model.get('minimized')).toBeTruthy();
chatview.el.querySelector('.toggle-chatbox-button').click();
chatview.querySelector('.toggle-chatbox-button').click();
await u.waitUntil(() => _converse.chatboxviews.keys().length);
_converse.minimized_chats.el.querySelector("a.restore-chat").click();
@ -150,7 +150,7 @@ describe("A Chatbox", function () {
await _converse.api.chats.create(sender_jid, {'minimized': true});
await u.waitUntil(() => _converse.chatboxes.length > 1);
const chatBoxView = _converse.chatboxviews.get(sender_jid);
expect(u.isVisible(chatBoxView.el)).toBeFalsy();
expect(u.isVisible(chatBoxview)).toBeFalsy();
expect(u.isVisible(_converse.minimized_chats.el.firstElementChild)).toBe(true);
expect(_converse.minimized_chats.el.firstElementChild.querySelectorAll('converse-minimized-chat').length).toBe(1);
expect(_converse.chatboxes.filter('minimized').length).toBe(1);
@ -175,9 +175,9 @@ describe("A Chatbox", function () {
expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open
_converse.rosterview.update(); // XXX: Hack to make sure $roster element is attached.
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group li').length);
// Test that they can be maximized again
const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
const online_contacts = _converse.rosterview.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
expect(online_contacts.length).toBe(17);
let i;
for (i=0; i<online_contacts.length; i++) {
@ -265,9 +265,9 @@ describe("A Minimized ChatBoxView's Unread Message Count", function () {
const view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
mock.sendMessage(view, message);
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg').length, 1000);
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg').length, 1000);
expect(view.model.sendMessage).toHaveBeenCalled();
const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view).pop();
await u.waitUntil(() => msg.innerHTML.replace(/\<!----\>/g, '') ===
'<a target="_blank" rel="noopener" href="https://www.openstreetmap.org/?mlat=37.786971&amp;'+
'mlon=-122.399677#map=18/37.786971/-122.399677">https://www.openstreetmap.org/?mlat=37.786971&amp;mlon=-122.399677#map=18/37.786971/-122.399677</a>');
@ -292,7 +292,7 @@ describe("The Minimized Chats Widget", function () {
let chatview = _converse.chatboxviews.get(contact_jid);
expect(chatview.model.get('minimized')).toBeFalsy();
expect(u.isVisible(_converse.minimized_chats.el.firstElementChild)).toBe(false);
chatview.el.querySelector('.toggle-chatbox-button').click();
chatview.querySelector('.toggle-chatbox-button').click();
expect(chatview.model.get('minimized')).toBeTruthy();
expect(u.isVisible(_converse.minimized_chats.el)).toBe(true);
expect(_converse.chatboxes.filter('minimized').length).toBe(1);
@ -302,7 +302,7 @@ describe("The Minimized Chats Widget", function () {
await mock.openChatBoxFor(_converse, contact_jid);
chatview = _converse.chatboxviews.get(contact_jid);
expect(chatview.model.get('minimized')).toBeFalsy();
chatview.el.querySelector('.toggle-chatbox-button').click();
chatview.querySelector('.toggle-chatbox-button').click();
expect(chatview.model.get('minimized')).toBeTruthy();
expect(u.isVisible(_converse.minimized_chats.el)).toBe(true);
expect(_converse.chatboxes.filter('minimized').length).toBe(2);

View File

@ -442,9 +442,9 @@ window.addEventListener('converse-loaded', () => {
mock.sendMessage = function (view, message) {
const promise = new Promise(resolve => view.model.messages.once('rendered', resolve));
view.el.querySelector('.chat-textarea').value = message;
view.querySelector('.chat-textarea').value = message;
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
target: view.querySelector('textarea.chat-textarea'),
preventDefault: () => {},
keyCode: 13
});
@ -641,7 +641,7 @@ window.addEventListener('converse-loaded', () => {
'view_mode': mock.view_mode
}, settings || {}));
_converse.ChatBoxViews.prototype.trimChat = function () {};
_converse.minimize.trimChat = function () {};
_converse.api.vcard.get = function (model, force) {
let jid;

View File

@ -8,7 +8,7 @@ const u = converse.env.utils;
async function openModtools (_converse, view) {
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
@ -263,7 +263,7 @@ describe("The groupchat moderator tool", function () {
));
await u.waitUntil(() => (view.model.occupants.length === 7), 1000);
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
@ -474,7 +474,7 @@ describe("The groupchat moderator tool", function () {
const members = [{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'}];
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid);
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);

View File

@ -32,7 +32,7 @@ describe("MUC Mention Notfications", function () {
await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED);
const lview = _converse.rooms_list_view
const room_el = await u.waitUntil(() => lview.el.querySelector(".available-chatroom"));
const room_el = await u.waitUntil(() => lview.querySelector(".available-chatroom"));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy();
const base_time = new Date();

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ describe("A Groupchat Message", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = 'hello world'
const enter_event = {
'target': textarea,
@ -42,7 +42,7 @@ describe("A Groupchat Message", function () {
</message>
`);
_converse.connection._dataRecv(mock.createRequest(error));
expect(await u.waitUntil(() => view.el.querySelector('.chat-msg__error')?.textContent?.trim())).toBe(err_msg_text);
expect(await u.waitUntil(() => view.querySelector('.chat-msg__error')?.textContent?.trim())).toBe(err_msg_text);
expect(view.model.messages.length).toBe(1);
const message = view.model.messages.at(0);
expect(message.get('received')).toBeUndefined();
@ -74,7 +74,7 @@ describe("A Groupchat Message", function () {
</presence>
`);
_converse.connection._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 1);
presence = u.toStanza(`
<presence xmlns="jabber:client" to="${_converse.jid}" from="${muc_jid}/romeo1">
@ -86,9 +86,9 @@ describe("A Groupchat Message", function () {
</presence>
`);
_converse.connection._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 2);
const messages = view.el.querySelectorAll('.chat-info');
const messages = view.querySelectorAll('.chat-info');
expect(u.hasClass('chat-msg--followup', messages[0])).toBe(false);
expect(u.hasClass('chat-msg--followup', messages[1])).toBe(false);
done();
@ -119,11 +119,11 @@ describe("A Groupchat Message", function () {
spyOn(view.model, 'createInfoMessages').and.callThrough();
_converse.connection._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.model.createInfoMessages.calls.count());
await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 1);
_converse.connection._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.model.createInfoMessages.calls.count() === 2);
expect(view.el.querySelectorAll('.chat-info').length).toBe(1);
expect(view.querySelectorAll('.chat-info').length).toBe(1);
done();
}));
});
@ -159,7 +159,7 @@ describe("A Groupchat Message", function () {
expect(converse.env.log.error).toHaveBeenCalledWith(
`Ignoring unencapsulated forwarded message from ${muc_jid}/mallory`
);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(0);
expect(view.querySelectorAll('.chat-msg').length).toBe(0);
expect(view.model.messages.length).toBe(0);
done();
}));
@ -172,7 +172,7 @@ describe("A Groupchat Message", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
if (!view.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
const message = 'romeo: Your attention is required';
const nick = mock.chatroom_names[0],
msg = $msg({
@ -185,7 +185,7 @@ describe("A Groupchat Message", function () {
.tree();
await view.model.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelector('.chat-msg')).not.toBe(null);
expect(view.querySelector('.chat-msg')).not.toBe(null);
done();
}));
@ -197,7 +197,7 @@ describe("A Groupchat Message", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
if (!view.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
const id = u.getUniqueId();
let msg = $msg({
from: 'lounge@montague.lit/some1',
@ -206,7 +206,7 @@ describe("A Groupchat Message", function () {
type: 'groupchat'
}).c('body').t('First message').tree();
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
msg = $msg({
from: 'lounge@montague.lit/some2',
@ -215,7 +215,7 @@ describe("A Groupchat Message", function () {
type: 'groupchat'
}).c('body').t('Another message').tree();
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
expect(view.model.messages.length).toBe(2);
done();
}));
@ -371,7 +371,7 @@ describe("A Groupchat Message", function () {
expect(converse.env.log.error).toHaveBeenCalledWith(
'Invalid Stanza: MUC messages SHOULD NOT be XEP-0280 carbon copied'
);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(0);
expect(view.querySelectorAll('.chat-msg').length).toBe(0);
expect(view.model.messages.length).toBe(0);
done();
}));
@ -391,10 +391,10 @@ describe("A Groupchat Message", function () {
type: 'groupchat'
}).c('body').t('I wrote this message!').tree();
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length);
expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner');
expect(view.model.messages.last().occupant.get('role')).toBe('moderator');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat chat-msg--with-avatar moderator owner');
let presence = $pres({
to:'romeo@montague.lit/orchard',
@ -420,7 +420,7 @@ describe("A Groupchat Message", function () {
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.model.messages.last().occupant.get('affiliation')).toBe('member');
expect(view.model.messages.last().occupant.get('role')).toBe('participant');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
expect(view.querySelectorAll('.chat-msg').length).toBe(2);
expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat chat-msg--with-avatar participant member');
presence = $pres({
@ -438,12 +438,12 @@ describe("A Groupchat Message", function () {
_converse.connection._dataRecv(mock.createRequest(presence));
view.model.sendMessage('hello world');
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 3);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 3);
const occupant = await u.waitUntil(() => view.model.messages.filter(m => m.get('type') === 'groupchat')[2].occupant);
expect(occupant.get('affiliation')).toBe('owner');
expect(occupant.get('role')).toBe('moderator');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
expect(view.querySelectorAll('.chat-msg').length).toBe(3);
await u.waitUntil(() => sizzle('.chat-msg', view.el).pop().classList.value.trim() === 'message chat-msg groupchat chat-msg--with-avatar moderator owner');
const add_events = view.model.occupants._events.add.length;
@ -533,7 +533,7 @@ describe("A Groupchat Message", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = 'But soft, what light through yonder airlock breaks?';
view.onKeyDown({
target: textarea,
@ -541,7 +541,7 @@ describe("A Groupchat Message", function () {
keyCode: 13 // Enter
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(0);
expect(view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(0);
const msg_obj = view.model.messages.at(0);
const stanza = u.toStanza(`
@ -556,9 +556,9 @@ describe("A Groupchat Message", function () {
<origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
</message>`);
await view.model.handleMessageStanza(stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
expect(view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
expect(view.querySelectorAll('.chat-msg__receipt').length).toBe(0);
expect(view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(1);
expect(view.model.messages.length).toBe(1);
const message = view.model.messages.at(0);
@ -611,7 +611,7 @@ describe("A Groupchat Message", function () {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = 'But soft, what light through yonder airlock breaks?';
view.onKeyDown({
target: textarea,
@ -619,7 +619,7 @@ describe("A Groupchat Message", function () {
keyCode: 13 // Enter
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
const msg_obj = view.model.messages.at(0);
let stanza = u.toStanza(`
@ -643,7 +643,7 @@ describe("A Groupchat Message", function () {
<origin-id xmlns="urn:xmpp:sid:0" id="CE08D448-5ED8-4B6A-BB5B-07ED9DFE4FF0"/>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
done();
}));
});

View File

@ -17,19 +17,19 @@ describe("A list of open groupchats", function () {
await mock.openChatRoom(_converse, 'room', 'conference.shakespeare.lit', 'JC');
const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".open-room");
await u.waitUntil(() => lview.querySelectorAll(".open-room").length);
let room_els = lview.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit');
await mock.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
await u.waitUntil(() => lview.querySelectorAll(".open-room").length > 1);
room_els = _converse.rooms_list_view.querySelectorAll(".open-room");
expect(room_els.length).toBe(2);
let view = _converse.chatboxviews.get('room@conference.shakespeare.lit');
await view.close();
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
room_els = _converse.rooms_list_view.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
expect(room_els[0].innerText).toBe('lounge@montague.lit');
list = controlbox.el.querySelector('.list-container--openrooms');
@ -37,7 +37,7 @@ describe("A list of open groupchats", function () {
view = _converse.chatboxviews.get('lounge@montague.lit');
await view.close();
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
room_els = _converse.rooms_list_view.querySelectorAll(".open-room");
expect(room_els.length).toBe(0);
list = controlbox.el.querySelector('.list-container--openrooms');
@ -119,8 +119,8 @@ describe("A groupchat shown in the groupchats list", function () {
const muc_jid = 'coven@chat.shakespeare.lit';
await _converse.api.rooms.open(muc_jid, {'nick': 'some1'}, true);
const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".available-chatroom");
await u.waitUntil(() => lview.querySelectorAll(".open-room").length);
let room_els = lview.querySelectorAll(".available-chatroom");
expect(room_els.length).toBe(1);
let item = room_els[0];
@ -128,11 +128,11 @@ describe("A groupchat shown in the groupchats list", function () {
await u.waitUntil(() => u.hasClass('open', item), 1000);
expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit');
await _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'}, true);
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
room_els = lview.el.querySelectorAll(".open-room");
await u.waitUntil(() => lview.querySelectorAll(".open-room").length > 1);
room_els = lview.querySelectorAll(".open-room");
expect(room_els.length).toBe(2);
room_els = lview.el.querySelectorAll(".available-chatroom.open");
room_els = lview.querySelectorAll(".available-chatroom.open");
expect(room_els.length).toBe(1);
item = room_els[0];
expect(item.textContent.trim()).toBe('balcony@chat.shakespeare.lit');
@ -198,10 +198,11 @@ describe("A groupchat shown in the groupchats list", function () {
.c('status').attrs({code:'110'});
_converse.connection._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".open-room").length, 500);
const room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
const rooms_list = document.querySelector('converse-rooms-list');
await u.waitUntil(() => rooms_list.querySelectorAll(".open-room").length, 500);
const room_els = rooms_list.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
const info_el = _converse.rooms_list_view.el.querySelector(".room-info");
const info_el = rooms_list.querySelector(".room-info");
info_el.click();
const modal = _converse.api.modal.get('muc-details-modal');
@ -259,16 +260,17 @@ describe("A groupchat shown in the groupchats list", function () {
await mock.openChatRoom(_converse, 'lounge', 'conference.shakespeare.lit', 'JC');
expect(_converse.chatboxes.length).toBe(2);
const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".open-room");
await u.waitUntil(() => lview.querySelectorAll(".open-room").length);
let room_els = lview.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
const close_el = _converse.rooms_list_view.el.querySelector(".close-room");
const rooms_list = document.querySelector('converse-rooms-list');
const close_el = rooms_list.querySelector(".close-room");
close_el.click();
expect(window.confirm).toHaveBeenCalledWith(
'Are you sure you want to leave the groupchat lounge@conference.shakespeare.lit?');
await new Promise(resolve => _converse.api.listen.once('chatBoxClosed', resolve));
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
room_els = rooms_list.querySelectorAll(".open-room");
expect(room_els.length).toBe(0);
expect(_converse.chatboxes.length).toBe(1);
done();
@ -284,7 +286,8 @@ describe("A groupchat shown in the groupchats list", function () {
const u = converse.env.utils;
await mock.openControlBox(_converse);
const room_jid = 'kitchen@conference.shakespeare.lit';
await u.waitUntil(() => _converse.rooms_list_view !== undefined, 500);
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});
@ -299,7 +302,7 @@ describe("A groupchat shown in the groupchats list", function () {
// If the user isn't mentioned, the counter doesn't get incremented, but the text of the groupchat is bold
const lview = _converse.rooms_list_view
let room_el = await u.waitUntil(() => lview.el.querySelector(".available-chatroom"));
let room_el = await u.waitUntil(() => lview.querySelector(".available-chatroom"));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
// If the user is mentioned, the counter also gets updated
@ -312,7 +315,7 @@ describe("A groupchat shown in the groupchats list", function () {
}).c('body').t('romeo: Your attention is required').tree()
);
let indicator_el = await u.waitUntil(() => lview.el.querySelector(".msgs-indicator"));
let indicator_el = await u.waitUntil(() => lview.querySelector(".msgs-indicator"));
expect(indicator_el.textContent).toBe('1');
spyOn(view.model, 'handleUnreadMessage').and.callThrough();
@ -325,13 +328,13 @@ describe("A groupchat shown in the groupchats list", function () {
}).c('body').t('romeo: and another thing...').tree()
);
await u.waitUntil(() => view.model.handleUnreadMessage.calls.count());
await u.waitUntil(() => lview.el.querySelector(".msgs-indicator").textContent === '2', 1000);
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});
indicator_el = lview.el.querySelector(".msgs-indicator");
indicator_el = lview.querySelector(".msgs-indicator");
expect(indicator_el === null);
room_el = lview.el.querySelector(".available-chatroom");
room_el = lview.querySelector(".available-chatroom");
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy();
done();
}));

View File

@ -39,7 +39,7 @@ describe("Notifications", function () {
await mock.waitForRoster(_converse, 'current');
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.api.chatviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) {
if (!view.querySelectorAll('.chat-area').length) {
view.renderChatArea();
}
@ -163,7 +163,7 @@ describe("Notifications", function () {
spyOn(window, 'Audio').and.returnValue(stub);
const view = _converse.chatboxviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) {
if (!view.querySelectorAll('.chat-area').length) {
view.renderChatArea();
}
let text = 'This message will play a sound because it mentions romeo';
@ -327,7 +327,7 @@ describe("Notifications", function () {
// come back to converse-chat page
_converse.saveWindowState({'type': 'focus'});
await u.waitUntil(() => u.isVisible(view.el));
await u.waitUntil(() => u.isVisible(view));
await u.waitUntil(() => favico.badge.calls.count() === 2);
expect(favico.badge.calls.mostRecent().args.pop()).toBe(0);
@ -338,7 +338,7 @@ describe("Notifications", function () {
// check that msg_counter is incremented from zero again
await _converse.handleMessageStanza(msgFactory());
view = _converse.chatboxviews.get(sender_jid);
await u.waitUntil(() => u.isVisible(view.el));
await u.waitUntil(() => u.isVisible(view));
await u.waitUntil(() => favico.badge.calls.count() === 3);
expect(favico.badge.calls.mostRecent().args.pop()).toBe(1);
done();

View File

@ -114,7 +114,7 @@ describe("The OMEMO module", function() {
const view = _converse.chatboxviews.get(contact_jid);
view.model.set('omemo_active', true);
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
textarea.value = 'This message will be encrypted';
view.onKeyDown({
target: textarea,
@ -198,7 +198,7 @@ describe("The OMEMO module", function() {
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.model.messages.length).toBe(2);
expect(view.el.querySelectorAll('.chat-msg__body')[1].textContent.trim())
expect(view.querySelectorAll('.chat-msg__body')[1].textContent.trim())
.toBe('This is an encrypted message from the contact');
// #1193 Check for a received message without <body> tag
@ -218,7 +218,7 @@ describe("The OMEMO module", function() {
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.model.messages.length > 1);
expect(view.model.messages.length).toBe(3);
expect(view.el.querySelectorAll('.chat-msg__body')[2].textContent.trim())
expect(view.querySelectorAll('.chat-msg__body')[2].textContent.trim())
.toBe('Another received encrypted message without fallback');
done();
}));
@ -244,7 +244,7 @@ describe("The OMEMO module", function() {
const view = _converse.chatboxviews.get('lounge@montague.lit');
await u.waitUntil(() => initializedOMEMO(_converse));
const toolbar = view.el.querySelector('.chat-toolbar');
const toolbar = view.querySelector('.chat-toolbar');
const el = await u.waitUntil(() => toolbar.querySelector('.toggle-omemo'));
el.click();
expect(view.model.get('omemo_active')).toBe(true);
@ -297,7 +297,7 @@ describe("The OMEMO module", function() {
expect(u.hasClass('fa-unlock', icon)).toBe(false);
expect(u.hasClass('fa-lock', icon)).toBe(true);
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
textarea.value = 'This message will be encrypted';
view.onKeyDown({
target: textarea,
@ -451,7 +451,7 @@ describe("The OMEMO module", function() {
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.model.messages.length).toBe(1);
expect(view.el.querySelector('.chat-msg__text').textContent.trim())
expect(view.querySelector('.chat-msg__text').textContent.trim())
.toBe('This is an encrypted carbon message from another device of mine');
expect(contact_devicelist.devices.length).toBe(1);
@ -463,7 +463,7 @@ describe("The OMEMO module", function() {
expect(my_devicelist.devices.at(2).get('id')).toBe('988349631');
expect(my_devicelist.devices.get('988349631').get('active')).toBe(true);
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
textarea.value = 'This is an encrypted message from this device';
view.onKeyDown({
target: textarea,
@ -514,13 +514,13 @@ describe("The OMEMO module", function() {
}).tree();
_converse.connection._dataRecv(mock.createRequest(stanza));
const toolbar = view.el.querySelector('.chat-toolbar');
const toolbar = view.querySelector('.chat-toolbar');
const toggle = await u.waitUntil(() => toolbar.querySelector('.toggle-omemo'));
toggle.click();
expect(view.model.get('omemo_active')).toBe(true);
expect(view.model.get('omemo_supported')).toBe(true);
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
textarea.value = 'This message will be encrypted';
view.onKeyDown({
target: textarea,
@ -605,7 +605,7 @@ describe("The OMEMO module", function() {
"to be subscribed to their presence in order to see their OMEMO information");
expect(view.model.get('omemo_supported')).toBe(false);
expect(view.el.querySelector('.chat-textarea').value).toBe('This message will be encrypted');
expect(view.querySelector('.chat-textarea').value).toBe('This message will be encrypted');
done();
}));
@ -1232,7 +1232,7 @@ describe("The OMEMO module", function() {
expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
await u.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar'));
const view = _converse.chatboxviews.get(contact_jid);
const toolbar = view.el.querySelector('.chat-toolbar');
const toolbar = view.querySelector('.chat-toolbar');
expect(view.model.get('omemo_active')).toBe(undefined);
const toggle = toolbar.querySelector('.toggle-omemo');
expect(toggle === null).toBe(false);
@ -1248,7 +1248,7 @@ describe("The OMEMO module", function() {
expect(u.hasClass('fa-unlock', icon)).toBe(false);
expect(u.hasClass('fa-lock', icon)).toBe(true);
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
textarea.value = 'This message will be sent encrypted';
view.onKeyDown({
target: textarea,
@ -1296,7 +1296,7 @@ describe("The OMEMO module", function() {
const view = _converse.chatboxviews.get('lounge@montague.lit');
await u.waitUntil(() => initializedOMEMO(_converse));
const toolbar = view.el.querySelector('.chat-toolbar');
const toolbar = view.querySelector('.chat-toolbar');
let toggle = await u.waitUntil(() => toolbar.querySelector('.toggle-omemo'));
expect(view.model.get('omemo_active')).toBe(undefined);
expect(view.model.get('omemo_supported')).toBe(true);
@ -1370,31 +1370,31 @@ describe("The OMEMO module", function() {
// anonymous or semi-anonymous
view.model.features.save({'nonanonymous': false, 'semianonymous': true});
await u.waitUntil(() => !view.model.get('omemo_supported'));
await u.waitUntil(() => view.el.querySelector('.toggle-omemo').disabled);
await u.waitUntil(() => view.querySelector('.toggle-omemo').disabled);
view.model.features.save({'nonanonymous': true, 'semianonymous': false});
await u.waitUntil(() => view.model.get('omemo_supported'));
await u.waitUntil(() => view.el.querySelector('.toggle-omemo') !== null);
await u.waitUntil(() => view.querySelector('.toggle-omemo') !== null);
expect(u.hasClass('fa-unlock', toolbar.querySelector('.toggle-omemo converse-icon'))).toBe(true);
expect(u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo converse-icon'))).toBe(false);
expect(!!view.el.querySelector('.toggle-omemo').disabled).toBe(false);
expect(!!view.querySelector('.toggle-omemo').disabled).toBe(false);
// Test that the button gets disabled when the room becomes open
view.model.features.save({'membersonly': false, 'open': true});
await u.waitUntil(() => !view.model.get('omemo_supported'));
await u.waitUntil(() => view.el.querySelector('.toggle-omemo').disabled);
await u.waitUntil(() => view.querySelector('.toggle-omemo').disabled);
view.model.features.save({'membersonly': true, 'open': false});
await u.waitUntil(() => view.model.get('omemo_supported'));
await u.waitUntil(() => !view.el.querySelector('.toggle-omemo').disabled);
await u.waitUntil(() => !view.querySelector('.toggle-omemo').disabled);
expect(u.hasClass('fa-unlock', view.el.querySelector('.toggle-omemo converse-icon'))).toBe(true);
expect(u.hasClass('fa-lock', view.el.querySelector('.toggle-omemo converse-icon'))).toBe(false);
expect(u.hasClass('fa-unlock', view.querySelector('.toggle-omemo converse-icon'))).toBe(true);
expect(u.hasClass('fa-lock', view.querySelector('.toggle-omemo converse-icon'))).toBe(false);
expect(view.model.get('omemo_supported')).toBe(true);
expect(view.model.get('omemo_active')).toBe(false);
view.el.querySelector('.toggle-omemo').click();
view.querySelector('.toggle-omemo').click();
expect(view.model.get('omemo_active')).toBe(true);
// Someone enters the room who doesn't have OMEMO support, while we
@ -1429,13 +1429,13 @@ describe("The OMEMO module", function() {
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => !view.model.get('omemo_supported'));
await u.waitUntil(() => view.el.querySelector('.chat-error .chat-info__message')?.textContent.trim() ===
await u.waitUntil(() => view.querySelector('.chat-error .chat-info__message')?.textContent.trim() ===
"oldguy doesn't appear to have a client that supports OMEMO. "+
"Encrypted chat will no longer be possible in this grouchat."
);
await u.waitUntil(() => toolbar.querySelector('.toggle-omemo').disabled);
icon = view.el.querySelector('.toggle-omemo converse-icon');
icon = view.querySelector('.toggle-omemo converse-icon');
expect(u.hasClass('fa-unlock', icon)).toBe(true);
expect(u.hasClass('fa-lock', icon)).toBe(false);
expect(toolbar.querySelector('.toggle-omemo').title).toBe('This groupchat needs to be members-only and non-anonymous in order to support OMEMO encrypted messages');
@ -1461,7 +1461,7 @@ describe("The OMEMO module", function() {
_converse.api.trigger('OMEMOInitialized');
const view = _converse.chatboxviews.get(contact_jid);
const show_modal_button = view.el.querySelector('.show-user-details-modal');
const show_modal_button = view.querySelector('.show-user-details-modal');
show_modal_button.click();
const modal = _converse.api.modal.get('user-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000);

View File

@ -75,7 +75,7 @@ describe("A sent presence stanza", function () {
spyOn(_converse.connection, 'send').and.callThrough();
const cbview = _converse.chatboxviews.get('controlbox');
const change_status_el = await u.waitUntil(() => cbview.el.querySelector('.change-status'));
const change_status_el = await u.waitUntil(() => cbview.querySelector('.change-status'));
change_status_el.click()
let modal = _converse.api.modal.get('modal-status-change');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
@ -94,7 +94,7 @@ describe("A sent presence stanza", function () {
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "true");
await u.waitUntil(() => !u.isVisible(modal.el));
cbview.el.querySelector('.change-status').click()
cbview.querySelector('.change-status').click()
modal = _converse.api.modal.get('modal-status-change');
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "false", 1000);
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"

View File

@ -65,7 +65,7 @@ describe("The Protocol", function () {
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
cbview.el.querySelector('.add-contact').click()
cbview.querySelector('.add-contact').click()
const modal = _converse.api.modal.get('add-contact-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
spyOn(modal, "addContactFromForm").and.callThrough();

View File

@ -97,7 +97,7 @@ describe("XEP-0437 Room Activity Indicators", function () {
expect(view.model.get('has_activity')).toBe(false);
const lview = _converse.rooms_list_view
const room_el = await u.waitUntil(() => lview.el.querySelector(".available-chatroom"));
const room_el = await u.waitUntil(() => lview.querySelector(".available-chatroom"));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy();
const activity_stanza = u.toStanza(`
@ -161,7 +161,7 @@ describe("XEP-0437 Room Activity Indicators", function () {
expect(view.model.get('has_activity')).toBe(false);
const lview = _converse.rooms_list_view
const room_el = await u.waitUntil(() => lview.el.querySelector(".available-chatroom"));
const room_el = await u.waitUntil(() => lview.querySelector(".available-chatroom"));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy();
const activity_stanza = u.toStanza(`

View File

@ -108,7 +108,7 @@ describe("A delivery receipt", function () {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
const textarea = view.el.querySelector('textarea.chat-textarea');
const textarea = view.querySelector('textarea.chat-textarea');
textarea.value = 'But soft, what light through yonder airlock breaks?';
view.onKeyDown({
target: textarea,
@ -126,7 +126,7 @@ describe("A delivery receipt", function () {
'id': u.getUniqueId(),
}).c('received', {'id': msg_id, xmlns: Strophe.NS.RECEIPTS}).up().tree();
_converse.connection._dataRecv(mock.createRequest(msg));
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__receipt').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg__receipt').length === 1);
// Also handle receipts with type 'chat'. See #1353
spyOn(_converse, 'handleMessageStanza').and.callThrough();
@ -147,7 +147,7 @@ describe("A delivery receipt", function () {
'id': u.getUniqueId(),
}).c('received', {'id': msg_id, xmlns: Strophe.NS.RECEIPTS}).up().tree();
_converse.connection._dataRecv(mock.createRequest(msg));
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__receipt').length === 2);
await u.waitUntil(() => view.querySelectorAll('.chat-msg__receipt').length === 2);
expect(_converse.handleMessageStanza.calls.count()).toBe(1);
done();
}));

View File

@ -16,7 +16,7 @@ describe("The Registration Panel", function () {
await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
const cbview = _converse.api.controlbox.get();
expect(cbview.el.querySelectorAll('a.register-account').length).toBe(0);
expect(cbview.querySelectorAll('a.register-account').length).toBe(0);
done();
}));
@ -36,10 +36,10 @@ describe("The Registration Panel", function () {
}
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'), 300);
const cbview = _converse.chatboxviews.get('controlbox');
const panels = cbview.el.querySelector('.controlbox-panes');
const panels = cbview.querySelector('.controlbox-panes');
const login = panels.firstElementChild;
const registration = panels.childNodes[1];
const register_link = cbview.el.querySelector('a.register-account');
const register_link = cbview.querySelector('a.register-account');
expect(register_link.textContent).toBe("Create an account");
register_link.click();
@ -62,17 +62,17 @@ describe("The Registration Panel", function () {
toggle.click();
const cbview = _converse.api.controlbox.get();
await u.waitUntil(() => u.isVisible(cbview.el));
await u.waitUntil(() => u.isVisible(cbview));
const registerview = cbview.registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'fetchRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
// Open the register panel
cbview.el.querySelector('.toggle-register-login').click();
cbview.querySelector('.toggle-register-login').click();
// Check the form layout
const form = cbview.el.querySelector('#converse-register');
const form = cbview.querySelector('#converse-register');
expect(form.querySelectorAll('input').length).toEqual(2);
expect(form.querySelectorAll('input')[0].getAttribute('name')).toEqual('domain');
expect(sizzle('input:last', form).pop().getAttribute('type')).toEqual('submit');
@ -100,7 +100,7 @@ describe("The Registration Panel", function () {
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
cbview.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'fetchRegistrationForm').and.callThrough();
@ -112,8 +112,8 @@ describe("The Registration Panel", function () {
expect(registerview._registering).toBeFalsy();
expect(_converse.api.connection.connected()).toBeFalsy();
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
registerview.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.querySelector('input[type=submit]').click();
expect(registerview.onProviderChosen).toHaveBeenCalled();
expect(registerview._registering).toBeTruthy();
await u.waitUntil(() => registerview.fetchRegistrationForm.calls.count());
@ -140,9 +140,9 @@ describe("The Registration Panel", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(registerview.onRegistrationFields).toHaveBeenCalled();
expect(registerview.renderRegistrationForm).toHaveBeenCalled();
expect(registerview.el.querySelectorAll('input').length).toBe(5);
expect(registerview.el.querySelectorAll('input[type=submit]').length).toBe(1);
expect(registerview.el.querySelectorAll('input[type=button]').length).toBe(1);
expect(registerview.querySelectorAll('input').length).toBe(5);
expect(registerview.querySelectorAll('input[type=submit]').length).toBe(1);
expect(registerview.querySelectorAll('input[type=button]').length).toBe(1);
done();
}));
@ -163,7 +163,7 @@ describe("The Registration Panel", function () {
toggle.click();
}
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
cbview.querySelector('.toggle-register-login').click();
const registerview = cbview.registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
@ -172,8 +172,8 @@ describe("The Registration Panel", function () {
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
registerview.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.querySelector('input[type=submit]').click();
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
@ -194,12 +194,12 @@ describe("The Registration Panel", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(registerview.form_type).toBe('legacy');
registerview.el.querySelector('input[name=username]').value = 'testusername';
registerview.el.querySelector('input[name=password]').value = 'testpassword';
registerview.el.querySelector('input[name=email]').value = 'test@email.local';
registerview.querySelector('input[name=username]').value = 'testusername';
registerview.querySelector('input[name=password]').value = 'testpassword';
registerview.querySelector('input[name=email]').value = 'test@email.local';
spyOn(_converse.connection, 'send');
registerview.el.querySelector('input[type=submit]').click();
registerview.querySelector('input[type=submit]').click();
expect(_converse.connection.send).toHaveBeenCalled();
stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
@ -227,7 +227,7 @@ describe("The Registration Panel", function () {
toggle.click();
}
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
cbview.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'getRegistrationFields').and.callThrough();
@ -235,8 +235,8 @@ describe("The Registration Panel", function () {
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
registerview.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.querySelector('input[type=submit]').click();
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
@ -259,13 +259,13 @@ describe("The Registration Panel", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(registerview.form_type).toBe('xform');
registerview.el.querySelector('input[name=username]').value = 'testusername';
registerview.el.querySelector('input[name=password]').value = 'testpassword';
registerview.el.querySelector('input[name=email]').value = 'test@email.local';
registerview.querySelector('input[name=username]').value = 'testusername';
registerview.querySelector('input[name=password]').value = 'testpassword';
registerview.querySelector('input[name=email]').value = 'test@email.local';
spyOn(_converse.connection, 'send');
registerview.el.querySelector('input[type=submit]').click();
registerview.querySelector('input[type=submit]').click();
expect(_converse.connection.send).toHaveBeenCalled();
stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
@ -308,7 +308,7 @@ describe("The Registration Panel", function () {
toggle.click();
}
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.toggle-register-login').click();
cbview.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'getRegistrationFields').and.callThrough();
@ -316,8 +316,8 @@ describe("The Registration Panel", function () {
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
registerview.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.querySelector('input[type=submit]').click();
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
@ -353,7 +353,7 @@ describe("The Registration Panel", function () {
</iq>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(registerview.form_type).toBe('xform');
expect(registerview.el.querySelectorAll('#converse-register input[required]').length).toBe(3);
expect(registerview.querySelectorAll('#converse-register input[required]').length).toBe(3);
// Hide the controlbox so that we can see whether the test
// passed or failed
u.addClass('hidden', _converse.chatboxviews.get('controlbox').el);
@ -379,12 +379,12 @@ describe("The Registration Panel", function () {
toggle.click();
}
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.toggle-register-login').click();
cbview.querySelector('.toggle-register-login').click();
const view = _converse.chatboxviews.get('controlbox').registerpanel;
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
view.el.querySelector('input[name=domain]').value = 'conversejs.org';
view.el.querySelector('input[type=submit]').click();
view.querySelector('input[name=domain]').value = 'conversejs.org';
view.querySelector('input[type=submit]').click();
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
@ -421,13 +421,13 @@ describe("The Registration Panel", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
spyOn(view, 'submitRegistrationForm').and.callThrough();
const username_input = view.el.querySelector('[name="username"]');
const username_input = view.querySelector('[name="username"]');
username_input.value = 'romeo';
const password_input = view.el.querySelector('[name="password"]');
const password_input = view.querySelector('[name="password"]');
password_input.value = 'secret';
const ocr_input = view.el.querySelector('[name="ocr"]');
const ocr_input = view.querySelector('[name="ocr"]');
ocr_input.value = '8m9D88';
view.el.querySelector('[type="submit"]').click();
view.querySelector('[type="submit"]').click();
expect(view.submitRegistrationForm).toHaveBeenCalled();
const response_IQ = u.toStanza(`
@ -439,7 +439,7 @@ describe("The Registration Panel", function () {
</error>
</iq>`);
_converse.connection._dataRecv(mock.createRequest(response_IQ));
expect(view.el.querySelector('.error')?.textContent.trim()).toBe('Too many CAPTCHA requests');
expect(view.querySelector('.error')?.textContent.trim()).toBe('Too many CAPTCHA requests');
delete _converse.connection;
done();
}));

View File

@ -6,7 +6,7 @@ const u = converse.env.utils;
async function sendAndThenRetractMessage (_converse, view) {
view.model.sendMessage('hello world');
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 1);
const msg_obj = view.model.messages.last();
const reflection_stanza = u.toStanza(`
<message xmlns="jabber:client"
@ -20,9 +20,9 @@ async function sendAndThenRetractMessage (_converse, view) {
<origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
</message>`);
await view.model.handleMessageStanza(reflection_stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
await u.waitUntil(() => view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
const retract_button = await u.waitUntil(() => view.el.querySelector('.chat-msg__content .chat-msg__action-retract'));
const retract_button = await u.waitUntil(() => view.querySelector('.chat-msg__content .chat-msg__action-retract'));
retract_button.click();
await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
const submit_button = document.querySelector('#converse-modals .modal button[type="submit"]');
@ -53,7 +53,7 @@ describe("Message Retractions", function () {
`);
const view = _converse.api.chatviews.get(muc_jid);
await view.model.handleMessageStanza(received_stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
expect(view.model.messages.at(0).get('retracted')).toBeFalsy();
expect(view.model.messages.at(0).get('is_ephemeral')).toBeFalsy();
@ -69,7 +69,7 @@ describe("Message Retractions", function () {
_converse.connection._dataRecv(mock.createRequest(retraction_stanza));
await u.waitUntil(() => view.model.handleRetraction.calls.count() === 1);
expect(await view.model.handleRetraction.calls.first().returnValue).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.model.messages.length).toBe(2);
expect(view.model.messages.at(1).get('retracted')).toBeTruthy();
expect(view.model.messages.at(1).get('is_ephemeral')).toBeFalsy();
@ -119,7 +119,7 @@ describe("Message Retractions", function () {
`);
_converse.connection._dataRecv(mock.createRequest(received_stanza));
await u.waitUntil(() => view.model.handleRetraction.calls.count() === 2);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1, 1000);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1, 1000);
expect(view.model.messages.length).toBe(1);
const message = view.model.messages.at(0)
@ -179,8 +179,8 @@ describe("Message Retractions", function () {
_converse.connection._dataRecv(mock.createRequest(received_stanza));
await u.waitUntil(() => view.model.handleModeration.calls.count() === 2);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.model.messages.length).toBe(1);
const message = view.model.messages.at(0)
@ -227,7 +227,7 @@ describe("Message Retractions", function () {
expect(message.get('dangling_retraction')).toBe(true);
expect(message.get('is_ephemeral')).toBe(false);
expect(message.get('retracted')).toBeTruthy();
expect(view.el.querySelectorAll('.chat-msg').length).toBe(0);
expect(view.querySelectorAll('.chat-msg').length).toBe(0);
const stanza = u.toStanza(`
<message xmlns="jabber:client"
@ -279,7 +279,7 @@ describe("Message Retractions", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.model.messages.length === 1);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
stanza = u.toStanza(`
<message xmlns="jabber:client"
@ -295,7 +295,7 @@ describe("Message Retractions", function () {
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.model.messages.length === 2);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
const retraction_stanza = u.toStanza(`
<message id="${u.getUniqueId()}"
@ -309,16 +309,16 @@ describe("Message Retractions", function () {
</message>
`);
_converse.connection._dataRecv(mock.createRequest(retraction_stanza));
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1);
expect(view.model.messages.length).toBe(2);
const message = view.model.messages.at(1);
expect(message.get('retracted')).toBeTruthy();
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const msg_el = view.el.querySelector('.chat-msg--retracted .chat-msg__message');
expect(view.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const msg_el = view.querySelector('.chat-msg--retracted .chat-msg__message');
expect(msg_el.textContent.trim()).toBe('Mercutio has removed this message');
expect(u.hasClass('chat-msg--followup', view.el.querySelector('.chat-msg--retracted'))).toBe(true);
expect(u.hasClass('chat-msg--followup', view.querySelector('.chat-msg--retracted'))).toBe(true);
done();
}));
});
@ -335,7 +335,7 @@ describe("Message Retractions", function () {
const view = await mock.openChatBoxFor(_converse, contact_jid);
view.model.sendMessage('hello world');
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
const message = view.model.messages.at(0);
expect(view.model.messages.length).toBe(1);
@ -343,14 +343,14 @@ describe("Message Retractions", function () {
expect(message.get('editable')).toBeTruthy();
const retract_button = await u.waitUntil(() => view.el.querySelector('.chat-msg__content .chat-msg__action-retract'));
const retract_button = await u.waitUntil(() => view.querySelector('.chat-msg__content .chat-msg__action-retract'));
retract_button.click();
await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
const submit_button = document.querySelector('#converse-modals .modal button[type="submit"]');
submit_button.click();
const sent_stanzas = _converse.connection.sent_stanzas;
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1);
const msg_obj = view.model.messages.at(0);
const retraction_stanza = await u.waitUntil(() => sent_stanzas.filter(s => s.querySelector('message apply-to[xmlns="urn:xmpp:fasten:0"]')).pop());
@ -365,8 +365,8 @@ describe("Message Retractions", function () {
expect(view.model.messages.length).toBe(1);
expect(message.get('retracted')).toBeTruthy();
expect(message.get('editable')).toBeFalsy();
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const el = view.el.querySelector('.chat-msg--retracted .chat-msg__message');
expect(view.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const el = view.querySelector('.chat-msg--retracted .chat-msg__message');
expect(el.textContent.trim()).toBe('Romeo Montague has removed this message');
done();
}));
@ -393,7 +393,7 @@ describe("Message Retractions", function () {
`);
const view = _converse.api.chatviews.get(muc_jid);
await view.model.handleMessageStanza(received_stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
expect(view.model.messages.at(0).get('retracted')).toBeFalsy();
expect(view.model.messages.at(0).get('is_ephemeral')).toBeFalsy();
@ -407,12 +407,12 @@ describe("Message Retractions", function () {
_converse.connection._dataRecv(mock.createRequest(retraction_stanza));
// We opportunistically save the message as retracted, even before receiving the retraction message
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('retracted')).toBeTruthy();
expect(view.model.messages.at(0).get('editable')).toBe(false);
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const msg_el = view.el.querySelector('.chat-msg--retracted .chat-msg__message');
expect(view.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const msg_el = view.querySelector('.chat-msg--retracted .chat-msg__message');
expect(msg_el.textContent.trim()).toBe('eve has removed this message');
expect(msg_el.querySelector('.chat-msg--retracted q')).toBe(null);
done();
@ -443,7 +443,7 @@ describe("Message Retractions", function () {
expect(view.model.messages.at(0).get('retracted')).toBeFalsy();
const reason = "This content is inappropriate for this forum!"
const retract_button = await u.waitUntil(() => view.el.querySelector('.chat-msg__content .chat-msg__action-retract'));
const retract_button = await u.waitUntil(() => view.querySelector('.chat-msg__content .chat-msg__action-retract'));
retract_button.click();
await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
@ -472,15 +472,15 @@ describe("Message Retractions", function () {
_converse.connection._dataRecv(mock.createRequest(result_iq));
// We opportunistically save the message as retracted, even before receiving the retraction message
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('moderated')).toBe('retracted');
expect(view.model.messages.at(0).get('moderation_reason')).toBe(reason);
expect(view.model.messages.at(0).get('is_ephemeral')).toBe(false);
expect(view.model.messages.at(0).get('editable')).toBe(false);
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
expect(view.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const msg_el = view.el.querySelector('.chat-msg--retracted .chat-msg__message');
const msg_el = view.querySelector('.chat-msg--retracted .chat-msg__message');
expect(msg_el.firstElementChild.textContent.trim()).toBe('romeo has removed this message');
const qel = msg_el.querySelector('q');
@ -523,8 +523,8 @@ describe("Message Retractions", function () {
</message>
`);
await view.model.handleMessageStanza(received_stanza);
await u.waitUntil(() => view.el.querySelector('.chat-msg__content'));
expect(view.el.querySelector('.chat-msg__content .chat-msg__action-retract')).toBe(null);
await u.waitUntil(() => view.querySelector('.chat-msg__content'));
expect(view.querySelector('.chat-msg__content .chat-msg__action-retract')).toBe(null);
const result = await view.model.canModerateMessages();
expect(result).toBe(false);
done();
@ -553,7 +553,7 @@ describe("Message Retractions", function () {
await u.waitUntil(() => view.model.messages.length === 1);
expect(view.model.messages.length).toBe(1);
const retract_button = await u.waitUntil(() => view.el.querySelector('.chat-msg__content .chat-msg__action-retract'));
const retract_button = await u.waitUntil(() => view.querySelector('.chat-msg__content .chat-msg__action-retract'));
retract_button.click();
await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
@ -579,13 +579,13 @@ describe("Message Retractions", function () {
</message>`);
await view.model.handleMessageStanza(retraction);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('moderated')).toBe('retracted');
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const msg_el = view.el.querySelector('.chat-msg--retracted .chat-msg__message div');
expect(view.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const msg_el = view.querySelector('.chat-msg--retracted .chat-msg__message div');
expect(msg_el.textContent).toBe('romeo has removed this message');
const qel = view.el.querySelector('.chat-msg--retracted .chat-msg__message q');
const qel = view.querySelector('.chat-msg--retracted .chat-msg__message q');
expect(qel.textContent).toBe('This content is inappropriate for this forum!');
const result_iq = $iq({'from': muc_jid, 'id': stanza.getAttribute('id'), 'to': _converse.bare_jid, 'type': 'result'});
@ -615,7 +615,7 @@ describe("Message Retractions", function () {
expect(occupant.get('role')).toBe('moderator');
occupant.save('role', 'member');
const retraction_stanza = await sendAndThenRetractMessage(_converse, view);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1, 1000);
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1, 1000);
console.log('XXX: First message retracted by author');
const msg_obj = view.model.messages.last();
@ -654,8 +654,8 @@ describe("Message Retractions", function () {
expect(view.model.messages.last().get('retracted')).toBeTruthy();
expect(view.model.messages.last().get('is_ephemeral')).toBe(false);
expect(view.model.messages.last().get('editable')).toBe(false);
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const el = view.el.querySelector('.chat-msg--retracted .chat-msg__message div');
expect(view.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const el = view.querySelector('.chat-msg--retracted .chat-msg__message div');
expect(el.textContent).toBe('romeo has removed this message');
done();
}));
@ -672,13 +672,13 @@ describe("Message Retractions", function () {
const occupant = view.model.getOwnOccupant();
expect(occupant.get('role')).toBe('moderator');
occupant.save('role', 'member');
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.includes("romeo is no longer a moderator"));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.includes("romeo is no longer a moderator"));
const retraction_stanza = await sendAndThenRetractMessage(_converse, view);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1, 1000);
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1, 1000);
expect(view.model.messages.length).toBe(1);
await u.waitUntil(() => view.model.messages.last().get('retracted'), 1000);
const el = view.el.querySelector('.chat-msg--retracted .chat-msg__message div');
const el = view.querySelector('.chat-msg--retracted .chat-msg__message div');
expect(el.textContent.trim()).toBe('romeo has removed this message');
const message = view.model.messages.last();
@ -697,14 +697,14 @@ describe("Message Retractions", function () {
</message>`);
_converse.connection._dataRecv(mock.createRequest(error));
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__error').length === 1, 1000);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 0, 1000);
await u.waitUntil(() => view.querySelectorAll('.chat-msg__error').length === 1, 1000);
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 0, 1000);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('retracted')).toBeFalsy();
expect(view.model.messages.at(0).get('is_ephemeral')).toBeFalsy();
expect(view.model.messages.at(0).get('editable')).toBe(false);
const errmsg = view.el.querySelector('.chat-msg__error');
const errmsg = view.querySelector('.chat-msg__error');
expect(errmsg.textContent.trim()).toBe("You're not allowed to retract your message.");
done();
}));
@ -723,23 +723,23 @@ describe("Message Retractions", function () {
const occupant = view.model.getOwnOccupant();
expect(occupant.get('role')).toBe('moderator');
occupant.save('role', 'member');
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.includes("romeo is no longer a moderator"))
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.includes("romeo is no longer a moderator"))
await sendAndThenRetractMessage(_converse, view);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.last().get('retracted')).toBeTruthy();
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1);
const el = view.el.querySelector('.chat-msg--retracted .chat-msg__message div');
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1);
const el = view.querySelector('.chat-msg--retracted .chat-msg__message div');
expect(el.textContent.trim()).toBe('romeo has removed this message');
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 0);
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 0);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('retracted')).toBeFalsy();
expect(view.model.messages.at(0).get('is_ephemeral')).toBeFalsy();
expect(view.model.messages.at(0).get('editable')).toBeTruthy();
const error_messages = view.el.querySelectorAll('.chat-msg__error');
const error_messages = view.querySelectorAll('.chat-msg__error');
expect(error_messages.length).toBe(1);
expect(error_messages[0].textContent.trim()).toBe('A timeout happened while while trying to retract your message.');
done();
@ -759,7 +759,7 @@ describe("Message Retractions", function () {
expect(occupant.get('role')).toBe('moderator');
view.model.sendMessage('Visit this site to get free bitcoin');
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
const stanza_id = 'retraction-id-1';
const msg_obj = view.model.messages.at(0);
const reflection_stanza = u.toStanza(`
@ -774,7 +774,7 @@ describe("Message Retractions", function () {
<origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
</message>`);
await view.model.handleMessageStanza(reflection_stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
await u.waitUntil(() => view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('editable')).toBe(true);
@ -811,7 +811,7 @@ describe("Message Retractions", function () {
expect(occupant.get('role')).toBe('moderator');
view.model.sendMessage('Visit this site to get free bitcoin');
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
const stanza_id = 'retraction-id-1';
const msg_obj = view.model.messages.at(0);
const reflection_stanza = u.toStanza(`
@ -826,7 +826,7 @@ describe("Message Retractions", function () {
<origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
</message>`);
await view.model.handleMessageStanza(reflection_stanza);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
await u.waitUntil(() => view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('editable')).toBe(true);
@ -853,15 +853,15 @@ describe("Message Retractions", function () {
_converse.connection._dataRecv(mock.createRequest(result_iq));
// We opportunistically save the message as retracted, even before receiving the retraction message
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1);
expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('moderated')).toBe('retracted');
expect(view.model.messages.at(0).get('moderation_reason')).toBe(undefined);
expect(view.model.messages.at(0).get('is_ephemeral')).toBe(false);
expect(view.model.messages.at(0).get('editable')).toBe(false);
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
expect(view.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const msg_el = view.el.querySelector('.chat-msg--retracted .chat-msg__message');
const msg_el = view.querySelector('.chat-msg--retracted .chat-msg__message');
expect(msg_el.firstElementChild.textContent.trim()).toBe('romeo has removed this message');
expect(msg_el.querySelector('q')).toBe(null);
@ -967,9 +967,9 @@ describe("Message Retractions", function () {
expect(await view.model.handleRetraction.calls.first().returnValue).toBe(false);
expect(await view.model.handleRetraction.calls.all()[1].returnValue).toBe(false);
expect(await view.model.handleRetraction.calls.all()[2].returnValue).toBe(true);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const el = view.el.querySelector('.chat-msg--retracted .chat-msg__message div');
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
expect(view.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const el = view.querySelector('.chat-msg--retracted .chat-msg__message div');
expect(el.textContent.trim()).toBe('Mercutio has removed this message');
expect(u.hasClass('chat-msg--followup', el.parentElement)).toBe(false);
done();
@ -1043,10 +1043,10 @@ describe("Message Retractions", function () {
message = view.model.messages.at(0);
expect(message.get('retracted')).toBeTruthy();
expect(message.get('is_tombstone')).toBe(true);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const el = view.el.querySelector('.chat-msg--retracted .chat-msg__message div');
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const el = view.querySelector('.chat-msg--retracted .chat-msg__message div');
expect(el.textContent.trim()).toBe('eve has removed this message');
done();
}));
@ -1128,13 +1128,13 @@ describe("Message Retractions", function () {
expect(message.get('is_tombstone')).toBe(true);
expect(message.get('moderation_reason')).toBe("This message contains inappropriate content");
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length, 500);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length, 500);
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const el = view.el.querySelector('.chat-msg--retracted .chat-msg__message div');
expect(view.querySelectorAll('.chat-msg--retracted').length).toBe(1);
const el = view.querySelector('.chat-msg--retracted .chat-msg__message div');
expect(el.textContent.trim()).toBe('A moderator has removed this message');
const qel = view.el.querySelector('.chat-msg--retracted .chat-msg__message q');
const qel = view.querySelector('.chat-msg--retracted .chat-msg__message q');
expect(qel.textContent.trim()).toBe('This message contains inappropriate content');
done();
}));

View File

@ -18,7 +18,7 @@ describe("Chatrooms", function () {
const muc_jid = 'coven@chat.shakespeare.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo')
const view = _converse.chatboxviews.get(muc_jid);
const textarea = view.el.querySelector('.chat-textarea')
const textarea = view.querySelector('.chat-textarea')
textarea.value = '/register';
view.onKeyDown({
target: textarea,

View File

@ -153,13 +153,13 @@ describe("The Contacts Roster", function () {
['rosterGroupsFetched'], {},
async function (done, _converse) {
const filter = _converse.rosterview.el.querySelector('.roster-filter');
const filter = _converse.rosterview.querySelector('.roster-filter');
expect(filter === null).toBe(false);
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
const view = _converse.chatboxviews.get('controlbox');
const flyout = view.el.querySelector('.box-flyout');
const flyout = view.querySelector('.box-flyout');
const panel = flyout.querySelector('.controlbox-pane');
function hasScrollBar (el) {
return el.isConnected && flyout.offsetHeight < panel.scrollHeight;
@ -176,7 +176,7 @@ describe("The Contacts Roster", function () {
await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current');
let filter = _converse.rosterview.el.querySelector('.roster-filter');
let filter = _converse.rosterview.querySelector('.roster-filter');
const roster = _converse.rosterview.roster_el;
_converse.rosterview.filter_view.delegateEvents();
@ -194,7 +194,7 @@ describe("The Contacts Roster", function () {
const visible_group = sizzle('.roster-group', roster).filter(u.isVisible).pop();
expect(visible_group.querySelector('a.group-toggle').textContent.trim()).toBe('friends & acquaintences');
filter = _converse.rosterview.el.querySelector('.roster-filter');
filter = _converse.rosterview.querySelector('.roster-filter');
filter.value = "j";
u.triggerEvent(filter, "keydown", "KeyboardEvent");
await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 2), 700);
@ -207,14 +207,14 @@ describe("The Contacts Roster", function () {
expect(visible_groups[0].textContent.trim()).toBe('friends & acquaintences');
expect(visible_groups[1].textContent.trim()).toBe('Ungrouped');
filter = _converse.rosterview.el.querySelector('.roster-filter');
filter = _converse.rosterview.querySelector('.roster-filter');
filter.value = "xxx";
u.triggerEvent(filter, "keydown", "KeyboardEvent");
await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 0), 600);
visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle'));
expect(visible_groups.length).toBe(0);
filter = _converse.rosterview.el.querySelector('.roster-filter');
filter = _converse.rosterview.querySelector('.roster-filter');
filter.value = "";
u.triggerEvent(filter, "keydown", "KeyboardEvent");
await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 17), 600);
@ -230,7 +230,7 @@ describe("The Contacts Roster", function () {
await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current');
const filter = _converse.rosterview.el.querySelector('.roster-filter');
const filter = _converse.rosterview.querySelector('.roster-filter');
const roster = _converse.rosterview.roster_el;
_converse.rosterview.filter_view.delegateEvents();
@ -278,13 +278,13 @@ describe("The Contacts Roster", function () {
_converse.rosterview.filter_view.delegateEvents();
var roster = _converse.rosterview.roster_el;
var button = _converse.rosterview.el.querySelector('span[data-type="groups"]');
var button = _converse.rosterview.querySelector('span[data-type="groups"]');
button.click();
await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 17), 600);
expect(sizzle('.roster-group', roster).filter(u.isVisible).length).toBe(5);
var filter = _converse.rosterview.el.querySelector('.roster-filter');
var filter = _converse.rosterview.querySelector('.roster-filter');
filter.value = "colleagues";
u.triggerEvent(filter, "keydown", "KeyboardEvent");
@ -294,14 +294,14 @@ describe("The Contacts Roster", function () {
// Check that all contacts under the group are shown
expect(sizzle('div.roster-group:not(.collapsed) li', roster).filter(l => !u.isVisible(l)).length).toBe(0);
filter = _converse.rosterview.el.querySelector('.roster-filter');
filter = _converse.rosterview.querySelector('.roster-filter');
filter.value = "xxx";
u.triggerEvent(filter, "keydown", "KeyboardEvent");
await u.waitUntil(() => (roster.querySelectorAll('div.roster-group.collapsed').length === 5), 700);
expect(roster.querySelectorAll('div.roster-group:not(.collapsed) a').length).toBe(0);
filter = _converse.rosterview.el.querySelector('.roster-filter');
filter = _converse.rosterview.querySelector('.roster-filter');
filter.value = ""; // Check that groups are shown again, when the filter string is cleared.
u.triggerEvent(filter, "keydown", "KeyboardEvent");
await u.waitUntil(() => (roster.querySelectorAll('div.roster-group.collapsed').length === 0), 700);
@ -318,15 +318,15 @@ describe("The Contacts Roster", function () {
await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current');
const filter = _converse.rosterview.el.querySelector('.roster-filter');
const filter = _converse.rosterview.querySelector('.roster-filter');
filter.value = "xxx";
u.triggerEvent(filter, "keydown", "KeyboardEvent");
expect(_.includes(filter.classList, "x")).toBeFalsy();
expect(u.hasClass('hidden', _converse.rosterview.el.querySelector('.roster-filter-form .clear-input'))).toBeTruthy();
expect(u.hasClass('hidden', _converse.rosterview.querySelector('.roster-filter-form .clear-input'))).toBeTruthy();
const isHidden = _.partial(u.hasClass, 'hidden');
await u.waitUntil(() => !isHidden(_converse.rosterview.el.querySelector('.roster-filter-form .clear-input')), 900);
_converse.rosterview.el.querySelector('.clear-input').click();
await u.waitUntil(() => !isHidden(_converse.rosterview.querySelector('.roster-filter-form .clear-input')), 900);
_converse.rosterview.querySelector('.clear-input').click();
expect(document.querySelector('.roster-filter').value).toBe("");
done();
}));
@ -344,11 +344,11 @@ describe("The Contacts Roster", function () {
jid = mock.cur_names[4].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'dnd');
mock.openControlBox(_converse);
const button = _converse.rosterview.el.querySelector('span[data-type="state"]');
const button = _converse.rosterview.querySelector('span[data-type="state"]');
button.click();
const roster = _converse.rosterview.roster_el;
await u.waitUntil(() => sizzle('li', roster).filter(u.isVisible).length === 15, 900);
const filter = _converse.rosterview.el.querySelector('.state-type');
const filter = _converse.rosterview.querySelector('.state-type');
expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5);
filter.value = "online";
u.triggerEvent(filter, 'change');
@ -565,7 +565,7 @@ describe("The Contacts Roster", function () {
}
});
const view = _converse.rosterview.get('Colleagues');
const toggle = view.el.querySelector('a.group-toggle');
const toggle = view.querySelector('a.group-toggle');
expect(view.model.get('state')).toBe('opened');
toggle.click();
await u.waitUntil(() => view.model.get('state') === 'closed');
@ -736,7 +736,7 @@ describe("The Contacts Roster", function () {
await u.waitUntil(() => sizzle('li', _converse.rosterview.get('Pending contacts').el).filter(u.isVisible).length, 900);
// Check that they are sorted alphabetically
const view = _converse.rosterview.get('Pending contacts');
const spans = view.el.querySelectorAll('.pending-xmpp-contact span');
const spans = view.querySelectorAll('.pending-xmpp-contact span');
const t = _.reduce(spans, (result, value) => result + _.trim(value.textContent), '');
expect(t).toEqual(mock.pend_names.slice(0,i+1).sort().join(''));
done();
@ -757,7 +757,7 @@ describe("The Contacts Roster", function () {
await _addContacts(_converse);
await u.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500);
await checkHeaderToggling.apply(_converse, [_converse.rosterview.el.querySelector('.roster-group')]);
await checkHeaderToggling.apply(_converse, [_converse.rosterview.querySelector('.roster-group')]);
done();
}));
@ -769,7 +769,7 @@ describe("The Contacts Roster", function () {
_converse.roster_groups = false;
await _addContacts(_converse);
await u.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500);
_converse.rosterview.el.querySelector('.roster-group a.group-toggle').click();
_converse.rosterview.querySelector('.roster-group a.group-toggle').click();
const name = "Romeo Montague";
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.create({
@ -814,7 +814,7 @@ describe("The Contacts Roster", function () {
async function (done, _converse) {
await _addContacts(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('li').length);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('li').length);
const name = mock.cur_names[0];
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
const contact = _converse.roster.get(jid);
@ -857,12 +857,12 @@ describe("The Contacts Roster", function () {
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback) {
if (typeof callback === "function") { return callback(); }
});
expect(u.isVisible(_converse.rosterview.el.querySelector('.roster-group'))).toBe(true);
expect(u.isVisible(_converse.rosterview.querySelector('.roster-group'))).toBe(true);
sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, _converse.rosterview.el).pop().click();
expect(window.confirm).toHaveBeenCalled();
expect(_converse.connection.sendIQ).toHaveBeenCalled();
expect(contact.removeFromRoster).toHaveBeenCalled();
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length === 0);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group').length === 0);
done();
}));
@ -872,7 +872,7 @@ describe("The Contacts Roster", function () {
async function (done, _converse) {
await _addContacts(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group li').length, 700);
const roster = _converse.rosterview.el;
const groups = roster.querySelectorAll('.roster-group');
const groupnames = Array.from(groups).map(g => g.getAttribute('data-group'));
@ -1025,7 +1025,7 @@ describe("The Contacts Roster", function () {
_converse.roster.get(jid).presence.set('show', 'unavailable');
}
await u.waitUntil(() => u.isVisible(_converse.rosterview.el.querySelector('li:first-child')), 900);
await u.waitUntil(() => u.isVisible(_converse.rosterview.querySelector('li:first-child')), 900);
const roster = _converse.rosterview.el;
const groups = roster.querySelectorAll('.roster-group');
const groupnames = Array.from(groups).map(g => g.getAttribute('data-group'));
@ -1165,7 +1165,7 @@ describe("The Contacts Roster", function () {
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
const contact = _converse.roster.get(jid);
spyOn(contact, 'authorize').and.callFake(() => contact);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length)
await u.waitUntil(() => _converse.rosterview.querySelectorAll('.roster-group li').length)
// TODO: Testing can be more thorough here, the user is
// actually not accepted/authorized because of
// mock_connection.

View File

@ -39,10 +39,10 @@ describe("A spoiler message", function () {
const view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Mercutio');
const message_content = view.el.querySelector('.chat-msg__text');
expect(view.querySelector('.chat-msg__author').textContent.trim()).toBe('Mercutio');
const message_content = view.querySelector('.chat-msg__text');
await u.waitUntil(() => message_content.textContent === spoiler);
const spoiler_hint_el = view.el.querySelector('.spoiler-hint');
const spoiler_hint_el = view.querySelector('.spoiler-hint');
expect(spoiler_hint_el.textContent).toBe(spoiler_hint);
done();
}));
@ -75,13 +75,13 @@ describe("A spoiler message", function () {
await new Promise(resolve => _converse.api.listen.once('chatBoxViewInitialized', resolve));
const view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => u.isVisible(view.el));
await u.waitUntil(() => u.isVisible(view));
await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
await u.waitUntil(() => u.isVisible(view.el.querySelector('.chat-msg__author')));
expect(view.el.querySelector('.chat-msg__author').textContent.includes('Mercutio')).toBeTruthy();
const message_content = view.el.querySelector('.chat-msg__text');
await u.waitUntil(() => u.isVisible(view.querySelector('.chat-msg__author')));
expect(view.querySelector('.chat-msg__author').textContent.includes('Mercutio')).toBeTruthy();
const message_content = view.querySelector('.chat-msg__text');
await u.waitUntil(() => message_content.textContent === spoiler);
const spoiler_hint_el = view.el.querySelector('.spoiler-hint');
const spoiler_hint_el = view.querySelector('.spoiler-hint');
expect(spoiler_hint_el.textContent).toBe('');
done();
}));
@ -112,11 +112,11 @@ describe("A spoiler message", function () {
const view = _converse.api.chatviews.get(contact_jid);
spyOn(_converse.connection, 'send');
await u.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
await u.waitUntil(() => view.querySelector('.toggle-compose-spoiler'));
let spoiler_toggle = view.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click();
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
textarea.value = 'This is the spoiler';
view.onKeyDown({
target: textarea,
@ -147,15 +147,15 @@ describe("A spoiler message", function () {
expect(body_el.textContent).toBe(spoiler);
/* Test the HTML spoiler message */
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Romeo Montague');
expect(view.querySelector('.chat-msg__author').textContent.trim()).toBe('Romeo Montague');
const message_content = view.el.querySelector('.chat-msg__text');
const message_content = view.querySelector('.chat-msg__text');
await u.waitUntil(() => message_content.textContent === spoiler);
const spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler');
const spoiler_msg_el = view.querySelector('.chat-msg__text.spoiler');
expect(Array.from(spoiler_msg_el.classList).includes('hidden')).toBeTruthy();
spoiler_toggle = view.el.querySelector('.spoiler-toggle');
spoiler_toggle = view.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent.trim()).toBe('Show more');
spoiler_toggle.click();
await u.waitUntil(() => !Array.from(spoiler_msg_el.classList).includes('hidden'));
@ -190,15 +190,15 @@ describe("A spoiler message", function () {
await mock.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]);
const view = _converse.api.chatviews.get(contact_jid);
await u.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
await u.waitUntil(() => view.querySelector('.toggle-compose-spoiler'));
let spoiler_toggle = view.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click();
spyOn(_converse.connection, 'send');
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.querySelector('.chat-textarea');
textarea.value = 'This is the spoiler';
const hint_input = view.el.querySelector('.spoiler-hint');
const hint_input = view.querySelector('.spoiler-hint');
hint_input.value = 'This is the hint';
view.onKeyDown({
@ -229,15 +229,15 @@ describe("A spoiler message", function () {
const body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe(spoiler);
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Romeo Montague');
expect(view.querySelector('.chat-msg__author').textContent.trim()).toBe('Romeo Montague');
const message_content = view.el.querySelector('.chat-msg__text');
const message_content = view.querySelector('.chat-msg__text');
await u.waitUntil(() => message_content.textContent === spoiler);
const spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler');
const spoiler_msg_el = view.querySelector('.chat-msg__text.spoiler');
expect(Array.from(spoiler_msg_el.classList).includes('hidden')).toBeTruthy();
spoiler_toggle = view.el.querySelector('.spoiler-toggle');
spoiler_toggle = view.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent.trim()).toBe('Show more');
spoiler_toggle.click();
await u.waitUntil(() => !Array.from(spoiler_msg_el.classList).includes('hidden'));

View File

@ -29,7 +29,7 @@ describe("An incoming chat Message", function () {
expect(view.model.messages.models[0].get('is_unstyled')).toBe(true);
setTimeout(() => {
const msg_el = view.el.querySelector('converse-chat-message-body');
const msg_el = view.querySelector('converse-chat-message-body');
expect(msg_el.innerText).toBe(msg_text);
done();
}, 500);
@ -60,7 +60,7 @@ describe("An incoming chat Message", function () {
expect(view.model.messages.models[0].get('is_unstyled')).toBe(false);
setTimeout(() => {
const msg_el = view.el.querySelector('converse-chat-message-body');
const msg_el = view.querySelector('converse-chat-message-body');
expect(msg_el.innerText).toBe(msg_text);
done();
}, 500);
@ -80,7 +80,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = view.el.querySelector('converse-chat-message-body');
msg_el = view.querySelector('converse-chat-message-body');
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'This <span class="styling-directive">*</span>'+
@ -94,7 +94,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'Here\'s a <span class="styling-directive">~</span><del>strikethrough section</del><span class="styling-directive">~</span>');
@ -104,7 +104,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<span class="styling-directive">~</span>'+
@ -117,7 +117,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<span class="styling-directive">*</span>'+
@ -129,7 +129,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<span class="styling-directive">~</span><del> Hello! <span title=":poop:">💩</span> </del><span class="styling-directive">~</span>');
@ -139,7 +139,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'This *is not a styling hint \n'+
@ -149,7 +149,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'(There are three blocks in this body marked by parens,)\n'+
@ -162,7 +162,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'_<span class="styling-directive">_</span><i> hello world </i><span class="styling-directive">_</span>');
@ -172,7 +172,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'Go to ~https://conversejs.org~now <span class="styling-directive">_</span><i>please</i><span class="styling-directive">_</span>');
@ -181,7 +181,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'Go to <span class="styling-directive">_</span>'+
@ -205,7 +205,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'Here\'s a code block: \n'+
@ -217,7 +217,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<div class="styling-directive">```</div>'+
@ -230,7 +230,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'```ignored\n (println "Hello, world!")\n ```\n\n'+
@ -253,7 +253,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote>This is quoted text\nThis is also quoted</blockquote>\nThis is not quoted');
@ -262,7 +262,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote>This is <span class="styling-directive">*</span><b>quoted</b><span class="styling-directive">*</span> text\n'+
@ -273,7 +273,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === "<blockquote><blockquote>This is doubly quoted text</blockquote></blockquote>");
@ -281,7 +281,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === "<blockquote><blockquote>This is doubly quoted text</blockquote></blockquote>");
@ -289,7 +289,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote>'+
@ -303,7 +303,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote>```\n (println "Hello, world!")</blockquote>\n\n'+
@ -313,7 +313,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote>Also, icons.js is loaded from /dist, instead of dist.</blockquote>\n'+
@ -323,7 +323,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote>Where is it located?</blockquote>\n'+
@ -334,7 +334,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote>What do you think of it?</blockquote>\n <span title=":poop:">💩</span>');
@ -343,7 +343,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote>What do you think of it?</blockquote>\n<span class="styling-directive">~</span><del>hello</del><span class="styling-directive">~</span>');
@ -352,7 +352,7 @@ describe("An incoming chat Message", function () {
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === 'hello world &gt; this is not a quote');
@ -380,7 +380,7 @@ describe("An incoming chat Message", function () {
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
`<blockquote>What do you think of it <span class="mention">romeo</span>?</blockquote>\n Did you see this <span class="mention">romeo</span>?`);
@ -407,10 +407,10 @@ describe("A outgoing groupchat Message", function () {
}).c('body').t(msg_text).up()
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'23', 'end':'29', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).nodeTree;
await view.model.handleMessageStanza(msg);
const message = await u.waitUntil(() => view.el.querySelector('.chat-msg__text'));
const message = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
expect(message.classList.length).toEqual(1);
const msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
const msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'This <span class="styling-directive">*</span><b>message mentions <span class="mention mention--self badge badge-info">romeo</span></b><span class="styling-directive">*</span>');

View File

@ -15,8 +15,9 @@ describe("The User Details Modal", function () {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
await u.waitUntil(() => _converse.chatboxes.length > 1);
const view = _converse.chatboxviews.get(contact_jid);
let show_modal_button = view.el.querySelector('.show-user-details-modal');
let show_modal_button = view.querySelector('.show-user-details-modal');
show_modal_button.click();
const modal = _converse.api.modal.get('user-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
@ -27,7 +28,7 @@ describe("The User Details Modal", function () {
remove_contact_button.click();
await u.waitUntil(() => modal.el.getAttribute('aria-hidden'), 1000);
await u.waitUntil(() => !u.isVisible(modal.el));
show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button = view.querySelector('.show-user-details-modal');
show_modal_button.click();
remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(remove_contact_button === null).toBeTruthy();
@ -43,7 +44,7 @@ describe("The User Details Modal", function () {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
const view = _converse.chatboxviews.get(contact_jid);
let show_modal_button = view.el.querySelector('.show-user-details-modal');
let show_modal_button = view.querySelector('.show-user-details-modal');
show_modal_button.click();
let modal = _converse.api.modal.get('user-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 2000);
@ -61,12 +62,12 @@ describe("The User Details Modal", function () {
expect(u.ancestor(header, '.modal-content').querySelector('.modal-body p').textContent.trim())
.toBe("Sorry, there was an error while trying to remove Mercutio as a contact.");
document.querySelector('.alert-danger button.close').click();
show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button = view.querySelector('.show-user-details-modal');
show_modal_button.click();
modal = _converse.api.modal.get('user-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 2000)
show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button = view.querySelector('.show-user-details-modal');
show_modal_button.click();
await u.waitUntil(() => u.isVisible(modal.el), 2000)

View File

@ -294,8 +294,8 @@ describe("XSS", function () {
_converse.connection._dataRecv(mock.createRequest(presence));
const view = _converse.chatboxviews.get('lounge@montague.lit');
await u.waitUntil(() => view.el.querySelectorAll('li .occupant-nick').length, 500);
const occupants = view.el.querySelector('.occupant-list').querySelectorAll('li .occupant-nick');
await u.waitUntil(() => view.querySelectorAll('li .occupant-nick').length, 500);
const occupants = view.querySelector('.occupant-list').querySelectorAll('li .occupant-nick');
expect(occupants.length).toBe(2);
expect(occupants[0].textContent.trim()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;");
done();
@ -314,7 +314,7 @@ describe("XSS", function () {
'text': subject,
'author': 'ralphm'
}});
const text = await u.waitUntil(() => view.el.querySelector('.chat-head__desc')?.textContent.trim());
const text = await u.waitUntil(() => view.querySelector('.chat-head__desc')?.textContent.trim());
expect(text).toBe(subject);
done();
}));

View File

@ -1,4 +1,5 @@
import { api, converse } from "@converse/headless/core";
import tpl_converse from "../templates/converse.js";
import { CustomElement } from './element.js';
/**
@ -8,11 +9,10 @@ import { api, converse } from "@converse/headless/core";
* It can be inserted into the DOM before or after Converse has loaded or been
* initialized.
*/
class ConverseRoot extends HTMLElement {
class ConverseRoot extends CustomElement {
async connectedCallback () {
await api.waitUntil('initialized');
converse.insertInto(this);
render () { // eslint-disable-line class-methods-use-this
return tpl_converse();
}
}

View File

@ -17,16 +17,17 @@ import "./plugins/chatview/index.js"; // Renders standalone chat boxes for
import "./plugins/controlbox/index.js"; // The control box
import "./plugins/dragresize/index.js"; // Allows chat boxes to be resized by dragging them
import "./plugins/fullscreen.js";
import "./plugins/headlines-view/index.js";
import "./plugins/mam-views.js";
import "./plugins/minimize/index.js"; // Allows chat boxes to be minimized
import "./plugins/muc-views/index.js"; // Views related to MUC
import "./plugins/headlines-view/index.js";
import "./plugins/notifications/index.js";
import "./plugins/omemo.js";
import "./plugins/profile/index.js";
import "./plugins/push.js"; // XEP-0357 Push Notifications
import "./plugins/register/index.js"; // XEP-0077 In-band registration
import "./plugins/roomslist/index.js"; // Show currently open chat rooms
import "./plugins/rootview/index.js";
import "./plugins/rosterview/index.js";
import "./plugins/singleton.js";
/* END: Removable components */
@ -43,17 +44,18 @@ const WHITELISTED_PLUGINS = [
'converse-controlbox',
'converse-dragresize',
'converse-fullscreen',
'converse-headlines-view',
'converse-mam-views',
'converse-minimize',
'converse-modal',
'converse-muc-views',
'converse-headlines-view',
'converse-notification',
'converse-omemo',
'converse-profile',
'converse-push',
'converse-register',
'converse-roomslist',
'converse-rootview',
'converse-rosterview',
'converse-singleton'
];

View File

@ -1,4 +1,4 @@
import { isElement } from 'lodash-es';
import isElement from 'lodash-es/isElement';
const LEVELS = {
'debug': 0,

View File

@ -2,7 +2,7 @@ import bootstrap from "bootstrap.native";
import log from "@converse/headless/log";
import tpl_alert_component from "templates/alert.js";
import { View } from '@converse/skeletor/src/view.js';
import { _converse, api, converse } from "@converse/headless/core";
import { api, converse } from "@converse/headless/core";
import { render } from 'lit-html';
const { sizzle } = converse.env;
@ -46,7 +46,7 @@ const BaseModal = View.extend({
},
insertIntoDOM () {
const container_el = _converse.chatboxviews.el.querySelector("#converse-modals");
const container_el = document.querySelector("#converse-modals");
container_el.insertAdjacentElement('beforeEnd', this.el);
},

View File

@ -28,7 +28,7 @@ export const bookmarkableChatRoomView = {
'model': this.model,
'chatroomview': this
});
const container_el = this.el.querySelector('.chatroom-body');
const container_el = this.querySelector('.chatroom-body');
container_el.insertAdjacentElement('beforeend', this.bookmark_form.el);
}
u.showElement(this.bookmark_form.el);

View File

@ -0,0 +1,45 @@
class ChatBoxViews {
constructor () {
this.views = {};
}
add (key, val) {
this.views[key] = val;
}
get (key) {
return this.views[key];
}
getAll () {
return Object.values(this.views);
}
keys () {
return Object.keys(this.views);
}
remove (key) {
delete this.views[key];
}
map (f) {
return Object.values(this.views).map(f);
}
forEach (f) {
return Object.values(this.views).forEach(f);
}
filter (f) {
return Object.values(this.views).filter(f);
}
closeAllChatBoxes () {
return Promise.all(Object.values(this.views).map(v => v.close({ 'name': 'closeAllChatBoxes' })));
}
}
export default ChatBoxViews;

View File

@ -3,23 +3,13 @@
* @copyright 2020, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import './view.js';
import '@converse/headless/plugins/chatboxes';
import 'components/converse.js';
import ChatBoxViews from './container.js';
import ViewWithAvatar from 'shared/avatar.js';
import ChatBoxViews from './view.js';
import { _converse, api, converse } from '@converse/headless/core';
function onChatBoxViewsInitialized () {
_converse.chatboxviews = new _converse.ChatBoxViews({
'model': _converse.chatboxes
});
/**
* Triggered once the _converse.ChatBoxViews view-colleciton has been initialized
* @event _converse#chatBoxViewsInitialized
* @example _converse.api.listen.on('chatBoxViewsInitialized', () => { ... });
*/
api.trigger('chatBoxViewsInitialized');
}
function calculateViewportHeightUnit () {
const vh = window.innerHeight * 0.01;
@ -47,13 +37,17 @@ converse.plugins.add('converse-chatboxviews', {
});
_converse.ViewWithAvatar = ViewWithAvatar;
_converse.ChatBoxViews = ChatBoxViews;
_converse.chatboxviews = new ChatBoxViews();
/************************ BEGIN Event Handlers ************************/
api.listen.on('chatBoxesInitialized', onChatBoxViewsInitialized);
api.listen.on('chatBoxesInitialized', () => {
_converse.chatboxes.on('destroy', m => _converse.chatboxviews.remove(m.get('jid')));
});
api.listen.on('cleanup', () => delete _converse.chatboxviews);
api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes());
api.listen.on('chatBoxViewsInitialized', calculateViewportHeightUnit);
window.addEventListener('resize', calculateViewportHeightUnit);
/************************ END Event Handlers ************************/

View File

@ -0,0 +1,25 @@
import { html } from 'lit-html';
import { _converse } from '@converse/headless/core';
export default () => {
const { chatboxes, CONTROLBOX_TYPE, CHATROOMS_TYPE } = _converse;
return html`
<converse-minimized-chats></converse-minimized-chats>
${chatboxes.map(m => {
if (m.get('type') === CONTROLBOX_TYPE) {
return html`
<converse-controlbox-toggle class="${!m.get('closed') ? 'hidden' : ''}"></converse-controlbox-toggle>
<converse-controlbox id="controlbox" class="chatbox ${m.get('closed') ? 'hidden' : ''}"></converse-controlbox>
`;
} else if (m.get('type') === CHATROOMS_TYPE) {
return html`
<converse-muc jid="${m.get('jid')}" class="chatbox ${(m.get('hidden') || m.get('minimized')) ? 'hidden' : ''}"></converse-muc>
`;
} else {
return html`
<converse-chat jid="${m.get('jid')}" class="chatbox ${(m.get('hidden') || m.get('minimized')) ? 'hidden' : ''}"></converse-chat>
`;
}
})}
`;
};

View File

@ -1,75 +1,39 @@
import tpl_background_logo from '../../templates/background_logo.js';
import tpl_converse from '../../templates/converse.js';
import { Overview } from '@converse/skeletor/src/overview';
import tpl_chats from './templates/chats.js';
import { ElementView } from '@converse/skeletor/src/element.js';
import { api, _converse } from '@converse/headless/core';
import { render } from 'lit-html';
import { result } from 'lodash-es';
import { _converse, api, converse } from '@converse/headless/core';
const u = converse.env.utils;
const ChatBoxViews = Overview.extend({
_ensureElement () {
/* Override method from backbone.js
* If the #conversejs element doesn't exist, create it.
*/
if (this.el) {
this.setElement(result(this, 'el'), false);
} else {
let el = _converse.root.querySelector('#conversejs');
if (el === null) {
el = document.createElement('div');
el.setAttribute('id', 'conversejs');
u.addClass(`theme-${api.settings.get('theme')}`, el);
const body = _converse.root.querySelector('body');
if (body) {
body.appendChild(el);
} else {
// Perhaps inside a web component?
_converse.root.appendChild(el);
}
}
this.setElement(el, false);
}
},
class ConverseChats extends ElementView {
initialize () {
this.listenTo(this.model, 'destroy', this.removeChat);
this.model = _converse.chatboxes;
this.listenTo(this.model, 'destroy', this.render);
this.listenTo(this.model, 'add', this.render);
this.listenTo(this.model, 'change:hidden', this.render);
this.listenTo(this.model, 'change:closed', this.render);
this.listenTo(this.model, 'change:jid', this.render);
const bg = document.getElementById('conversejs-bg');
if (bg && !bg.innerHTML.trim()) {
render(tpl_background_logo(), bg);
}
const body = document.querySelector('body');
body.classList.add(`converse-${api.settings.get('view_mode')}`);
this.el.classList.add(`converse-${api.settings.get('view_mode')}`);
if (api.settings.get('singleton')) {
this.el.classList.add(`converse-singleton`);
}
this.render();
},
/**
* Triggered once the _converse.ChatBoxViews view-colleciton has been initialized
* @event _converse#chatBoxViewsInitialized
* @example _converse.api.listen.on('chatBoxViewsInitialized', () => { ... });
*/
api.trigger('chatBoxViewsInitialized');
}
render () {
this._ensureElement();
render(tpl_converse(), this.el);
this.row_el = this.el.querySelector('.row');
},
/**
* Add a new DOM element (likely a chat box) into the
* the row managed by this overview.
* @param { HTMLElement } el
*/
insertRowColumn (el) {
this.row_el.insertAdjacentElement('afterBegin', el);
},
removeChat (item) {
this.remove(item.get('id'));
},
closeAllChatBoxes () {
return Promise.all(this.map(v => v.close({ 'name': 'closeAllChatBoxes' })));
render(tpl_chats(), this);
}
});
}
export default ChatBoxViews;
api.elements.define('converse-chats', ConverseChats);

View File

@ -14,25 +14,6 @@ import chatview_api from './api.js';
const { Strophe } = converse.env;
function onWindowStateChanged (data) {
if (_converse.chatboxviews) {
_converse.chatboxviews.forEach(view => {
if (view.model.get('id') !== 'controlbox') {
view.onWindowStateChanged(data.state);
}
});
}
}
function onChatBoxViewsInitialized () {
const views = _converse.chatboxviews;
_converse.chatboxes.on('add', async item => {
if (!views.get(item.get('id')) && item.get('type') === _converse.PRIVATE_CHAT_TYPE) {
await item.initialized;
views.add(item.get('id'), new _converse.ChatBoxView({ model: item }));
}
});
}
converse.plugins.add('converse-chatview', {
/* Plugin dependencies are other plugins which might be
@ -77,8 +58,6 @@ converse.plugins.add('converse-chatview', {
_converse.ChatBoxView = ChatBoxView;
api.listen.on('chatBoxViewsInitialized', onChatBoxViewsInitialized);
api.listen.on('windowStateChanged', onWindowStateChanged);
api.listen.on('connected', () => api.disco.own.features.add(Strophe.NS.SPOILER));
}
});

View File

@ -1,12 +1,10 @@
import BaseChatView from 'shared/chatview.js';
import UserDetailsModal from 'modals/user-details.js';
import log from '@converse/headless/log';
import tpl_chatbox from 'templates/chatbox.js';
import tpl_chatbox_head from 'templates/chatbox_head.js';
import tpl_chatbox_message_form from 'templates/chatbox_message_form.js';
import tpl_spinner from 'templates/spinner.js';
import tpl_toolbar from 'templates/toolbar.js';
import { View } from '@converse/skeletor/src/view.js';
import { __ } from '../../i18n';
import { __ } from 'i18n';
import { _converse, api, converse } from '@converse/headless/core';
import { debounce } from 'lodash-es';
import { html, render } from 'lit-html';
@ -20,12 +18,12 @@ const { dayjs } = converse.env;
* @namespace _converse.ChatBoxView
* @memberOf _converse
*/
const ChatBoxView = View.extend({
length: 200,
className: 'chatbox hidden',
is_chatroom: false, // Leaky abstraction from MUC
export default class ChatView extends BaseChatView {
length = 200
className = 'chatbox hidden'
is_chatroom = false // Leaky abstraction from MUC
events: {
events = {
'click .chatbox-navback': 'showControlBox',
'click .new-msgs-indicator': 'viewUnreadMessages',
'click .send-button': 'onFormSubmitted',
@ -34,15 +32,20 @@ const ChatBoxView = View.extend({
'keydown .chat-textarea': 'onKeyDown',
'keyup .chat-textarea': 'onKeyUp',
'paste .chat-textarea': 'onPaste'
},
}
async initialize () {
const jid = this.getAttribute('jid');
_converse.chatboxviews.add(jid, this);
this.model = _converse.chatboxes.get(jid);
this.initDebounced();
api.listen.on('windowStateChanged', this.onWindowStateChanged);
this.listenTo(this.model, 'change:composing_spoiler', this.renderMessageForm);
this.listenTo(this.model, 'change:hidden', m => (m.get('hidden') ? this.hide() : this.show()));
this.listenTo(this.model, 'change:hidden', m => (!m.get('hidden') && this.show()));
this.listenTo(this.model, 'change:status', this.onStatusMessageChanged);
this.listenTo(this.model, 'destroy', this.remove);
this.listenTo(this.model, 'show', this.show);
this.listenTo(this.model, 'vcard:change', this.renderHeading);
@ -68,7 +71,6 @@ const ChatBoxView = View.extend({
this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages);
await this.model.messages.fetched;
this.insertIntoDOM();
this.model.maybeShow();
this.scrollDown();
/**
@ -78,7 +80,7 @@ const ChatBoxView = View.extend({
* @example _converse.api.listen.on('chatBoxViewInitialized', view => { ... });
*/
api.trigger('chatBoxViewInitialized', this);
},
}
initDebounced () {
this.markScrolled = debounce(this._markScrolled, 100);
@ -93,20 +95,20 @@ const ChatBoxView = View.extend({
this.renderChatHistory = () => this.renderChatContent(false);
this.renderNotifications = () => this.renderChatContent(true);
}
},
}
render () {
const result = tpl_chatbox(Object.assign(this.model.toJSON(), { 'markScrolled': ev => this.markScrolled(ev) }));
render(result, this.el);
this.content = this.el.querySelector('.chat-content');
this.notifications = this.el.querySelector('.chat-content__notifications');
this.msgs_container = this.el.querySelector('.chat-content__messages');
this.help_container = this.el.querySelector('.chat-content__help');
render(result, this);
this.content = this.querySelector('.chat-content');
this.notifications = this.querySelector('.chat-content__notifications');
this.msgs_container = this.querySelector('.chat-content__messages');
this.help_container = this.querySelector('.chat-content__help');
this.renderChatContent();
this.renderMessageForm();
this.renderHeading();
return this;
},
}
onMessageAdded (message) {
this.renderChatHistory();
@ -122,7 +124,7 @@ const ChatBoxView = View.extend({
this.showNewMessagesIndicator();
}
}
},
}
getNotifications () {
if (this.model.notifications.get('chat_state') === _converse.COMPOSING) {
@ -134,16 +136,16 @@ const ChatBoxView = View.extend({
} else {
return '';
}
},
}
getHelpMessages () {
getHelpMessages () { // eslint-disable-line class-methods-use-this
return [
`<strong>/clear</strong>: ${__('Remove messages')}`,
`<strong>/close</strong>: ${__('Close this chat')}`,
`<strong>/me</strong>: ${__('Write in the third person')}`,
`<strong>/help</strong>: ${__('Show this menu')}`
];
},
}
renderHelpMessages () {
render(
@ -159,80 +161,22 @@ const ChatBoxView = View.extend({
this.help_container
);
},
renderChatContent (msgs_by_ref = false) {
if (!this.tpl_chat_content) {
this.tpl_chat_content = o => {
return html`
<converse-chat-content .chatview=${this} .messages=${o.messages} notifications=${o.notifications}>
</converse-chat-content>
`;
};
}
const msg_models = this.model.messages.models;
const messages = msgs_by_ref ? msg_models : Array.from(msg_models);
render(this.tpl_chat_content({ messages, 'notifications': this.getNotifications() }), this.msgs_container);
},
renderToolbar () {
if (!api.settings.get('show_toolbar')) {
return this;
}
const options = Object.assign(
{
'model': this.model,
'chatview': this
},
this.model.toJSON(),
this.getToolbarOptions()
);
render(tpl_toolbar(options), this.el.querySelector('.chat-toolbar'));
/**
* Triggered once the _converse.ChatBoxView's toolbar has been rendered
* @event _converse#renderToolbar
* @type { _converse.ChatBoxView }
* @example _converse.api.listen.on('renderToolbar', view => { ... });
*/
api.trigger('renderToolbar', this);
return this;
},
renderMessageForm () {
const form_container = this.el.querySelector('.message-form-container');
render(
tpl_chatbox_message_form(
Object.assign(this.model.toJSON(), {
'hint_value': this.el.querySelector('.spoiler-hint')?.value,
'label_message': this.model.get('composing_spoiler') ? __('Hidden message') : __('Message'),
'label_spoiler_hint': __('Optional hint'),
'message_value': this.el.querySelector('.chat-textarea')?.value,
'show_send_button': api.settings.get('show_send_button'),
'show_toolbar': api.settings.get('show_toolbar'),
'unread_msgs': __('You have unread messages')
})
),
form_container
);
this.el.addEventListener('focusin', ev => this.emitFocused(ev));
this.el.addEventListener('focusout', ev => this.emitBlurred(ev));
this.renderToolbar();
},
}
showControlBox () {
// Used in mobile view, to navigate back to the controlbox
_converse.chatboxviews.get('controlbox')?.show();
this.hide();
},
}
showUserDetailsModal (ev) {
ev.preventDefault();
api.modal.show(UserDetailsModal, { model: this.model }, ev);
},
}
onDragOver (evt) {
onDragOver (evt) { // eslint-disable-line class-methods-use-this
evt.preventDefault();
},
}
onDrop (evt) {
if (evt.dataTransfer.files.length == 0) {
@ -242,33 +186,12 @@ const ChatBoxView = View.extend({
}
evt.preventDefault();
this.model.sendFiles(evt.dataTransfer.files);
},
}
async renderHeading () {
const tpl = await this.generateHeadingTemplate();
render(tpl, this.el.querySelector('.chat-head-chatbox'));
},
async getHeadingStandaloneButton (promise_or_data) {
const data = await promise_or_data;
return html`
<a
href="#"
class="chatbox-btn ${data.a_class} fa ${data.icon_class}"
@click=${data.handler}
title="${data.i18n_title}"
></a>
`;
},
async getHeadingDropdownItem (promise_or_data) {
const data = await promise_or_data;
return html`
<a href="#" class="dropdown-item ${data.a_class}" @click=${data.handler} title="${data.i18n_title}"
><i class="fa ${data.icon_class}"></i>${data.i18n_text}</a
>
`;
},
render(tpl, this.querySelector('.chat-head-chatbox'));
}
async generateHeadingTemplate () {
const vcard = this.model?.vcard;
@ -295,7 +218,7 @@ const ChatBoxView = View.extend({
'standalone_btns': standalone_btns.map(b => this.getHeadingStandaloneButton(b))
})
);
},
}
/**
* Returns a list of objects which represent buttons for the chat's header.
@ -344,12 +267,12 @@ const ChatBoxView = View.extend({
* });
*/
return _converse.api.hook('getHeadingButtons', this, buttons);
},
}
getToolbarOptions () {
getToolbarOptions () { // eslint-disable-line class-methods-use-this
// FIXME: can this be removed?
return {};
},
}
/**
* Scrolls the chat down, *if* appropriate.
@ -365,32 +288,7 @@ const ChatBoxView = View.extend({
if ((new_own_msg || !this.model.get('scrolled')) && !this.model.isHidden()) {
this.debouncedScrollDown();
}
},
/**
* Scrolls the chat down.
*
* This method will always scroll the chat down, regardless of
* whether the user scrolled up manually or not.
* @param { Event } [ev] - An optional event that is the cause for needing to scroll down.
*/
scrollDown (ev) {
ev?.preventDefault?.();
ev?.stopPropagation?.();
if (this.model.get('scrolled')) {
u.safeSave(this.model, {
'scrolled': false,
'scrollTop': null
});
}
if (this.msgs_container.scrollTo) {
const behavior = this.msgs_container.scrollTop ? 'smooth' : 'auto';
this.msgs_container.scrollTo({ 'top': this.msgs_container.scrollHeight, behavior });
} else {
this.msgs_container.scrollTop = this.msgs_container.scrollHeight;
}
this.onScrolledDown();
},
}
/**
* Scroll to the previously saved scrollTop position, or scroll
@ -403,22 +301,10 @@ const ChatBoxView = View.extend({
} else {
this.scrollDown();
}
},
insertIntoDOM () {
_converse.chatboxviews.insertRowColumn(this.el);
/**
* Triggered once the _converse.ChatBoxView has been inserted into the DOM
* @event _converse#chatBoxInsertedIntoDOM
* @type { _converse.ChatBoxView | _converse.HeadlinesBoxView }
* @example _converse.api.listen.on('chatBoxInsertedIntoDOM', view => { ... });
*/
api.trigger('chatBoxInsertedIntoDOM', this);
return this;
},
}
addSpinner (append = false) {
if (this.el.querySelector('.spinner') === null) {
if (this.querySelector('.spinner') === null) {
const el = u.getElementFromTemplateResult(tpl_spinner());
if (append) {
this.content.insertAdjacentElement('beforeend', el);
@ -427,11 +313,11 @@ const ChatBoxView = View.extend({
this.content.insertAdjacentElement('afterbegin', el);
}
}
},
}
clearSpinner () {
this.content.querySelectorAll('.spinner').forEach(u.removeElement);
},
}
onStatusMessageChanged (item) {
this.renderHeading();
@ -447,7 +333,7 @@ const ChatBoxView = View.extend({
'contact': item.attributes,
'message': item.get('status')
});
},
}
/**
* Given a message element, determine wether it should be
@ -463,7 +349,7 @@ const ChatBoxView = View.extend({
* @method _converse.ChatBoxView#markFollowups
* @param { HTMLElement } el - The message element
*/
markFollowups (el) {
markFollowups (el) { // eslint-disable-line class-methods-use-this
const from = el.getAttribute('data-from');
const previous_el = el.previousElementSibling;
const date = dayjs(el.getAttribute('data-isodate'));
@ -495,7 +381,7 @@ const ChatBoxView = View.extend({
} else {
u.removeClass('chat-msg--followup', next_el);
}
},
}
parseMessageForCommands (text) {
const match = text.replace(/^\s*/, '').match(/^\/(.*)\s*$/);
@ -511,82 +397,7 @@ const ChatBoxView = View.extend({
return true;
}
}
},
async onFormSubmitted (ev) {
ev.preventDefault();
const textarea = this.el.querySelector('.chat-textarea');
const message_text = textarea.value.trim();
if (
(api.settings.get('message_limit') && message_text.length > api.settings.get('message_limit')) ||
!message_text.replace(/\s/g, '').length
) {
return;
}
if (!_converse.connection.authenticated) {
const err_msg = __('Sorry, the connection has been lost, and your message could not be sent');
api.alert('error', __('Error'), err_msg);
api.connection.reconnect();
return;
}
let spoiler_hint,
hint_el = {};
if (this.model.get('composing_spoiler')) {
hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint');
spoiler_hint = hint_el.value;
}
u.addClass('disabled', textarea);
textarea.setAttribute('disabled', 'disabled');
this.el.querySelector('converse-emoji-dropdown')?.hideMenu();
const is_command = this.parseMessageForCommands(message_text);
const message = is_command ? null : await this.model.sendMessage(message_text, spoiler_hint);
if (is_command || message) {
hint_el.value = '';
textarea.value = '';
u.removeClass('correcting', textarea);
textarea.style.height = 'auto';
this.updateCharCounter(textarea.value);
}
if (message) {
/**
* Triggered whenever a message is sent by the user
* @event _converse#messageSend
* @type { _converse.Message }
* @example _converse.api.listen.on('messageSend', message => { ... });
*/
api.trigger('messageSend', message);
}
if (api.settings.get('view_mode') === 'overlayed') {
// XXX: Chrome flexbug workaround. The .chat-content area
// doesn't resize when the textarea is resized to its original size.
this.msgs_container.parentElement.style.display = 'none';
}
textarea.removeAttribute('disabled');
u.removeClass('disabled', textarea);
if (api.settings.get('view_mode') === 'overlayed') {
// XXX: Chrome flexbug workaround.
this.msgs_container.parentElement.style.display = '';
}
// Suppress events, otherwise superfluous CSN gets set
// immediately after the message, causing rate-limiting issues.
this.model.setChatState(_converse.ACTIVE, { 'silent': true });
textarea.focus();
},
updateCharCounter (chars) {
if (api.settings.get('message_limit')) {
const message_limit = this.el.querySelector('.message-limit');
const counter = api.settings.get('message_limit') - chars.length;
message_limit.textContent = counter;
if (counter < 1) {
u.addClass('error', message_limit);
} else {
u.removeClass('error', message_limit);
}
}
},
}
onPaste (ev) {
if (ev.clipboardData.files.length !== 0) {
@ -599,28 +410,7 @@ const ChatBoxView = View.extend({
return;
}
this.updateCharCounter(ev.clipboardData.getData('text/plain'));
},
autocompleteInPicker (input, value) {
const emoji_dropdown = this.el.querySelector('converse-emoji-dropdown');
const emoji_picker = this.el.querySelector('converse-emoji-picker');
if (emoji_picker && emoji_dropdown) {
emoji_picker.model.set({
'ac_position': input.selectionStart,
'autocompleting': value,
'query': value
});
emoji_dropdown.showMenu();
return true;
}
},
onEmojiReceivedFromPicker (emoji) {
const model = this.el.querySelector('converse-emoji-picker').model;
const autocompleting = model.get('autocompleting');
const ac_position = model.get('ac_position');
this.insertIntoTextArea(emoji, autocompleting, false, ac_position);
},
}
/**
* Event handler for when a depressed key goes up
@ -629,7 +419,7 @@ const ChatBoxView = View.extend({
*/
onKeyUp (ev) {
this.updateCharCounter(ev.target.value);
},
}
/**
* Event handler for when a key is pressed down in a chat box textarea.
@ -657,14 +447,14 @@ const ChatBoxView = View.extend({
} else if (ev.keyCode === converse.keycodes.ENTER) {
return this.onEnterPressed(ev);
} else if (ev.keyCode === converse.keycodes.UP_ARROW && !ev.target.selectionEnd) {
const textarea = this.el.querySelector('.chat-textarea');
const textarea = this.querySelector('.chat-textarea');
if (!textarea.value || u.hasClass('correcting', textarea)) {
return this.editEarlierMessage();
}
} else if (
ev.keyCode === converse.keycodes.DOWN_ARROW &&
ev.target.selectionEnd === ev.target.value.length &&
u.hasClass('correcting', this.el.querySelector('.chat-textarea'))
u.hasClass('correcting', this.querySelector('.chat-textarea'))
) {
return this.editLaterMessage();
}
@ -685,15 +475,11 @@ const ChatBoxView = View.extend({
// (which would imply an internal command and not a message).
this.model.setChatState(_converse.COMPOSING);
}
},
}
getOwnMessages () {
return this.model.messages.filter({ 'sender': 'me' });
},
onEnterPressed (ev) {
return this.onFormSubmitted(ev);
},
}
onEscapePressed (ev) {
ev.preventDefault();
@ -703,7 +489,7 @@ const ChatBoxView = View.extend({
message.save('correcting', false);
}
this.insertIntoTextArea('', true, false);
},
}
async onMessageRetractButtonClicked (message) {
if (message.get('sender') !== 'me') {
@ -723,11 +509,11 @@ const ChatBoxView = View.extend({
if (result) {
this.model.retractOwnMessage(message);
}
},
}
onMessageEditButtonClicked (message) {
const currently_correcting = this.model.messages.findWhere('correcting');
const unsent_text = this.el.querySelector('.chat-textarea')?.value;
const unsent_text = this.querySelector('.chat-textarea')?.value;
if (unsent_text && (!currently_correcting || currently_correcting.get('message') !== unsent_text)) {
if (!confirm(__('You have an unsent message which will be lost if you continue. Are you sure?'))) {
return;
@ -742,7 +528,7 @@ const ChatBoxView = View.extend({
message.save('correcting', false);
this.insertIntoTextArea('', true, false);
}
},
}
editLaterMessage () {
let message;
@ -764,7 +550,7 @@ const ChatBoxView = View.extend({
} else {
this.insertIntoTextArea('', true, false);
}
},
}
editEarlierMessage () {
let message;
@ -789,15 +575,15 @@ const ChatBoxView = View.extend({
this.insertIntoTextArea(u.prefixMentions(message), true, true);
message.save('correcting', true);
}
},
}
inputChanged (ev) {
inputChanged (ev) { // eslint-disable-line class-methods-use-this
const height = ev.target.scrollHeight + 'px';
if (ev.target.style.height != height) {
ev.target.style.height = 'auto';
ev.target.style.height = height;
}
},
}
async clearMessages (ev) {
if (ev && ev.preventDefault) {
@ -808,52 +594,14 @@ const ChatBoxView = View.extend({
await this.model.clearMessages();
}
return this;
},
/**
* Insert a particular string value into the textarea of this chat box.
* @private
* @method _converse.ChatBoxView#insertIntoTextArea
* @param {string} value - The value to be inserted.
* @param {(boolean|string)} [replace] - Whether an existing value
* should be replaced. If set to `true`, the entire textarea will
* be replaced with the new value. If set to a string, then only
* that string will be replaced *if* a position is also specified.
* @param {integer} [position] - The end index of the string to be
* replaced with the new value.
*/
insertIntoTextArea (value, replace = false, correcting = false, position) {
const textarea = this.el.querySelector('.chat-textarea');
if (correcting) {
u.addClass('correcting', textarea);
} else {
u.removeClass('correcting', textarea);
}
if (replace) {
if (position && typeof replace == 'string') {
textarea.value = textarea.value.replace(new RegExp(replace, 'g'), (match, offset) =>
offset == position - replace.length ? value + ' ' : match
);
} else {
textarea.value = value;
}
} else {
let existing = textarea.value;
if (existing && existing[existing.length - 1] !== ' ') {
existing = existing + ' ';
}
textarea.value = existing + value + ' ';
}
this.updateCharCounter(textarea.value);
u.placeCaretAtEnd(textarea);
},
}
onPresenceChanged (item) {
const show = item.get('show');
const fullname = this.model.getDisplayName();
let text;
if (u.isVisible(this.el)) {
if (u.isVisible(this)) {
if (show === 'offline') {
text = __('%1$s has gone offline', fullname);
} else if (show === 'away') {
@ -865,7 +613,7 @@ const ChatBoxView = View.extend({
}
text && this.model.createMessage({ 'message': text, 'type': 'info' });
}
},
}
async close (ev) {
if (ev && ev.preventDefault) {
@ -881,7 +629,6 @@ const ChatBoxView = View.extend({
this.model.sendChatState();
}
await this.model.close(ev);
this.remove();
/**
* Triggered once a chatbox has been closed.
* @event _converse#chatBoxClosed
@ -890,154 +637,23 @@ const ChatBoxView = View.extend({
*/
api.trigger('chatBoxClosed', this);
return this;
},
emitBlurred (ev) {
if (this.el.contains(document.activeElement) || this.el.contains(ev.relatedTarget)) {
// Something else in this chatbox is still focused
return;
}
/**
* Triggered when the focus has been removed from a particular chat.
* @event _converse#chatBoxBlurred
* @type { _converse.ChatBoxView | _converse.ChatRoomView }
* @example _converse.api.listen.on('chatBoxBlurred', (view, event) => { ... });
*/
api.trigger('chatBoxBlurred', this, ev);
},
emitFocused (ev) {
if (this.el.contains(ev.relatedTarget)) {
// Something else in this chatbox was already focused
return;
}
/**
* Triggered when the focus has been moved to a particular chat.
* @event _converse#chatBoxFocused
* @type { _converse.ChatBoxView | _converse.ChatRoomView }
* @example _converse.api.listen.on('chatBoxFocused', (view, event) => { ... });
*/
api.trigger('chatBoxFocused', this, ev);
},
focus () {
const textarea_el = this.el.getElementsByClassName('chat-textarea')[0];
if (textarea_el && document.activeElement !== textarea_el) {
textarea_el.focus();
}
return this;
},
maybeFocus () {
api.settings.get('auto_focus') && this.focus();
},
hide () {
this.el.classList.add('hidden');
return this;
},
}
afterShown () {
this.model.clearUnreadMsgCounter();
this.model.setChatState(_converse.ACTIVE);
this.scrollDown();
this.maybeFocus();
},
show () {
if (this.model.get('hidden')) {
log.debug(`Not showing chat ${this.model.get('jid')} because it's set as hidden`);
return;
}
if (u.isVisible(this.el)) {
this.maybeFocus();
return;
}
if (api.settings.get('animate')) {
u.fadeIn(this.el, () => this.afterShown());
} else {
u.showElement(this.el);
this.afterShown();
}
},
}
showNewMessagesIndicator () {
u.showElement(this.el.querySelector('.new-msgs-indicator'));
},
hideNewMessagesIndicator () {
const new_msgs_indicator = this.el.querySelector('.new-msgs-indicator');
if (new_msgs_indicator !== null) {
new_msgs_indicator.classList.add('hidden');
}
},
/**
* Called when the chat content is scrolled up or down.
* We want to record when the user has scrolled away from
* the bottom, so that we don't automatically scroll away
* from what the user is reading when new messages are received.
*
* Don't call this method directly, instead, call `markScrolled`,
* which debounces this method by 100ms.
* @private
*/
_markScrolled: function (ev) {
let scrolled = true;
let scrollTop = null;
const is_at_bottom =
this.msgs_container.scrollTop + this.msgs_container.clientHeight >= this.msgs_container.scrollHeight - 62; // sigh...
if (is_at_bottom) {
scrolled = false;
this.onScrolledDown();
} else if (this.msgs_container.scrollTop === 0) {
/**
* Triggered once the chat's message area has been scrolled to the top
* @event _converse#chatBoxScrolledUp
* @property { _converse.ChatBoxView | _converse.ChatRoomView } view
* @example _converse.api.listen.on('chatBoxScrolledUp', obj => { ... });
*/
api.trigger('chatBoxScrolledUp', this);
} else {
scrollTop = ev.target.scrollTop;
}
u.safeSave(this.model, { scrolled, scrollTop });
},
u.showElement(this.querySelector('.new-msgs-indicator'));
}
viewUnreadMessages () {
this.model.save({ 'scrolled': false, 'scrollTop': null });
this.scrollDown();
},
onScrolledDown () {
this.hideNewMessagesIndicator();
if (!this.model.isHidden()) {
this.model.clearUnreadMsgCounter();
// Clear location hash if set to one of the messages in our history
const hash = window.location.hash;
hash && this.model.messages.get(hash.slice(1)) && _converse.router.history.navigate();
}
/**
* Triggered once the chat's message area has been scrolled down to the bottom.
* @event _converse#chatBoxScrolledDown
* @type {object}
* @property { _converse.ChatBox | _converse.ChatRoom } chatbox - The chat model
* @example _converse.api.listen.on('chatBoxScrolledDown', obj => { ... });
*/
api.trigger('chatBoxScrolledDown', { 'chatbox': this.model }); // TODO: clean up
},
onWindowStateChanged (state) {
if (state === 'visible') {
if (!this.model.isHidden() && this.model.get('num_unread', 0)) {
this.model.clearUnreadMsgCounter();
}
} else if (state === 'hidden') {
this.model.setChatState(_converse.INACTIVE, { 'silent': true });
this.model.sendChatState();
}
}
});
}
export default ChatBoxView;
api.elements.define('converse-chat', ChatView);

View File

@ -8,7 +8,7 @@ import "../chatview/index.js";
import ControlBoxMixin from './model.js';
import ControlBoxPane from './pane.js';
import ControlBoxToggle from './toggle.js';
import ControlBoxViewMixin from './view.js';
import ControlBoxView from './view.js';
import log from '@converse/headless/log';
import { LoginPanelModel, LoginPanel } from './loginpanel.js';
import { _converse, api, converse } from '@converse/headless/core';
@ -17,20 +17,6 @@ import controlbox_api from './api.js';
const u = converse.env.utils;
function onChatBoxViewsInitialized () {
_converse.chatboxes.on('add', item => {
if (item.get('type') === _converse.CONTROLBOX_TYPE) {
const views = _converse.chatboxviews;
const view = views.get(item.get('id'));
if (view) {
view.model = item;
view.initialize();
} else {
views.add(item.get('id'), new _converse.ControlBoxView({ model: item }));
}
}
});
}
function disconnect () {
/* Upon disconnection, set connected to `false`, so that if
@ -96,9 +82,6 @@ converse.plugins.add('converse-controlbox', {
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
api.settings.extend({
allow_logout: true,
allow_user_trust_override: true,
@ -111,15 +94,14 @@ converse.plugins.add('converse-controlbox', {
api.promises.add('controlBoxInitialized');
Object.assign(api, controlbox_api);
_converse.ControlBoxView = ControlBoxView;
_converse.ControlBox = _converse.ChatBox.extend(ControlBoxMixin);
_converse.ControlBoxView = _converse.ChatBoxView.extend(ControlBoxViewMixin);
_converse.LoginPanelModel = LoginPanelModel;
_converse.LoginPanel = LoginPanel;
_converse.ControlBoxPane = ControlBoxPane;
_converse.ControlBoxToggle = ControlBoxToggle;
/******************** Event Handlers ********************/
api.listen.on('chatBoxViewsInitialized', onChatBoxViewsInitialized);
api.listen.on('chatBoxesFetched', onChatBoxesFetched);
api.listen.on('cleanup', () => delete _converse.controlboxtoggle);
api.listen.on('clearSession', clearSession);

View File

@ -5,5 +5,9 @@ export default (o) => html`
<div class="chat-head controlbox-head">
${o.sticky_controlbox ? '' : html`<a class="chatbox-btn close-chatbox-button fa fa-times"></a>` }
</div>
<div class="controlbox-panes"></div>
</div>`;
<div class="controlbox-panes">
<converse-headlines-panel></converse-headlines-panel>
<converse-rooms-list></converse-rooms-list>
</div>
</div>
`;

View File

@ -1,8 +1,8 @@
import { html } from "lit-html";
import { api } from "@converse/headless/core";
import { __ } from 'i18n';
import { api } from "@converse/headless/core";
import { html } from "lit-html";
export default () => {
const i18n_toggle = api.connection.connected() ? __('Chat Contacts') : __('Toggle chat');
return html`<span class="toggle-feedback">${i18n_toggle}</span>`;
return html`<a id="toggle-controlbox" class="toggle-controlbox"><span class="toggle-feedback">${i18n_toggle}</span></a>`;
}

View File

@ -1,6 +1,5 @@
import log from "@converse/headless/log";
import tpl_controlbox_toggle from "./templates/toggle.js";
import { View } from "@converse/skeletor/src/view";
import { ElementView } from '@converse/skeletor/src/element.js';
import { _converse, api, converse } from "@converse/headless/core";
import { addControlBox } from './utils.js';
import { render } from 'lit-html';
@ -8,47 +7,39 @@ import { render } from 'lit-html';
const u = converse.env.utils;
const ControlBoxToggle = View.extend({
tagName: 'a',
className: 'toggle-controlbox hidden',
id: 'toggle-controlbox',
events: {
class ControlBoxToggle extends ElementView {
events = {
'click': 'onClick'
},
attributes: {
'href': "#"
},
}
initialize () {
_converse.chatboxviews.insertRowColumn(this.render().el);
api.waitUntil('initialized')
.then(this.render.bind(this))
.catch(e => log.fatal(e));
},
async initialize () {
await api.waitUntil('initialized');
this.render();
}
render () {
// We let the render method of ControlBoxView decide whether
// the ControlBox or the Toggle must be shown. This prevents
// artifacts (i.e. on page load the toggle is shown only to then
// seconds later be hidden in favor of the controlbox).
render(tpl_controlbox_toggle(), this.el);
render(tpl_controlbox_toggle(), this);
return this;
},
}
hide (callback) {
if (u.isVisible(this.el)) {
u.hideElement(this.el);
if (u.isVisible(this)) {
u.hideElement(this);
callback();
}
},
}
show (callback) {
if (!u.isVisible(this.el)) {
u.fadeIn(this.el, callback);
if (!u.isVisible(this)) {
u.fadeIn(this, callback);
}
},
}
showControlBox () {
showControlBox () { // eslint-disable-line class-methods-use-this
let controlbox = _converse.chatboxes.get('controlbox');
if (!controlbox) {
controlbox = addControlBox();
@ -58,7 +49,7 @@ const ControlBoxToggle = View.extend({
} else {
controlbox.trigger('show');
}
},
}
onClick (e) {
e.preventDefault();
@ -73,6 +64,8 @@ const ControlBoxToggle = View.extend({
this.showControlBox();
}
}
});
}
api.elements.define('converse-controlbox-toggle', ControlBoxToggle);
export default ControlBoxToggle;

View File

@ -1,38 +1,27 @@
import tpl_controlbox from './templates/controlbox.js';
import { render } from 'lit-html';
import { ElementView } from '@converse/skeletor/src/element.js';
import { _converse, api, converse } from '@converse/headless/core';
import { render } from 'lit-html';
const u = converse.env.utils;
/**
* Mixin which turns a ChatBoxView into a ControlBoxView.
*
* The ControlBox is the section of the chat that contains the open groupchats,
* bookmarks and roster.
*
* In `overlayed` `view_mode` it's a box like the chat boxes, in `fullscreen`
* `view_mode` it's a left-aligned sidebar.
* @mixin
*/
const ControlBoxViewMixin = {
tagName: 'div',
className: 'chatbox',
id: 'controlbox',
events: {
class ControlBoxView extends ElementView {
events = {
'click a.close-chatbox-button': 'close'
},
}
initialize () {
if (_converse.controlboxtoggle === undefined) {
_converse.controlboxtoggle = new _converse.ControlBoxToggle();
}
_converse.controlboxtoggle.el.insertAdjacentElement('afterend', this.el);
this.model = _converse.chatboxes.get(this.getAttribute('id'));
this.listenTo(this.model, 'change:connected', this.onConnected);
this.listenTo(this.model, 'destroy', this.hide);
this.listenTo(this.model, 'hide', this.hide);
// this.listenTo(this.model, 'hide', this.hide);
this.listenTo(this.model, 'show', this.show);
this.listenTo(this.model, 'change:closed', this.ensureClosedState);
this.render();
/**
* Triggered when the _converse.ControlBoxView has been initialized and therefore
@ -43,9 +32,11 @@ const ControlBoxViewMixin = {
* @example _converse.api.listen.on('controlBoxInitialized', view => { ... });
*/
api.trigger('controlBoxInitialized', this);
},
}
render () {
_converse.chatboxviews.add('controlbox', this);
if (this.model.get('connected')) {
if (this.model.get('closed') === undefined) {
this.model.set('closed', !api.settings.get('show_controlbox_by_default'));
@ -56,13 +47,7 @@ const ControlBoxViewMixin = {
'sticky_controlbox': api.settings.get('sticky_controlbox'),
...this.model.toJSON()
});
render(tpl_result, this.el);
if (!this.model.get('closed')) {
this.show();
} else {
this.hide();
}
render(tpl_result, this);
const connection = _converse?.connection || {};
if (!connection.connected || !connection.authenticated || connection.disconnecting) {
@ -71,29 +56,29 @@ const ControlBoxViewMixin = {
this.renderControlBoxPane();
}
return this;
},
}
onConnected () {
if (this.model.get('connected')) {
this.render();
}
},
}
renderLoginPanel () {
this.el.classList.add('logged-out');
this.classList.add('logged-out');
if (this.loginpanel) {
this.loginpanel.render();
} else {
this.loginpanel = new _converse.LoginPanel({
'model': new _converse.LoginPanelModel()
});
const panes = this.el.querySelector('.controlbox-panes');
const panes = this.querySelector('.controlbox-panes');
panes.innerHTML = '';
panes.appendChild(this.loginpanel.render().el);
}
this.loginpanel.initPopovers();
return this;
},
}
/**
* Renders the "Contacts" panel of the controlbox.
@ -109,12 +94,12 @@ const ControlBoxViewMixin = {
if (this.controlbox_pane && u.isVisible(this.controlbox_pane.el)) {
return;
}
this.el.classList.remove('logged-out');
this.classList.remove('logged-out');
this.controlbox_pane = new _converse.ControlBoxPane();
this.el
this
.querySelector('.controlbox-panes')
.insertAdjacentElement('afterBegin', this.controlbox_pane.el);
},
}
async close (ev) {
if (ev && ev.preventDefault) {
@ -143,48 +128,42 @@ const ControlBoxViewMixin = {
}
api.trigger('controlBoxClosed', this);
return this;
},
ensureClosedState () {
if (this.model.get('closed')) {
this.hide();
} else {
this.show();
}
},
}
hide (callback) {
if (api.settings.get('sticky_controlbox')) {
return;
}
u.addClass('hidden', this.el);
u.addClass('hidden', this);
api.trigger('chatBoxClosed', this);
if (!api.connection.connected()) {
_converse.controlboxtoggle.render();
}
_converse.controlboxtoggle.show(callback);
return this;
},
}
onControlBoxToggleHidden () {
this.model.set('closed', false);
this.el.classList.remove('hidden');
this.classList.remove('hidden');
/**
* Triggered once the controlbox has been opened
* @event _converse#controlBoxOpened
* @type {_converse.ControlBox}
*/
api.trigger('controlBoxOpened', this);
},
}
show () {
_converse.controlboxtoggle.hide(() => this.onControlBoxToggleHidden());
return this;
},
}
showHelpMessages () {
showHelpMessages () { // eslint-disable-line class-methods-use-this
return;
}
};
}
export default ControlBoxViewMixin;
api.elements.define('converse-controlbox', ControlBoxView);
export default ControlBoxView;

View File

@ -126,6 +126,8 @@ converse.plugins.add('converse-dragresize', {
});
Object.assign(_converse.ChatBoxView.prototype, DragResizableMixin);
Object.assign(_converse.ChatRoomView.prototype, DragResizableMixin);
Object.assign(_converse.ControlBoxView.prototype, DragResizableMixin);
/************************ BEGIN Event Handlers ************************/
function registerGlobalEventHandlers () {

View File

@ -11,7 +11,7 @@ const DragResizableMixin = {
// Determine and store the default box size.
// We need this information for the drag-resizing feature.
const flyout = this.el.querySelector('.box-flyout');
const flyout = this.querySelector('.box-flyout');
const style = window.getComputedStyle(flyout);
if (this.model.get('height') === undefined) {
@ -66,7 +66,7 @@ const DragResizableMixin = {
// If a custom width is applied (due to drag-resizing),
// then we need to set the width of the .chatbox element as well.
if (this.model.get('width')) {
this.el.style.width = this.model.get('width');
this.style.width = this.model.get('width');
}
},
@ -83,7 +83,7 @@ const DragResizableMixin = {
} else {
height = '';
}
const flyout_el = this.el.querySelector('.box-flyout');
const flyout_el = this.querySelector('.box-flyout');
if (flyout_el !== null) {
flyout_el.style.height = height;
}
@ -95,8 +95,8 @@ const DragResizableMixin = {
} else {
width = '';
}
this.el.style.width = width;
const flyout_el = this.el.querySelector('.box-flyout');
this.style.width = width;
const flyout_el = this.querySelector('.box-flyout');
if (flyout_el !== null) {
flyout_el.style.width = width;
}
@ -124,7 +124,7 @@ const DragResizableMixin = {
}
ev.preventDefault();
// Record element attributes for mouseMove().
const flyout = this.el.querySelector('.box-flyout'),
const flyout = this.querySelector('.box-flyout'),
style = window.getComputedStyle(flyout);
this.height = parseInt(style.height.replace(/px$/, ''), 10);
_converse.resizing = {
@ -147,8 +147,8 @@ const DragResizableMixin = {
return true;
}
ev.preventDefault();
const flyout = this.el.querySelector('.box-flyout'),
style = window.getComputedStyle(flyout);
const flyout = this.querySelector('.box-flyout');
const style = window.getComputedStyle(flyout);
this.width = parseInt(style.width.replace(/px$/, ''), 10);
_converse.resizing = {
'chatbox': this,

View File

@ -23,8 +23,8 @@ export function applyDragResistance (value, default_value) {
return value;
}
export function renderDragResizeHandles (_converse, view) {
const flyout = view.el.querySelector('.box-flyout');
export function renderDragResizeHandles (_converse, el) {
const flyout = el.querySelector('.box-flyout');
const div = document.createElement('div');
render(tpl_dragresize(), div);
flyout.insertBefore(div, flyout.firstChild);

View File

@ -5,7 +5,7 @@
*/
import '../chatview/index.js';
import HeadlinesBoxViewMixin from './view.js';
import { HeadlinesPanelMixin, HeadlinesPanelView } from './panel.js';
import { HeadlinesPanelMixin, HeadlinesPanel} from './panel.js';
import { _converse, api, converse } from '@converse/headless/core';
function onChatBoxViewsInitialized () {
@ -45,7 +45,7 @@ converse.plugins.add('converse-headlines-view', {
*/
_converse.ControlBoxView && Object.assign(_converse.ControlBoxView.prototype, HeadlinesPanelMixin);
_converse.HeadlinesBoxView = _converse.ChatBoxView.extend(HeadlinesBoxViewMixin);
_converse.HeadlinesPanel = HeadlinesPanelView;
_converse.HeadlinesPanel = HeadlinesPanel;
api.listen.on('chatBoxViewsInitialized', onChatBoxViewsInitialized);
}

View File

@ -1,5 +1,5 @@
import tpl_headline_panel from './templates/panel.js';
import { View } from '@converse/skeletor/src/view.js';
import { ElementView } from '@converse/skeletor/src/element.js';
import { __ } from 'i18n';
import { _converse, api, converse } from '@converse/headless/core';
@ -11,22 +11,20 @@ const u = converse.env.utils;
* @namespace _converse.HeadlinesPanel
* @memberOf _converse
*/
export const HeadlinesPanelView = View.extend({
tagName: 'div',
className: 'controlbox-section',
id: 'headline',
events: {
export class HeadlinesPanel extends ElementView {
tagName = 'div'
className = 'controlbox-section'
id = 'headline'
events = {
'click .open-headline': 'openHeadline'
},
}
initialize () {
this.listenTo(this.model, 'add', this.renderIfHeadline);
this.listenTo(this.model, 'remove', this.renderIfHeadline);
this.listenTo(this.model, 'destroy', this.renderIfHeadline);
this.render();
this.insertIntoDOM();
},
}
toHTML () {
return tpl_headline_panel({
@ -34,24 +32,21 @@ export const HeadlinesPanelView = View.extend({
'headlineboxes': this.model.filter(m => m.get('type') === _converse.HEADLINES_TYPE),
'open_title': __('Click to open this server message')
});
},
}
renderIfHeadline (model) {
return model && model.get('type') === _converse.HEADLINES_TYPE && this.render();
},
}
openHeadline (ev) {
openHeadline (ev) { // eslint-disable-line class-methods-use-this
ev.preventDefault();
const jid = ev.target.getAttribute('data-headline-jid');
const chat = _converse.chatboxes.get(jid);
chat.maybeShow(true);
},
insertIntoDOM () {
const view = _converse.chatboxviews.get('controlbox');
view && view.el.querySelector('.controlbox-pane').insertAdjacentElement('beforeEnd', this.el);
}
});
}
api.elements.define('converse-headlines-panel', HeadlinesPanel);
/**
* Mixin for the {@link _converse.ControlBoxView } which add support for

View File

@ -13,8 +13,14 @@ const HeadlinesBoxViewMixin = {
},
async initialize () {
const jid = this.getAttribute('jid');
_converse.chatboxviews.add(jid, this);
this.model = _converse.chatboxes.get(jid);
this.initDebounced();
api.listen.on('windowStateChanged', this.onWindowStateChanged);
this.model.disable_mam = true; // Don't do MAM queries for this box
this.listenTo(this.model, 'change:hidden', m => (m.get('hidden') ? this.hide() : this.show()));
this.listenTo(this.model, 'destroy', this.remove);

View File

@ -125,7 +125,8 @@ converse.plugins.add('converse-minimize', {
/************************ BEGIN Event Handlers ************************/
api.listen.on('chatBoxInsertedIntoDOM', view => _converse.minimize.trimChats(view));
api.listen.on('chatBoxViewInitialized', view => _converse.minimize.trimChats(view));
api.listen.on('chatRoomViewInitialized', view => _converse.minimize.trimChats(view));
api.listen.on('connected', () => initMinimizedChats());
api.listen.on('controlBoxOpened', view => _converse.minimize.trimChats(view));
api.listen.on('chatBoxViewInitialized', v => v.listenTo(v.model, 'change:minimized', v.onMinimizedChanged));

View File

@ -66,7 +66,6 @@ export const minimizableChatBoxView = {
this.model.set({ 'scroll': this.content.scrollTop });
}
this.model.setChatState(_converse.INACTIVE);
this.hide();
/**
* Triggered when a previously maximized chat gets Minimized
* @event _converse#chatBoxMinimized

View File

@ -6,28 +6,24 @@ const u = converse.env.utils;
function getChatBoxWidth (view) {
if (view.model.get('id') === 'controlbox') {
const controlbox = view.model;
// We return the width of the controlbox or its toggle,
// depending on which is visible.
if (u.isVisible(controlbox.el)) {
return u.getOuterWidth(controlbox.el, true);
if (u.isVisible(view)) {
return u.getOuterWidth(view, true);
} else {
return u.getOuterWidth(_converse.controlboxtoggle.el, true);
}
} else if (!view.model.get('minimized') && u.isVisible(view.el)) {
return u.getOuterWidth(view.el, true);
} else if (!view.model.get('minimized') && u.isVisible(view)) {
return u.getOuterWidth(view, true);
}
return 0;
}
function getShownChats () {
return _converse.chatboxviews.filter((view) =>
return _converse.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.
!view.model.get('minimized') &&
!view.model.get('closed') &&
u.isVisible(view.el)
// 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)
);
}

View File

@ -7,7 +7,7 @@
import '../../components/muc-sidebar';
import '../chatview/index.js';
import '../modal.js';
import ChatRoomViewMixin from './muc.js';
import MUCView from './muc.js';
import MUCConfigForm from './config-form.js';
import MUCPasswordForm from './password-form.js';
import log from '@converse/headless/log';
@ -60,18 +60,10 @@ function fetchAndSetMUCDomain (controlboxview) {
}
}
function openChatRoomFromURIClicked (ev) {
ev.preventDefault();
api.rooms.open(ev.target.href);
}
async function addView (model) {
const views = _converse.chatboxviews;
if (!views.get(model.get('id')) && model.get('type') === _converse.CHATROOMS_TYPE && model.isValid()) {
await model.initialized;
return views.add(model.get('id'), new _converse.ChatRoomView({ model }));
}
}
// function openChatRoomFromURIClicked (ev) {
// ev.preventDefault();
// api.rooms.open(ev.target.href);
// }
converse.plugins.add('converse-muc-views', {
/* Dependencies are other plugins which might be
@ -129,7 +121,7 @@ converse.plugins.add('converse-muc-views', {
_converse.MUCConfigForm = MUCConfigForm;
_converse.MUCPasswordForm = MUCPasswordForm;
_converse.ChatRoomView = _converse.ChatBoxView.extend(ChatRoomViewMixin);
_converse.ChatRoomView = MUCView;
_converse.RoomsPanel = RoomsPanel;
_converse.ControlBoxView && Object.assign(_converse.ControlBoxView.prototype, RoomsPanelViewMixin);
@ -137,8 +129,11 @@ converse.plugins.add('converse-muc-views', {
/************************ BEGIN Event Handlers ************************/
api.listen.on('chatBoxViewsInitialized', () => {
_converse.chatboxviews.delegate('click', 'a.open-chatroom', openChatRoomFromURIClicked);
_converse.chatboxes.on('add', addView);
// FIXME: Find a new way to implement this
// _converse.chatboxviews.delegate('click', 'a.open-chatroom', openChatRoomFromURIClicked);
// TODO: Remove
// _converse.chatboxes.on('add', addView);
});
api.listen.on('clearSession', () => {

View File

@ -1,6 +1,7 @@
import './config-form.js';
import './password-form.js';
import 'shared/autocomplete/index.js';
import BaseChatView from 'shared/chatview.js';
import MUCInviteModal from 'modals/muc-invite.js';
import ModeratorToolsModal from 'modals/moderator-tools.js';
import OccupantModal from 'modals/occupant.js';
@ -11,9 +12,9 @@ import tpl_chatroom_head from 'templates/chatroom_head.js';
import tpl_muc_bottom_panel from 'templates/muc_bottom_panel.js';
import tpl_muc_destroyed from 'templates/muc_destroyed.js';
import tpl_muc_disconnect from 'templates/muc_disconnect.js';
import { $pres, Strophe } from 'strophe.js/src/strophe';
import tpl_muc_nickname_form from 'templates/muc_nickname_form.js';
import tpl_spinner from 'templates/spinner.js';
import { $pres, Strophe } from 'strophe.js/src/strophe';
import { Model } from '@converse/skeletor/src/model.js';
import { __ } from 'i18n';
import { _converse, api, converse } from '@converse/headless/core';
@ -49,12 +50,12 @@ const COMMAND_TO_AFFILIATION = {
* @namespace _converse.ChatRoomView
* @memberOf _converse
*/
const ChatRoomViewMixin = {
length: 300,
tagName: 'div',
className: 'chatbox chatroom hidden',
is_chatroom: true,
events: {
export default class MUCView extends BaseChatView {
length = 300
tagName = 'div'
className = 'chatbox chatroom hidden'
is_chatroom = true
events = {
'click .chatbox-navback': 'showControlBox',
'click .hide-occupants': 'hideOccupants',
'click .new-msgs-indicator': 'viewUnreadMessages',
@ -71,21 +72,25 @@ const ChatRoomViewMixin = {
'mousedown .dragresize-occupants-left': 'onStartResizeOccupants',
'paste .chat-textarea': 'onPaste',
'submit .muc-nickname-form': 'submitNickname'
},
}
async initialize () {
const jid = this.getAttribute('jid');
_converse.chatboxviews.add(jid, this);
this.model = _converse.chatboxes.get(jid);
this.initDebounced();
api.listen.on('windowStateChanged', this.onWindowStateChanged);
this.listenTo(
this.model,
'change',
debounce(() => this.renderHeading(), 250)
);
this.listenTo(this.model, 'change:composing_spoiler', this.renderMessageForm);
this.listenTo(this.model, 'change:hidden', m => (m.get('hidden') ? this.hide() : this.show()));
this.listenTo(this.model, 'change:hidden_occupants', this.onSidebarToggle);
this.listenTo(this.model, 'configurationNeeded', this.getAndRenderConfigurationForm);
this.listenTo(this.model, 'destroy', this.hide);
this.listenTo(this.model, 'show', this.show);
this.listenTo(this.model.features, 'change:moderated', this.renderBottomPanel);
this.listenTo(this.model.features, 'change:open', this.renderHeading);
@ -115,7 +120,6 @@ const ChatRoomViewMixin = {
this.listenTo(this.model.occupants, 'remove', this.onOccupantRemoved);
this.renderChatContent();
this.insertIntoDOM();
// Register later due to await
const user_settings = await _converse.api.user.settings.getModel();
this.listenTo(user_settings, 'change:mucs_with_hidden_subject', this.renderHeading);
@ -129,11 +133,11 @@ const ChatRoomViewMixin = {
* @example _converse.api.listen.on('chatRoomViewInitialized', view => { ... });
*/
api.trigger('chatRoomViewInitialized', this);
},
}
async render () {
const sidebar_hidden = !this.shouldShowSidebar();
this.el.setAttribute('id', this.model.get('box_id'));
this.setAttribute('id', this.model.get('box_id'));
render(
tpl_chatroom({
sidebar_hidden,
@ -146,13 +150,13 @@ const ChatRoomViewMixin = {
'muc_show_logs_before_join': api.settings.get('muc_show_logs_before_join'),
'show_send_button': _converse.show_send_button
}),
this.el
this
);
this.notifications = this.el.querySelector('.chat-content__notifications');
this.content = this.el.querySelector('.chat-content');
this.msgs_container = this.el.querySelector('.chat-content__messages');
this.help_container = this.el.querySelector('.chat-content__help');
this.notifications = this.querySelector('.chat-content__notifications');
this.content = this.querySelector('.chat-content');
this.msgs_container = this.querySelector('.chat-content__messages');
this.help_container = this.querySelector('.chat-content__help');
this.renderBottomPanel();
if (
@ -166,7 +170,7 @@ const ChatRoomViewMixin = {
// Otherwise e.g. this.notifications is not yet defined when accessed elsewhere.
await this.renderHeading();
!this.model.get('hidden') && this.show();
},
}
getNotifications () {
const actors_per_state = this.model.notifications.toJSON();
@ -240,7 +244,7 @@ const ChatRoomViewMixin = {
}
return result;
}, '');
},
}
getHelpMessages () {
const setting = api.settings.get('muc_disable_slash_commands');
@ -269,7 +273,7 @@ const ChatRoomViewMixin = {
]
.filter(line => disabled_commands.every(c => !line.startsWith(c + '<', 9)))
.filter(line => this.getAllowedCommands().some(c => line.startsWith(c + '<', 9)));
},
}
/**
* Renders the MUC heading if any relevant attributes have changed.
@ -279,11 +283,11 @@ const ChatRoomViewMixin = {
*/
async renderHeading () {
const tpl = await this.generateHeadingTemplate();
render(tpl, this.el.querySelector('.chat-head-chatroom'));
},
render(tpl, this.querySelector('.chat-head-chatroom'));
}
renderBottomPanel () {
const container = this.el.querySelector('.bottom-panel');
const container = this.querySelector('.bottom-panel');
const entered = this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED;
const can_edit = entered && !(this.model.features.get('moderated') && this.model.getOwnRole() === 'visitor');
render(tpl_muc_bottom_panel({ can_edit, entered }), container);
@ -291,18 +295,18 @@ const ChatRoomViewMixin = {
this.renderMessageForm();
this.initMentionAutoComplete();
}
},
}
onStartResizeOccupants (ev) {
this.resizing = true;
this.el.addEventListener('mousemove', this.onMouseMove);
this.el.addEventListener('mouseup', this.onMouseUp);
this.addEventListener('mousemove', this.onMouseMove);
this.addEventListener('mouseup', this.onMouseUp);
const sidebar_el = this.el.querySelector('converse-muc-sidebar');
const sidebar_el = this.querySelector('converse-muc-sidebar');
const style = window.getComputedStyle(sidebar_el);
this.width = parseInt(style.width.replace(/px$/, ''), 10);
this.prev_pageX = ev.pageX;
},
}
onMouseMove (ev) {
if (this.resizing) {
@ -311,24 +315,24 @@ const ChatRoomViewMixin = {
this.resizeSidebarView(delta, ev.pageX);
this.prev_pageX = ev.pageX;
}
},
}
onMouseUp (ev) {
if (this.resizing) {
ev.preventDefault();
this.resizing = false;
this.el.removeEventListener('mousemove', this.onMouseMove);
this.el.removeEventListener('mouseup', this.onMouseUp);
const sidebar_el = this.el.querySelector('converse-muc-sidebar');
this.removeEventListener('mousemove', this.onMouseMove);
this.removeEventListener('mouseup', this.onMouseUp);
const sidebar_el = this.querySelector('converse-muc-sidebar');
const element_position = sidebar_el.getBoundingClientRect();
const occupants_width = this.calculateSidebarWidth(element_position, 0);
const attrs = { occupants_width };
_converse.connection.connected ? this.model.save(attrs) : this.model.set(attrs);
}
},
}
resizeSidebarView (delta, current_mouse_position) {
const sidebar_el = this.el.querySelector('converse-muc-sidebar');
const sidebar_el = this.querySelector('converse-muc-sidebar');
const element_position = sidebar_el.getBoundingClientRect();
if (this.is_minimum) {
this.is_minimum = element_position.left < current_mouse_position;
@ -338,11 +342,11 @@ const ChatRoomViewMixin = {
const occupants_width = this.calculateSidebarWidth(element_position, delta);
sidebar_el.style.flex = '0 0 ' + occupants_width + 'px';
}
},
}
calculateSidebarWidth (element_position, delta) {
let occupants_width = element_position.width + delta;
const room_width = this.el.clientWidth;
const room_width = this.clientWidth;
// keeping display in boundaries
if (occupants_width < room_width * 0.2) {
// set pixel to 20% width
@ -361,13 +365,13 @@ const ChatRoomViewMixin = {
this.is_minimum = false;
}
return occupants_width;
},
}
getAutoCompleteList () {
return this.model.getAllKnownNicknames().map(nick => ({ 'label': nick, 'value': `@${nick}` }));
},
}
getAutoCompleteListItem (text, input) {
getAutoCompleteListItem (text, input) { // eslint-disable-line class-methods-use-this
input = input.trim();
const element = document.createElement('li');
element.setAttribute('aria-selected', 'false');
@ -401,10 +405,10 @@ const ChatRoomViewMixin = {
});
return element;
},
}
initMentionAutoComplete () {
this.mention_auto_complete = new _converse.AutoComplete(this.el, {
this.mention_auto_complete = new _converse.AutoComplete(this, {
'auto_first': true,
'auto_evaluate': false,
'min_chars': api.settings.get('muc_mention_autocomplete_min_chars'),
@ -419,7 +423,7 @@ const ChatRoomViewMixin = {
'item': this.getAutoCompleteListItem
});
this.mention_auto_complete.on('suggestion-box-selectcomplete', () => (this.auto_completing = false));
},
}
/**
* Get the nickname value from the form and then join the groupchat with it.
@ -431,19 +435,19 @@ const ChatRoomViewMixin = {
ev.preventDefault();
const nick = ev.target.nick.value.trim();
nick && this.model.join(nick);
},
}
onKeyDown (ev) {
if (this.mention_auto_complete.onKeyDown(ev)) {
return;
}
return _converse.ChatBoxView.prototype.onKeyDown.call(this, ev);
},
}
onKeyUp (ev) {
this.mention_auto_complete.evaluate(ev);
return _converse.ChatBoxView.prototype.onKeyUp.call(this, ev);
},
}
async onMessageRetractButtonClicked (message) {
const retraction_warning = __(
@ -480,7 +484,7 @@ const ChatRoomViewMixin = {
const err_msg = __(`Sorry, you're not allowed to retract this message`);
api.alert('error', __('Error'), err_msg);
}
},
}
/**
* Retract someone else's message in this groupchat.
@ -501,7 +505,7 @@ const ChatRoomViewMixin = {
log(err_msg, Strophe.LogLevel.WARN);
log(result, Strophe.LogLevel.WARN);
}
},
}
showModeratorToolsModal (affiliation) {
if (!this.verifyRoles(['moderator'])) {
@ -515,48 +519,48 @@ const ChatRoomViewMixin = {
modal = api.modal.create(ModeratorToolsModal, { model, _converse, 'chatroomview': this });
}
modal.show();
},
}
showRoomDetailsModal (ev) {
ev.preventDefault();
api.modal.show(RoomDetailsModal, { 'model': this.model }, ev);
},
}
showOccupantDetailsModal (ev, message) {
showOccupantDetailsModal (ev, message) { // eslint-disable-line class-methods-use-this
ev.preventDefault();
api.modal.show(OccupantModal, { 'model': message.occupant }, ev);
},
}
showChatStateNotification (message) {
if (message.get('sender') === 'me') {
return;
}
return _converse.ChatBoxView.prototype.showChatStateNotification.apply(this, arguments);
},
}
shouldShowSidebar () {
return (
!this.model.get('hidden_occupants') &&
this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED
);
},
}
onSidebarToggle () {
this.renderToolbar();
this.el.querySelector('.occupants')?.setVisibility();
},
this.querySelector('.occupants')?.setVisibility();
}
onOccupantAffiliationChanged (occupant) {
if (occupant.get('jid') === _converse.bare_jid) {
this.renderHeading();
}
},
}
onOccupantRoleChanged (occupant) {
if (occupant.get('jid') === _converse.bare_jid) {
this.renderBottomPanel();
}
},
}
/**
* Returns a list of objects which represent buttons for the groupchat header.
@ -653,7 +657,7 @@ const ChatRoomViewMixin = {
});
}
return _converse.api.hook('getHeadingButtons', this, buttons);
},
}
/**
* Returns the groupchat heading TemplateResult to be rendered.
@ -674,16 +678,16 @@ const ChatRoomViewMixin = {
'title': this.model.getDisplayName()
})
);
},
}
toggleTopic () {
this.model.toggleSubjectHiddenState();
},
}
showInviteModal (ev) {
ev.preventDefault();
api.modal.show(MUCInviteModal, { 'model': new Model(), 'chatroomview': this }, ev);
},
}
/**
* Callback method that gets called after the chat has become visible.
@ -696,7 +700,7 @@ const ChatRoomViewMixin = {
// This is instead done in `onConnectionStatusChanged` below.
this.model.clearUnreadMsgCounter();
this.scrollDown();
},
}
onConnectionStatusChanged () {
const conn_status = this.model.session.get('connection_status');
@ -715,7 +719,7 @@ const ChatRoomViewMixin = {
} else if (conn_status === converse.ROOMSTATUS.DESTROYED) {
this.showDestroyedMessage();
}
},
}
getToolbarOptions () {
return Object.assign(_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), {
@ -723,7 +727,7 @@ const ChatRoomViewMixin = {
'label_hide_occupants': __('Hide the list of participants'),
'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants
});
},
}
/**
* Closes this chat box, which implies leaving the groupchat as well.
@ -731,12 +735,11 @@ const ChatRoomViewMixin = {
* @method _converse.ChatRoomView#close
*/
close () {
this.hide();
if (_converse.router.history.getFragment() === 'converse/room?jid=' + this.model.get('jid')) {
_converse.router.navigate('');
}
return _converse.ChatBoxView.prototype.close.apply(this, arguments);
},
}
/**
* Hide the right sidebar containing the chat occupants.
@ -750,7 +753,7 @@ const ChatRoomViewMixin = {
}
this.model.save({ 'hidden_occupants': true });
this.scrollDown();
},
}
verifyRoles (roles, occupant, show_error = true) {
if (!Array.isArray(roles)) {
@ -771,7 +774,7 @@ const ChatRoomViewMixin = {
this.model.createMessage({ message, 'type': 'error' });
}
return false;
},
}
verifyAffiliations (affiliations, occupant, show_error = true) {
if (!Array.isArray(affiliations)) {
@ -792,7 +795,7 @@ const ChatRoomViewMixin = {
this.model.createMessage({ message, 'type': 'error' });
}
return false;
},
}
validateRoleOrAffiliationChangeArgs (command, args) {
if (!args) {
@ -804,7 +807,7 @@ const ChatRoomViewMixin = {
return false;
}
return true;
},
}
getNickOrJIDFromCommandArgs (args) {
if (u.isValidJID(args.trim())) {
@ -832,7 +835,7 @@ const ChatRoomViewMixin = {
return;
}
return nick_or_jid;
},
}
setAffiliation (command, args, required_affiliations) {
const affiliation = COMMAND_TO_AFFILIATION[command];
@ -874,11 +877,11 @@ const ChatRoomViewMixin = {
.setAffiliation(affiliation, [attrs])
.then(() => this.model.occupants.fetchMembers())
.catch(err => this.onCommandError(err));
},
}
getReason (args) {
getReason (args) { // eslint-disable-line class-methods-use-this
return args.includes(',') ? args.slice(args.indexOf(',') + 1).trim() : null;
},
}
setRole (command, args, required_affiliations = [], required_roles = []) {
/* Check that a command to change a groupchat user's role or
@ -903,7 +906,7 @@ const ChatRoomViewMixin = {
const occupant = this.model.getOccupant(nick_or_jid);
this.model.setRole(occupant, role, reason, undefined, this.onCommandError.bind(this));
return true;
},
}
onCommandError (err) {
log.fatal(err);
@ -912,7 +915,7 @@ const ChatRoomViewMixin = {
' ' +
__("Check your browser's developer console for details.");
this.model.createMessage({ message, 'type': 'error' });
},
}
getAllowedCommands () {
let allowed_commands = ['clear', 'help', 'me', 'nick', 'register'];
@ -937,7 +940,7 @@ const ChatRoomViewMixin = {
} else {
return allowed_commands;
}
},
}
async destroy () {
const messages = [__('Are you sure you want to destroy this groupchat?')];
@ -968,7 +971,7 @@ const ChatRoomViewMixin = {
} catch (e) {
log.error(e);
}
},
}
parseMessageForCommands (text) {
if (
@ -1088,7 +1091,7 @@ const ChatRoomViewMixin = {
return _converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments);
}
return true;
},
}
/**
* Renders a form given an IQ stanza containing the current
@ -1107,11 +1110,11 @@ const ChatRoomViewMixin = {
'model': this.model,
'chatroomview': this
});
const container_el = this.el.querySelector('.chatroom-body');
const container_el = this.querySelector('.chatroom-body');
container_el.insertAdjacentElement('beforeend', this.config_form.el);
}
u.showElement(this.config_form.el);
},
}
/**
* Renders a form which allows the user to choose theirnickname.
@ -1122,24 +1125,24 @@ const ChatRoomViewMixin = {
const tpl_result = tpl_muc_nickname_form(this.model.toJSON());
if (api.settings.get('muc_show_logs_before_join')) {
this.hideSpinner();
u.showElement(this.el.querySelector('.chat-area'));
const container = this.el.querySelector('.muc-bottom-panel');
u.showElement(this.querySelector('.chat-area'));
const container = this.querySelector('.muc-bottom-panel');
render(tpl_result, container);
u.addClass('muc-bottom-panel--nickname', container);
} else {
const form = this.el.querySelector('.muc-nickname-form');
const form = this.querySelector('.muc-nickname-form');
const form_el = u.getElementFromTemplateResult(tpl_result);
if (form) {
sizzle('.spinner', this.el).forEach(u.removeElement);
sizzle('.spinner', this).forEach(u.removeElement);
form.outerHTML = form_el.outerHTML;
} else {
this.hideChatRoomContents();
const container = this.el.querySelector('.chatroom-body');
const container = this.querySelector('.chatroom-body');
container.insertAdjacentElement('beforeend', form_el);
}
}
u.safeSave(this.model.session, { 'connection_status': converse.ROOMSTATUS.NICKNAME_REQUIRED });
},
}
/**
* Remove the configuration form without submitting and return to the chat view.
@ -1147,9 +1150,9 @@ const ChatRoomViewMixin = {
* @method _converse.ChatRoomView#closeForm
*/
closeForm () {
sizzle('.chatroom-form-container', this.el).forEach(e => u.addClass('hidden', e));
sizzle('.chatroom-form-container', this).forEach(e => u.addClass('hidden', e));
this.renderAfterTransition();
},
}
/**
* Start the process of configuring a groupchat, either by
@ -1175,14 +1178,14 @@ const ChatRoomViewMixin = {
} else {
this.closeForm();
}
},
}
hideChatRoomContents () {
const container_el = this.el.querySelector('.chatroom-body');
const container_el = this.querySelector('.chatroom-body');
if (container_el !== null) {
[].forEach.call(container_el.children, child => child.classList.add('hidden'));
}
},
}
renderPasswordForm () {
this.hideChatRoomContents();
@ -1196,19 +1199,19 @@ const ChatRoomViewMixin = {
}),
'chatroomview': this
});
const container_el = this.el.querySelector('.chatroom-body');
const container_el = this.querySelector('.chatroom-body');
container_el.insertAdjacentElement('beforeend', this.password_form.el);
} else {
this.password_form.model.set('validation_message', message);
}
u.showElement(this.password_form.el);
this.model.session.save('connection_status', converse.ROOMSTATUS.PASSWORD_REQUIRED);
},
}
showDestroyedMessage () {
u.hideElement(this.el.querySelector('.chat-area'));
u.hideElement(this.el.querySelector('.occupants'));
sizzle('.spinner', this.el).forEach(u.removeElement);
u.hideElement(this.querySelector('.chat-area'));
u.hideElement(this.querySelector('.occupants'));
sizzle('.spinner', this).forEach(u.removeElement);
const reason = this.model.get('destroyed_reason');
const moved_jid = this.model.get('moved_jid');
@ -1216,7 +1219,7 @@ const ChatRoomViewMixin = {
'destroyed_reason': undefined,
'moved_jid': undefined
});
const container = this.el.querySelector('.disconnect-container');
const container = this.querySelector('.disconnect-container');
render(tpl_muc_destroyed(moved_jid, reason), container);
const switch_el = container.querySelector('a.switch-chat');
if (switch_el) {
@ -1228,16 +1231,16 @@ const ChatRoomViewMixin = {
});
}
u.showElement(container);
},
}
showDisconnectMessage () {
const message = this.model.get('disconnection_message');
if (!message) {
return;
}
u.hideElement(this.el.querySelector('.chat-area'));
u.hideElement(this.el.querySelector('.occupants'));
sizzle('.spinner', this.el).forEach(u.removeElement);
u.hideElement(this.querySelector('.chat-area'));
u.hideElement(this.querySelector('.occupants'));
sizzle('.spinner', this).forEach(u.removeElement);
const messages = [message];
const actor = this.model.get('disconnection_actor');
@ -1253,17 +1256,17 @@ const ChatRoomViewMixin = {
'disconnection_reason': undefined,
'disconnection_actor': undefined
});
const container = this.el.querySelector('.disconnect-container');
const container = this.querySelector('.disconnect-container');
render(tpl_muc_disconnect(messages), container);
u.showElement(container);
},
}
onOccupantAdded (occupant) {
if (occupant.get('jid') === _converse.bare_jid) {
this.renderHeading();
this.renderBottomPanel();
}
},
}
/**
* Working backwards, get today's most recent join/leave notification
@ -1273,7 +1276,7 @@ const ChatRoomViewMixin = {
* @param {HTMLElement} el
* @param {string} nick
*/
getPreviousJoinOrLeaveNotification (el, nick) {
getPreviousJoinOrLeaveNotification (el, nick) { // eslint-disable-line class-methods-use-this
const today = new Date().toISOString().split('T')[0];
while (el !== null) {
if (!el.classList.contains('chat-info')) {
@ -1291,7 +1294,7 @@ const ChatRoomViewMixin = {
}
el = el.previousElementSibling;
}
},
}
/**
* Rerender the groupchat after some kind of transition. For
@ -1308,18 +1311,18 @@ const ChatRoomViewMixin = {
this.renderPasswordForm();
} else if (conn_status == converse.ROOMSTATUS.ENTERED) {
this.hideChatRoomContents();
u.showElement(this.el.querySelector('.chat-area'));
this.el.querySelector('.occupants')?.setVisibility();
u.showElement(this.querySelector('.chat-area'));
this.querySelector('.occupants')?.setVisibility();
this.scrollDown();
}
},
}
showSpinner () {
sizzle('.spinner', this.el).forEach(u.removeElement);
sizzle('.spinner', this).forEach(u.removeElement);
this.hideChatRoomContents();
const container_el = this.el.querySelector('.chatroom-body');
const container_el = this.querySelector('.chatroom-body');
container_el.insertAdjacentElement('afterbegin', u.getElementFromTemplateResult(tpl_spinner()));
},
}
/**
* Check if the spinner is being shown and if so, hide it.
@ -1329,13 +1332,13 @@ const ChatRoomViewMixin = {
* @method _converse.ChatRoomView#hideSpinner
*/
hideSpinner () {
const spinner = this.el.querySelector('.spinner');
const spinner = this.querySelector('.spinner');
if (spinner !== null) {
u.removeElement(spinner);
this.renderAfterTransition();
}
return this;
}
};
}
export default ChatRoomViewMixin;
api.elements.define('converse-muc', MUCView);

View File

@ -57,7 +57,7 @@ export const RoomsPanelViewMixin = {
}))()
});
this.roomspanel.model.fetch();
this.el.querySelector('.controlbox-pane').insertAdjacentElement('beforeEnd', this.roomspanel.render().el);
this.querySelector('.controlbox-pane').insertAdjacentElement('beforeEnd', this.roomspanel.render().el);
/**
* Triggered once the section of the { @link _converse.ControlBoxView }

View File

@ -67,23 +67,23 @@ function onChatBoxesInitialized () {
}
function onChatInitialized (view) {
view.listenTo(view.model.messages, 'add', (message) => {
function onChatInitialized (el) {
el.listenTo(el.model.messages, 'add', (message) => {
if (message.get('is_encrypted') && !message.get('is_error')) {
view.model.save('omemo_supported', true);
el.model.save('omemo_supported', true);
}
});
view.listenTo(view.model, 'change:omemo_supported', () => {
if (!view.model.get('omemo_supported') && view.model.get('omemo_active')) {
view.model.set('omemo_active', false);
el.listenTo(el.model, 'change:omemo_supported', () => {
if (!el.model.get('omemo_supported') && el.model.get('omemo_active')) {
el.model.set('omemo_active', false);
} else {
// Manually trigger an update, setting omemo_active to
// false above will automatically trigger one.
view.el.querySelector('converse-chat-toolbar')?.requestUpdate();
el.querySelector('converse-chat-toolbar')?.requestUpdate();
}
});
view.listenTo(view.model, 'change:omemo_active', () => {
view.el.querySelector('converse-chat-toolbar').requestUpdate();
el.listenTo(el.model, 'change:omemo_active', () => {
el.querySelector('converse-chat-toolbar').requestUpdate();
});
}

View File

@ -1,6 +1,7 @@
import { _converse, api } from '@converse/headless/core';
const ControlBoxRegistrationMixin = {
showLoginOrRegisterForm () {
if (!this.registerpanel) {
return;
@ -21,7 +22,7 @@ const ControlBoxRegistrationMixin = {
});
this.registerpanel.render();
this.registerpanel.el.classList.add('hidden');
const login_panel = this.el.querySelector('#converse-login-panel');
const login_panel = this.querySelector('#converse-login-panel');
if (login_panel) {
login_panel.insertAdjacentElement('afterend', this.registerpanel.el);
}

View File

@ -0,0 +1,31 @@
import { api, converse } from '@converse/headless/core';
const u = converse.env.utils;
function ensureElement () {
if (!api.settings.get('auto_insert')) {
return;
}
const root = api.settings.get('root');
if (!root.querySelector('converse-root#conversejs')) {
const el = document.createElement('converse-root');
el.setAttribute('id', 'conversejs');
u.addClass(`theme-${api.settings.get('theme')}`, el);
const body = root.querySelector('body');
if (body) {
body.appendChild(el);
} else {
root.appendChild(el); // Perhaps inside a web component?
}
}
}
converse.plugins.add('converse-rootview', {
initialize () {
api.settings.extend({
'auto_insert': true
});
api.listen.on('chatBoxesInitialized', ensureElement);
}
});

View File

@ -0,0 +1,31 @@
import { api, converse } from '@converse/headless/converse-core';
const u = converse.env.utils;
converse.plugins.add('converse-rootview', {
initialize () {
api.settings.extend({
'auto_insert': true
});
function ensureElement () {
if (!api.settings.get('auto_insert')) {
return;
}
const root = api.settings.get('root');
if (!root.querySelector('converse-root#conversejs')) {
const el = document.createElement('converse-root');
el.setAttribute('id', 'conversejs');
u.addClass(`theme-${api.settings.get('theme')}`, el);
const body = root.querySelector('body');
if (body) {
body.appendChild(el);
} else {
root.appendChild(el); // Perhaps inside a web component?
}
}
}
api.listen.on('chatBoxesInitialized', ensureElement);
}
});

View File

@ -91,12 +91,12 @@ const RosterView = OrderedListView.extend({
* contact fetched from browser storage.
*/
updateFilter: debounce(function () {
this.filter_view = this.el.querySelector('converse-roster-filter');
const type = this.filter_view.model.get('filter_type');
const filter = new _converse.RosterFilter();
const type = filter.get('filter_type');
if (type === 'state') {
this.filter(this.filter_view.model.get('chat_state'), type);
this.filter(filter.get('chat_state'), type);
} else {
this.filter(this.filter_view.model.get('filter_text'), type);
this.filter(filter.get('filter_text'), type);
}
}, 100),

391
src/shared/chatview.js Normal file
View File

@ -0,0 +1,391 @@
import log from '@converse/headless/log';
import tpl_chatbox_message_form from 'templates/chatbox_message_form.js';
import tpl_toolbar from 'templates/toolbar.js';
import { ElementView } from '@converse/skeletor/src/element.js';
import { __ } from 'i18n';
import { _converse, api, converse } from '@converse/headless/core';
import { debounce } from 'lodash-es';
import { html, render } from 'lit-html';
const u = converse.env.utils;
export default class BaseChatView extends ElementView {
initDebounced () {
this.markScrolled = debounce(this._markScrolled, 100);
this.debouncedScrollDown = debounce(this.scrollDown, 100);
// For tests that use Jasmine.Clock we want to turn of
// debouncing, since setTimeout breaks.
if (api.settings.get('debounced_content_rendering')) {
this.renderChatHistory = debounce(() => this.renderChatContent(false), 100);
this.renderNotifications = debounce(() => this.renderChatContent(true), 100);
} else {
this.renderChatHistory = () => this.renderChatContent(false);
this.renderNotifications = () => this.renderChatContent(true);
}
}
renderChatContent (msgs_by_ref = false) {
if (!this.tpl_chat_content) {
this.tpl_chat_content = o => {
return html`
<converse-chat-content .chatview=${this} .messages=${o.messages} notifications=${o.notifications}>
</converse-chat-content>
`;
};
}
const msg_models = this.model.messages.models;
const messages = msgs_by_ref ? msg_models : Array.from(msg_models);
render(this.tpl_chat_content({ messages, 'notifications': this.getNotifications() }), this.msgs_container);
}
renderMessageForm () {
const form_container = this.querySelector('.message-form-container');
render(
tpl_chatbox_message_form(
Object.assign(this.model.toJSON(), {
'hint_value': this.querySelector('.spoiler-hint')?.value,
'label_message': this.model.get('composing_spoiler') ? __('Hidden message') : __('Message'),
'label_spoiler_hint': __('Optional hint'),
'message_value': this.querySelector('.chat-textarea')?.value,
'show_send_button': api.settings.get('show_send_button'),
'show_toolbar': api.settings.get('show_toolbar'),
'unread_msgs': __('You have unread messages')
})
),
form_container
);
this.addEventListener('focusin', ev => this.emitFocused(ev));
this.addEventListener('focusout', ev => this.emitBlurred(ev));
this.renderToolbar();
}
renderToolbar () {
if (!api.settings.get('show_toolbar')) {
return this;
}
const options = Object.assign(
{
'model': this.model,
'chatview': this
},
this.model.toJSON(),
this.getToolbarOptions()
);
render(tpl_toolbar(options), this.querySelector('.chat-toolbar'));
/**
* Triggered once the _converse.ChatBoxView's toolbar has been rendered
* @event _converse#renderToolbar
* @type { _converse.ChatBoxView }
* @example _converse.api.listen.on('renderToolbar', view => { ... });
*/
api.trigger('renderToolbar', this);
return this;
}
async getHeadingStandaloneButton (promise_or_data) { // eslint-disable-line class-methods-use-this
const data = await promise_or_data;
return html`
<a
href="#"
class="chatbox-btn ${data.a_class} fa ${data.icon_class}"
@click=${data.handler}
title="${data.i18n_title}"
></a>
`;
}
hideNewMessagesIndicator () {
const new_msgs_indicator = this.querySelector('.new-msgs-indicator');
if (new_msgs_indicator !== null) {
new_msgs_indicator.classList.add('hidden');
}
}
maybeFocus () {
api.settings.get('auto_focus') && this.focus();
}
focus () {
const textarea_el = this.getElementsByClassName('chat-textarea')[0];
if (textarea_el && document.activeElement !== textarea_el) {
textarea_el.focus();
}
return this;
}
show () {
if (this.model.get('hidden')) {
log.debug(`Not showing chat ${this.model.get('jid')} because it's set as hidden`);
return;
}
if (u.isVisible(this)) {
this.maybeFocus();
return;
}
this.afterShown();
}
emitBlurred (ev) {
if (this.contains(document.activeElement) || this.contains(ev.relatedTarget)) {
// Something else in this chatbox is still focused
return;
}
/**
* Triggered when the focus has been removed from a particular chat.
* @event _converse#chatBoxBlurred
* @type { _converse.ChatBoxView | _converse.ChatRoomView }
* @example _converse.api.listen.on('chatBoxBlurred', (view, event) => { ... });
*/
api.trigger('chatBoxBlurred', this, ev);
}
emitFocused (ev) {
if (this.contains(ev.relatedTarget)) {
// Something else in this chatbox was already focused
return;
}
/**
* Triggered when the focus has been moved to a particular chat.
* @event _converse#chatBoxFocused
* @type { _converse.ChatBoxView | _converse.ChatRoomView }
* @example _converse.api.listen.on('chatBoxFocused', (view, event) => { ... });
*/
api.trigger('chatBoxFocused', this, ev);
}
async getHeadingDropdownItem (promise_or_data) { // eslint-disable-line class-methods-use-this
const data = await promise_or_data;
return html`
<a href="#" class="dropdown-item ${data.a_class}" @click=${data.handler} title="${data.i18n_title}"
><i class="fa ${data.icon_class}"></i>${data.i18n_text}</a
>
`;
}
autocompleteInPicker (input, value) {
const emoji_dropdown = this.querySelector('converse-emoji-dropdown');
const emoji_picker = this.querySelector('converse-emoji-picker');
if (emoji_picker && emoji_dropdown) {
emoji_picker.model.set({
'ac_position': input.selectionStart,
'autocompleting': value,
'query': value
});
emoji_dropdown.showMenu();
return true;
}
}
onEmojiReceivedFromPicker (emoji) {
const model = this.querySelector('converse-emoji-picker').model;
const autocompleting = model.get('autocompleting');
const ac_position = model.get('ac_position');
this.insertIntoTextArea(emoji, autocompleting, false, ac_position);
}
/**
* Insert a particular string value into the textarea of this chat box.
* @private
* @method _converse.ChatBoxView#insertIntoTextArea
* @param {string} value - The value to be inserted.
* @param {(boolean|string)} [replace] - Whether an existing value
* should be replaced. If set to `true`, the entire textarea will
* be replaced with the new value. If set to a string, then only
* that string will be replaced *if* a position is also specified.
* @param {integer} [position] - The end index of the string to be
* replaced with the new value.
*/
insertIntoTextArea (value, replace = false, correcting = false, position) {
const textarea = this.querySelector('.chat-textarea');
if (correcting) {
u.addClass('correcting', textarea);
} else {
u.removeClass('correcting', textarea);
}
if (replace) {
if (position && typeof replace == 'string') {
textarea.value = textarea.value.replace(new RegExp(replace, 'g'), (match, offset) =>
offset == position - replace.length ? value + ' ' : match
);
} else {
textarea.value = value;
}
} else {
let existing = textarea.value;
if (existing && existing[existing.length - 1] !== ' ') {
existing = existing + ' ';
}
textarea.value = existing + value + ' ';
}
this.updateCharCounter(textarea.value);
u.placeCaretAtEnd(textarea);
}
/**
* Called when the chat content is scrolled up or down.
* We want to record when the user has scrolled away from
* the bottom, so that we don't automatically scroll away
* from what the user is reading when new messages are received.
*
* Don't call this method directly, instead, call `markScrolled`,
* which debounces this method by 100ms.
* @private
*/
_markScrolled (ev) {
let scrolled = true;
let scrollTop = null;
const is_at_bottom =
this.msgs_container.scrollTop + this.msgs_container.clientHeight >= this.msgs_container.scrollHeight - 62; // sigh...
if (is_at_bottom) {
scrolled = false;
this.onScrolledDown();
} else if (this.msgs_container.scrollTop === 0) {
/**
* Triggered once the chat's message area has been scrolled to the top
* @event _converse#chatBoxScrolledUp
* @property { _converse.ChatBoxView | _converse.ChatRoomView } view
* @example _converse.api.listen.on('chatBoxScrolledUp', obj => { ... });
*/
api.trigger('chatBoxScrolledUp', this);
} else {
scrollTop = ev.target.scrollTop;
}
u.safeSave(this.model, { scrolled, scrollTop });
}
/**
* Scrolls the chat down.
*
* This method will always scroll the chat down, regardless of
* whether the user scrolled up manually or not.
* @param { Event } [ev] - An optional event that is the cause for needing to scroll down.
*/
scrollDown (ev) {
ev?.preventDefault?.();
ev?.stopPropagation?.();
if (this.model.get('scrolled')) {
u.safeSave(this.model, {
'scrolled': false,
'scrollTop': null
});
}
if (this.msgs_container.scrollTo) {
const behavior = this.msgs_container.scrollTop ? 'smooth' : 'auto';
this.msgs_container.scrollTo({ 'top': this.msgs_container.scrollHeight, behavior });
} else {
this.msgs_container.scrollTop = this.msgs_container.scrollHeight;
}
this.onScrolledDown();
}
onScrolledDown () {
this.hideNewMessagesIndicator();
if (!this.model.isHidden()) {
this.model.clearUnreadMsgCounter();
// Clear location hash if set to one of the messages in our history
const hash = window.location.hash;
hash && this.model.messages.get(hash.slice(1)) && _converse.router.history.navigate();
}
/**
* Triggered once the chat's message area has been scrolled down to the bottom.
* @event _converse#chatBoxScrolledDown
* @type {object}
* @property { _converse.ChatBox | _converse.ChatRoom } chatbox - The chat model
* @example _converse.api.listen.on('chatBoxScrolledDown', obj => { ... });
*/
api.trigger('chatBoxScrolledDown', { 'chatbox': this.model }); // TODO: clean up
}
onWindowStateChanged (state) {
if (state === 'visible') {
if (!this.model.isHidden() && this.model.get('num_unread', 0)) {
this.model.clearUnreadMsgCounter();
}
} else if (state === 'hidden') {
this.model.setChatState(_converse.INACTIVE, { 'silent': true });
this.model.sendChatState();
}
}
async onFormSubmitted (ev) {
ev.preventDefault();
const textarea = this.querySelector('.chat-textarea');
const message_text = textarea.value.trim();
if (
(api.settings.get('message_limit') && message_text.length > api.settings.get('message_limit')) ||
!message_text.replace(/\s/g, '').length
) {
return;
}
if (!_converse.connection.authenticated) {
const err_msg = __('Sorry, the connection has been lost, and your message could not be sent');
api.alert('error', __('Error'), err_msg);
api.connection.reconnect();
return;
}
let spoiler_hint,
hint_el = {};
if (this.model.get('composing_spoiler')) {
hint_el = this.querySelector('form.sendXMPPMessage input.spoiler-hint');
spoiler_hint = hint_el.value;
}
u.addClass('disabled', textarea);
textarea.setAttribute('disabled', 'disabled');
this.querySelector('converse-emoji-dropdown')?.hideMenu();
const is_command = this.parseMessageForCommands(message_text);
const message = is_command ? null : await this.model.sendMessage(message_text, spoiler_hint);
if (is_command || message) {
hint_el.value = '';
textarea.value = '';
u.removeClass('correcting', textarea);
textarea.style.height = 'auto';
this.updateCharCounter(textarea.value);
}
if (message) {
/**
* Triggered whenever a message is sent by the user
* @event _converse#messageSend
* @type { _converse.Message }
* @example _converse.api.listen.on('messageSend', message => { ... });
*/
api.trigger('messageSend', message);
}
if (api.settings.get('view_mode') === 'overlayed') {
// XXX: Chrome flexbug workaround. The .chat-content area
// doesn't resize when the textarea is resized to its original size.
this.msgs_container.parentElement.style.display = 'none';
}
textarea.removeAttribute('disabled');
u.removeClass('disabled', textarea);
if (api.settings.get('view_mode') === 'overlayed') {
// XXX: Chrome flexbug workaround.
this.msgs_container.parentElement.style.display = '';
}
// Suppress events, otherwise superfluous CSN gets set
// immediately after the message, causing rate-limiting issues.
this.model.setChatState(_converse.ACTIVE, { 'silent': true });
textarea.focus();
}
onEnterPressed (ev) {
return this.onFormSubmitted(ev);
}
updateCharCounter (chars) {
if (api.settings.get('message_limit')) {
const message_limit = this.querySelector('.message-limit');
const counter = api.settings.get('message_limit') - chars.length;
message_limit.textContent = counter;
if (counter < 1) {
u.addClass('error', message_limit);
} else {
u.removeClass('error', message_limit);
}
}
}
}

View File

@ -4,7 +4,13 @@ const getImgHref = (image, image_type) => {
return image.startsWith('data:') ? image : `data:${image_type};base64,${image}`;
}
export default (o) => html`
<svg xmlns="http://www.w3.org/2000/svg" class="avatar ${o.classes}" width="${o.width}" height="${o.height}">
<image width="${o.width}" height="${o.height}" preserveAspectRatio="xMidYMid meet" href="${getImgHref(o.image, o.image_type)}"/>
</svg>`;
export default (o) => {
if (o.image) {
return html`
<svg xmlns="http://www.w3.org/2000/svg" class="avatar ${o.classes}" width="${o.width}" height="${o.height}">
<image width="${o.width}" height="${o.height}" preserveAspectRatio="xMidYMid meet" href="${getImgHref(o.image, o.image_type)}"/>
</svg>`;
} else {
return '';
}
}

View File

@ -1,9 +1,13 @@
import { html } from 'lit-html';
import '../components/font-awesome.js';
import { api } from '@converse/headless/core';
import { html } from 'lit-html';
export default () => html`
<div class="converse-chatboxes row no-gutters"></div>
<div id="converse-modals" class="modals"></div>
<converse-fontawesome></converse-fontawesome>
`;
export default () => {
let extra_classes = api.settings.get('singleton') ? 'converse-singleton' : '';
extra_classes += `converse-${api.settings.get('view_mode')}`;
return html`
<converse-chats class="converse-chatboxes row no-gutters ${extra_classes}"></converse-chats>
<div id="converse-modals" class="modals"></div>
<converse-fontawesome></converse-fontawesome>
`;
};