Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
converse.js
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
converse.js
Commits
c41bdac6
Commit
c41bdac6
authored
Sep 03, 2020
by
JC Brand
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Some work on componentizing the minimized chats UI
parent
d5c93eb0
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
416 additions
and
489 deletions
+416
-489
package-lock.json
package-lock.json
+14
-6
spec/chatbox.js
spec/chatbox.js
+0
-190
spec/messages.js
spec/messages.js
+0
-62
spec/minchats.js
spec/minchats.js
+300
-26
spec/muc.js
spec/muc.js
+0
-32
src/components/minimized_chat.js
src/components/minimized_chat.js
+47
-0
src/converse-minimize.js
src/converse-minimize.js
+34
-161
src/templates/chats_panel.html
src/templates/chats_panel.html
+0
-2
src/templates/chats_panel.js
src/templates/chats_panel.js
+18
-0
src/templates/toggle_chats.js
src/templates/toggle_chats.js
+0
-8
src/templates/trimmed_chat.js
src/templates/trimmed_chat.js
+2
-2
webpack.html
webpack.html
+1
-0
No files found.
package-lock.json
View file @
c41bdac6
...
...
@@ -3199,7 +3199,8 @@
"dependencies"
:
{
"filesize"
:
{
"version"
:
"6.1.0"
,
"resolved"
:
false
"resolved"
:
"https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz"
,
"integrity"
:
"sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg=="
},
"fs-extra"
:
{
"version"
:
"8.1.0"
,
...
...
@@ -3233,7 +3234,8 @@
},
"jed"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
false
"resolved"
:
"https://registry.npmjs.org/jed/-/jed-1.1.1.tgz"
,
"integrity"
:
"sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ="
},
"jsonfile"
:
{
"version"
:
"5.0.0"
,
...
...
@@ -3254,7 +3256,8 @@
},
"localforage"
:
{
"version"
:
"1.7.3"
,
"resolved"
:
false
,
"resolved"
:
"https://registry.npmjs.org/localforage/-/localforage-1.7.3.tgz"
,
"integrity"
:
"sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ=="
,
"requires"
:
{
"lie"
:
"3.1.1"
}
...
...
@@ -3266,13 +3269,14 @@
},
"pluggable.js"
:
{
"version"
:
"2.0.1"
,
"resolved"
:
false
,
"resolved"
:
"https://registry.npmjs.org/pluggable.js/-/pluggable.js-2.0.1.tgz"
,
"integrity"
:
"sha512-SBt6v6Tbp20Jf8hU0cpcc/+HBHGMY8/Q+yA6Ih0tBQE8tfdZ6U4PRG0iNvUUjLx/hVyOP53n0UfGBymlfaaXCg=="
,
"requires"
:
{
"lodash"
:
"^4.17.11"
}
},
"skeletor.js"
:
{
"version"
:
"
0.0.
1"
,
"version"
:
"
github:skeletorjs/skeletor#bf6d9c86f9fcf224fa9d9af5a25380b77aa4b56
1"
,
"from"
:
"github:skeletorjs/skeletor#bf6d9c86f9fcf224fa9d9af5a25380b77aa4b561"
,
"requires"
:
{
"lodash"
:
"^4.17.14"
...
...
@@ -3280,7 +3284,11 @@
},
"strophe.js"
:
{
"version"
:
"github:strophe/strophejs#c4a94e59877c06dc2395f4ccbd26f3fee67a4c9f"
,
"from"
:
"strophe.js@github:strophe/strophejs#c4a94e59877c06dc2395f4ccbd26f3fee67a4c9f"
"from"
:
"strophe.js@github:strophe/strophejs#c4a94e59877c06dc2395f4ccbd26f3fee67a4c9f"
,
"requires"
:
{
"abab"
:
"^2.0.3"
,
"xmldom"
:
"^0.1.27"
}
},
"twemoji"
:
{
"version"
:
"12.1.5"
,
...
...
spec/chatbox.js
View file @
c41bdac6
...
...
@@ -193,82 +193,6 @@ describe("Chatboxes", function () {
done
();
}));
it
(
"
can be trimmed to conserve space
"
,
mock
.
initConverse
([
'
rosterGroupsFetched
'
],
{},
async
function
(
done
,
_converse
)
{
spyOn
(
_converse
.
chatboxviews
,
'
trimChats
'
);
const
trimmed_chatboxes
=
_converse
.
minimized_chats
;
spyOn
(
trimmed_chatboxes
,
'
addChat
'
).
and
.
callThrough
();
spyOn
(
trimmed_chatboxes
,
'
removeChat
'
).
and
.
callThrough
();
await
mock
.
waitForRoster
(
_converse
,
'
current
'
);
await
mock
.
openControlBox
(
_converse
);
expect
(
_converse
.
chatboxviews
.
trimChats
.
calls
.
count
()).
toBe
(
1
);
let
jid
,
chatboxview
;
// openControlBox was called earlier, so the controlbox is
// visible, but no other chat boxes have been created.
expect
(
_converse
.
chatboxes
.
length
).
toEqual
(
1
);
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
);
// Test that they can be maximized again
const
online_contacts
=
_converse
.
rosterview
.
el
.
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
++
)
{
const
el
=
online_contacts
[
i
];
el
.
click
();
}
await
u
.
waitUntil
(()
=>
_converse
.
chatboxes
.
length
==
16
);
expect
(
_converse
.
chatboxviews
.
trimChats
.
calls
.
count
()).
toBe
(
16
);
_converse
.
api
.
chatviews
.
get
().
forEach
(
v
=>
spyOn
(
v
,
'
onMinimized
'
).
and
.
callThrough
());
for
(
i
=
0
;
i
<
online_contacts
.
length
;
i
++
)
{
const
el
=
online_contacts
[
i
];
jid
=
_
.
trim
(
el
.
textContent
.
trim
()).
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
chatboxview
=
_converse
.
chatboxviews
.
get
(
jid
);
chatboxview
.
model
.
set
({
'
minimized
'
:
true
});
expect
(
trimmed_chatboxes
.
addChat
).
toHaveBeenCalled
();
expect
(
chatboxview
.
onMinimized
).
toHaveBeenCalled
();
}
await
u
.
waitUntil
(()
=>
_converse
.
chatboxviews
.
keys
().
length
);
var
key
=
_converse
.
chatboxviews
.
keys
()[
1
];
const
trimmedview
=
trimmed_chatboxes
.
get
(
key
);
const
chatbox
=
trimmedview
.
model
;
spyOn
(
chatbox
,
'
maximize
'
).
and
.
callThrough
();
spyOn
(
trimmedview
,
'
restore
'
).
and
.
callThrough
();
trimmedview
.
delegateEvents
();
trimmedview
.
el
.
querySelector
(
"
a.restore-chat
"
).
click
();
expect
(
trimmedview
.
restore
).
toHaveBeenCalled
();
expect
(
chatbox
.
maximize
).
toHaveBeenCalled
();
expect
(
_converse
.
chatboxviews
.
trimChats
.
calls
.
count
()).
toBe
(
17
);
done
();
}));
it
(
"
can be opened in minimized mode initially
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
waitForRoster
(
_converse
,
'
current
'
);
const
sender_jid
=
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
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
();
const
minimized_chat
=
_converse
.
minimized_chats
.
get
(
sender_jid
);
expect
(
minimized_chat
).
toBeTruthy
();
expect
(
u
.
isVisible
(
minimized_chat
.
el
)).
toBeTruthy
();
done
();
}));
it
(
"
is focused if its already open and you click on its corresponding roster item
"
,
mock
.
initConverse
([
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
...
...
@@ -364,42 +288,6 @@ describe("Chatboxes", function () {
done
();
}));
it
(
"
can be minimized by clicking a DOM element with class 'toggle-chatbox-button'
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
waitForRoster
(
_converse
,
'
current
'
);
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
mock
.
openChatBoxFor
(
_converse
,
contact_jid
);
const
trimmed_chatboxes
=
_converse
.
minimized_chats
;
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
();
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
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeTruthy
();
chatview
.
el
.
querySelector
(
'
.toggle-chatbox-button
'
).
click
();
const
trimmedview
=
trimmed_chatboxes
.
get
(
chatview
.
model
.
get
(
'
id
'
));
spyOn
(
trimmedview
,
'
restore
'
).
and
.
callThrough
();
trimmedview
.
delegateEvents
();
trimmedview
.
el
.
querySelector
(
"
a.restore-chat
"
).
click
();
expect
(
trimmedview
.
restore
).
toHaveBeenCalled
();
expect
(
_converse
.
api
.
trigger
).
toHaveBeenCalledWith
(
'
chatBoxMaximized
'
,
jasmine
.
any
(
Object
));
expect
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeFalsy
();
done
();
}));
it
(
"
will be removed from browserStorage when closed
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
...
...
@@ -1591,82 +1479,4 @@ describe("Chatboxes", function () {
done
();
}));
});
describe
(
"
A Minimized ChatBoxView's Unread Message Count
"
,
function
()
{
it
(
"
is displayed when scrolled up chatbox is minimized after receiving unread messages
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
waitForRoster
(
_converse
,
'
current
'
,
1
);
const
sender_jid
=
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
await
mock
.
openChatBoxFor
(
_converse
,
sender_jid
);
const
msgFactory
=
function
()
{
return
mock
.
createChatMessage
(
_converse
,
sender_jid
,
'
This message will be received as unread, but eventually will be read
'
);
};
const
selectUnreadMsgCount
=
function
()
{
const
minimizedChatBoxView
=
_converse
.
minimized_chats
.
get
(
sender_jid
);
return
minimizedChatBoxView
.
el
.
querySelector
(
'
.message-count
'
);
};
const
chatbox
=
_converse
.
chatboxes
.
get
(
sender_jid
);
chatbox
.
save
(
'
scrolled
'
,
true
);
_converse
.
handleMessageStanza
(
msgFactory
());
await
u
.
waitUntil
(()
=>
chatbox
.
messages
.
length
);
const
chatboxview
=
_converse
.
chatboxviews
.
get
(
sender_jid
);
chatboxview
.
minimize
();
const
unread_count
=
selectUnreadMsgCount
();
expect
(
u
.
isVisible
(
unread_count
)).
toBeTruthy
();
expect
(
unread_count
.
innerHTML
.
replace
(
/<!---->/g
,
''
)).
toBe
(
'
1
'
);
done
();
}));
it
(
"
is incremented when message is received and windows is not focused
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
waitForRoster
(
_converse
,
'
current
'
,
1
);
const
sender_jid
=
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
const
view
=
await
mock
.
openChatBoxFor
(
_converse
,
sender_jid
)
const
msgFactory
=
function
()
{
return
mock
.
createChatMessage
(
_converse
,
sender_jid
,
'
This message will be received as unread, but eventually will be read
'
);
};
const
selectUnreadMsgCount
=
function
()
{
const
minimizedChatBoxView
=
_converse
.
minimized_chats
.
get
(
sender_jid
);
return
minimizedChatBoxView
.
el
.
querySelector
(
'
.message-count
'
);
};
view
.
minimize
();
_converse
.
handleMessageStanza
(
msgFactory
());
await
u
.
waitUntil
(()
=>
view
.
model
.
messages
.
length
);
const
unread_count
=
selectUnreadMsgCount
();
expect
(
u
.
isVisible
(
unread_count
)).
toBeTruthy
();
expect
(
unread_count
.
innerHTML
.
replace
(
/<!---->/g
,
''
)).
toBe
(
'
1
'
);
done
();
}));
it
(
"
will render Openstreetmap-URL from geo-URI
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
waitForRoster
(
_converse
,
'
current
'
,
1
);
const
message
=
"
geo:37.786971,-122.399677
"
;
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
);
spyOn
(
view
.
model
,
'
sendMessage
'
).
and
.
callThrough
();
mock
.
sendMessage
(
view
,
message
);
await
u
.
waitUntil
(()
=>
view
.
el
.
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
();
await
u
.
waitUntil
(()
=>
msg
.
innerHTML
.
replace
(
/
\<
!----
\>
/g
,
''
)
===
'
<a target="_blank" rel="noopener" href="https://www.openstreetmap.org/?mlat=37.786971&
'
+
'
mlon=-122.399677#map=18/37.786971/-122.399677">https://www.openstreetmap.org/?mlat=37.786971&mlon=-122.399677#map=18/37.786971/-122.399677</a>
'
);
done
();
}));
});
});
spec/messages.js
View file @
c41bdac6
...
...
@@ -700,68 +700,6 @@ describe("A Chat Message", function () {
done
();
}));
it
(
"
received for a minimized chat box will increment a counter on its header
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
if
(
_converse
.
view_mode
===
'
fullscreen
'
)
{
return
done
();
}
await
mock
.
waitForRoster
(
_converse
,
'
current
'
);
const
contact_name
=
mock
.
cur_names
[
0
];
const
contact_jid
=
contact_name
.
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
await
mock
.
openControlBox
(
_converse
);
spyOn
(
_converse
.
api
,
"
trigger
"
).
and
.
callThrough
();
await
u
.
waitUntil
(()
=>
_converse
.
rosterview
.
el
.
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
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeFalsy
();
chatview
.
el
.
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
'
;
var
msg
=
$msg
({
from
:
sender_jid
,
to
:
_converse
.
connection
.
jid
,
type
:
'
chat
'
,
id
:
u
.
getUniqueId
()
}).
c
(
'
body
'
).
t
(
message
).
up
()
.
c
(
'
active
'
,
{
'
xmlns
'
:
'
http://jabber.org/protocol/chatstates
'
}).
tree
();
await
_converse
.
handleMessageStanza
(
msg
);
await
u
.
waitUntil
(()
=>
chatview
.
model
.
messages
.
length
);
expect
(
_converse
.
api
.
trigger
).
toHaveBeenCalledWith
(
'
message
'
,
jasmine
.
any
(
Object
));
const
trimmed_chatboxes
=
_converse
.
minimized_chats
;
const
trimmedview
=
trimmed_chatboxes
.
get
(
contact_jid
);
let
count
=
trimmedview
.
el
.
querySelector
(
'
.message-count
'
);
expect
(
u
.
isVisible
(
chatview
.
el
)).
toBeFalsy
();
expect
(
trimmedview
.
model
.
get
(
'
minimized
'
)).
toBeTruthy
();
expect
(
u
.
isVisible
(
count
)).
toBeTruthy
();
expect
(
count
.
textContent
).
toBe
(
'
1
'
);
_converse
.
handleMessageStanza
(
$msg
({
from
:
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
,
to
:
_converse
.
connection
.
jid
,
type
:
'
chat
'
,
id
:
u
.
getUniqueId
()
}).
c
(
'
body
'
).
t
(
'
This message is also sent to a minimized chatbox
'
).
up
()
.
c
(
'
active
'
,
{
'
xmlns
'
:
'
http://jabber.org/protocol/chatstates
'
}).
tree
()
);
await
u
.
waitUntil
(()
=>
(
chatview
.
model
.
messages
.
length
>
1
));
expect
(
u
.
isVisible
(
chatview
.
el
)).
toBeFalsy
();
expect
(
trimmedview
.
model
.
get
(
'
minimized
'
)).
toBeTruthy
();
count
=
trimmedview
.
el
.
querySelector
(
'
.message-count
'
);
expect
(
u
.
isVisible
(
count
)).
toBeTruthy
();
expect
(
count
.
textContent
).
toBe
(
'
2
'
);
trimmedview
.
el
.
querySelector
(
'
.restore-chat
'
).
click
();
expect
(
trimmed_chatboxes
.
keys
().
length
).
toBe
(
0
);
done
();
}));
it
(
"
will indicate when it has a time difference of more than a day between it and its predecessor
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
...
...
spec/minchats.js
View file @
c41bdac6
/*global mock */
/*global mock
, converse
*/
const
_
=
converse
.
env
.
_
;
const
$msg
=
converse
.
env
.
$msg
;
const
u
=
converse
.
env
.
utils
;
const
sizzle
=
converse
.
env
.
sizzle
;
describe
(
"
A chat message
"
,
function
()
{
it
(
"
received for a minimized chat box will increment a counter on its header
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
if
(
_converse
.
view_mode
===
'
fullscreen
'
)
{
return
done
();
}
await
mock
.
waitForRoster
(
_converse
,
'
current
'
);
const
contact_name
=
mock
.
cur_names
[
0
];
const
contact_jid
=
contact_name
.
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
await
mock
.
openControlBox
(
_converse
);
spyOn
(
_converse
.
api
,
"
trigger
"
).
and
.
callThrough
();
await
u
.
waitUntil
(()
=>
_converse
.
rosterview
.
el
.
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
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeFalsy
();
chatview
.
el
.
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
'
;
var
msg
=
$msg
({
from
:
sender_jid
,
to
:
_converse
.
connection
.
jid
,
type
:
'
chat
'
,
id
:
u
.
getUniqueId
()
}).
c
(
'
body
'
).
t
(
message
).
up
()
.
c
(
'
active
'
,
{
'
xmlns
'
:
'
http://jabber.org/protocol/chatstates
'
}).
tree
();
await
_converse
.
handleMessageStanza
(
msg
);
await
u
.
waitUntil
(()
=>
chatview
.
model
.
messages
.
length
);
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
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeTruthy
();
expect
(
u
.
isVisible
(
count
)).
toBeTruthy
();
expect
(
count
.
textContent
).
toBe
(
'
1
'
);
_converse
.
handleMessageStanza
(
$msg
({
from
:
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
,
to
:
_converse
.
connection
.
jid
,
type
:
'
chat
'
,
id
:
u
.
getUniqueId
()
}).
c
(
'
body
'
).
t
(
'
This message is also sent to a minimized chatbox
'
).
up
()
.
c
(
'
active
'
,
{
'
xmlns
'
:
'
http://jabber.org/protocol/chatstates
'
}).
tree
()
);
await
u
.
waitUntil
(()
=>
(
chatview
.
model
.
messages
.
length
>
1
));
expect
(
u
.
isVisible
(
chatview
.
el
)).
toBeFalsy
();
expect
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeTruthy
();
count
=
trimmed_chatboxes
.
el
.
querySelector
(
'
converse-minimized-chat .message-count
'
);
expect
(
u
.
isVisible
(
count
)).
toBeTruthy
();
expect
(
count
.
textContent
).
toBe
(
'
2
'
);
_converse
.
minimized_chats
.
el
.
querySelector
(
"
a.restore-chat
"
).
click
();
expect
(
_converse
.
chatboxes
.
filter
(
'
minimized
'
).
length
).
toBe
(
0
);
done
();
}));
});
describe
(
"
A Groupcaht
"
,
function
()
{
it
(
"
can be minimized by clicking a DOM element with class 'toggle-chatbox-button'
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
openChatRoom
(
_converse
,
'
lounge
'
,
'
montague.lit
'
,
'
romeo
'
);
const
view
=
_converse
.
chatboxviews
.
get
(
'
lounge@montague.lit
'
);
spyOn
(
view
,
'
onMinimized
'
).
and
.
callThrough
();
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
'
));
button
.
click
();
expect
(
view
.
onMinimized
).
toHaveBeenCalled
();
expect
(
_converse
.
api
.
trigger
).
toHaveBeenCalledWith
(
'
chatBoxMinimized
'
,
jasmine
.
any
(
Object
));
expect
(
u
.
isVisible
(
view
.
el
)).
toBeFalsy
();
expect
(
view
.
model
.
get
(
'
minimized
'
)).
toBeTruthy
();
expect
(
view
.
onMinimized
).
toHaveBeenCalled
();
const
el
=
await
u
.
waitUntil
(()
=>
_converse
.
minimized_chats
.
el
.
querySelector
(
"
a.restore-chat
"
));
el
.
click
();
expect
(
view
.
onMaximized
).
toHaveBeenCalled
();
expect
(
_converse
.
api
.
trigger
).
toHaveBeenCalledWith
(
'
chatBoxMaximized
'
,
jasmine
.
any
(
Object
));
expect
(
view
.
model
.
get
(
'
minimized
'
)).
toBeFalsy
();
expect
(
_converse
.
api
.
trigger
.
calls
.
count
(),
3
);
done
();
}));
});
describe
(
"
A Chatbox
"
,
function
()
{
it
(
"
can be minimized by clicking a DOM element with class 'toggle-chatbox-button'
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
waitForRoster
(
_converse
,
'
current
'
);
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
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
();
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
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeTruthy
();
chatview
.
el
.
querySelector
(
'
.toggle-chatbox-button
'
).
click
();
await
u
.
waitUntil
(()
=>
_converse
.
chatboxviews
.
keys
().
length
);
_converse
.
minimized_chats
.
el
.
querySelector
(
"
a.restore-chat
"
).
click
();
expect
(
_converse
.
api
.
trigger
).
toHaveBeenCalledWith
(
'
chatBoxMaximized
'
,
jasmine
.
any
(
Object
));
expect
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeFalsy
();
done
();
}));
it
(
"
can be opened in minimized mode initially
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
waitForRoster
(
_converse
,
'
current
'
);
const
sender_jid
=
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
.
firstElementChild
)).
toBe
(
false
);
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
(
_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
);
done
();
}));
it
(
"
can be trimmed to conserve space
"
,
mock
.
initConverse
([
'
rosterGroupsFetched
'
],
{},
async
function
(
done
,
_converse
)
{
spyOn
(
_converse
.
chatboxviews
,
'
trimChats
'
);
await
mock
.
waitForRoster
(
_converse
,
'
current
'
);
await
mock
.
openControlBox
(
_converse
);
expect
(
_converse
.
chatboxviews
.
trimChats
.
calls
.
count
()).
toBe
(
1
);
let
jid
,
chatboxview
;
// openControlBox was called earlier, so the controlbox is
// visible, but no other chat boxes have been created.
expect
(
_converse
.
chatboxes
.
length
).
toEqual
(
1
);
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
);
// Test that they can be maximized again
const
online_contacts
=
_converse
.
rosterview
.
el
.
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
++
)
{
const
el
=
online_contacts
[
i
];
el
.
click
();
}
await
u
.
waitUntil
(()
=>
_converse
.
chatboxes
.
length
==
16
);
expect
(
_converse
.
chatboxviews
.
trimChats
.
calls
.
count
()).
toBe
(
16
);
_converse
.
api
.
chatviews
.
get
().
forEach
(
v
=>
spyOn
(
v
,
'
onMinimized
'
).
and
.
callThrough
());
for
(
i
=
0
;
i
<
online_contacts
.
length
;
i
++
)
{
const
el
=
online_contacts
[
i
];
jid
=
el
.
textContent
.
trim
().
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
chatboxview
=
_converse
.
chatboxviews
.
get
(
jid
);
chatboxview
.
model
.
set
({
'
minimized
'
:
true
});
expect
(
chatboxview
.
onMinimized
).
toHaveBeenCalled
();
}
await
u
.
waitUntil
(()
=>
_converse
.
chatboxviews
.
keys
().
length
);
var
key
=
_converse
.
chatboxviews
.
keys
()[
1
];
const
chatbox
=
_converse
.
chatboxes
.
get
(
key
);
spyOn
(
chatbox
,
'
maximize
'
).
and
.
callThrough
();
_converse
.
minimized_chats
.
el
.
querySelector
(
"
a.restore-chat
"
).
click
();
expect
(
chatbox
.
maximize
).
toHaveBeenCalled
();
expect
(
_converse
.
chatboxviews
.
trimChats
.
calls
.
count
()).
toBe
(
17
);
done
();
}));
});
describe
(
"
A Minimized ChatBoxView's Unread Message Count
"
,
function
()
{
it
(
"
is displayed when scrolled up chatbox is minimized after receiving unread messages
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
waitForRoster
(
_converse
,
'
current
'
,
1
);
const
sender_jid
=
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
await
mock
.
openChatBoxFor
(
_converse
,
sender_jid
);
const
msgFactory
=
()
=>
mock
.
createChatMessage
(
_converse
,
sender_jid
,
'
This message will be received as unread, but eventually will be read
'
);
const
selectUnreadMsgCount
=
()
=>
_converse
.
minimized_chats
.
el
.
querySelector
(
'
#toggle-minimized-chats .unread-message-count
'
);
const
chatbox
=
_converse
.
chatboxes
.
get
(
sender_jid
);
chatbox
.
save
(
'
scrolled
'
,
true
);
_converse
.
handleMessageStanza
(
msgFactory
());
await
u
.
waitUntil
(()
=>
chatbox
.
messages
.
length
);
const
chatboxview
=
_converse
.
chatboxviews
.
get
(
sender_jid
);
chatboxview
.
minimize
();
const
unread_count
=
selectUnreadMsgCount
();
expect
(
u
.
isVisible
(
unread_count
)).
toBeTruthy
();
expect
(
unread_count
.
innerHTML
.
replace
(
/<!---->/g
,
''
)).
toBe
(
'
1
'
);
done
();
}));
it
(
"
is incremented when message is received and windows is not focused
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
waitForRoster
(
_converse
,
'
current
'
,
1
);
const
sender_jid
=
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
const
view
=
await
mock
.
openChatBoxFor
(
_converse
,
sender_jid
)
const
msgFactory
=
()
=>
mock
.
createChatMessage
(
_converse
,
sender_jid
,
'
This message will be received as unread, but eventually will be read
'
);
const
selectUnreadMsgCount
=
()
=>
_converse
.
minimized_chats
.
el
.
querySelector
(
'
#toggle-minimized-chats .unread-message-count
'
);
view
.
minimize
();
_converse
.
handleMessageStanza
(
msgFactory
());
await
u
.
waitUntil
(()
=>
view
.
model
.
messages
.
length
);
const
unread_count
=
selectUnreadMsgCount
();
expect
(
u
.
isVisible
(
unread_count
)).
toBeTruthy
();
expect
(
unread_count
.
innerHTML
.
replace
(
/<!---->/g
,
''
)).
toBe
(
'
1
'
);
done
();
}));
it
(
"
will render Openstreetmap-URL from geo-URI
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
waitForRoster
(
_converse
,
'
current
'
,
1
);
const
message
=
"
geo:37.786971,-122.399677
"
;
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
);
spyOn
(
view
.
model
,
'
sendMessage
'
).
and
.
callThrough
();
mock
.
sendMessage
(
view
,
message
);
await
u
.
waitUntil
(()
=>
view
.
el
.
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
();
await
u
.
waitUntil
(()
=>
msg
.
innerHTML
.
replace
(
/
\<
!----
\>
/g
,
''
)
===
'
<a target="_blank" rel="noopener" href="https://www.openstreetmap.org/?mlat=37.786971&
'
+
'
mlon=-122.399677#map=18/37.786971/-122.399677">https://www.openstreetmap.org/?mlat=37.786971&mlon=-122.399677#map=18/37.786971/-122.399677</a>
'
);
done
();
}));
});
describe
(
"
The Minimized Chats Widget
"
,
function
()
{
...
...
@@ -19,12 +291,12 @@ describe("The Minimized Chats Widget", function () {
await
mock
.
openChatBoxFor
(
_converse
,
contact_jid
)
let
chatview
=
_converse
.
chatboxviews
.
get
(
contact_jid
);
expect
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeFalsy
();
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
)).
toBe
(
false
);
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
.
firstElementChild
)).
toBe
(
false
);
chatview
.
el
.
querySelector
(
'
.toggle-chatbox-button
'
).
click
();
expect
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeTruthy
();
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
)).
toBe
(
true
);
expect
(
_converse
.
minimized_chats
.
keys
(
).
length
).
toBe
(
1
);
expect
(
_converse
.
minimized_chats
.
keys
()[
0
]
).
toBe
(
contact_jid
);
expect
(
_converse
.
chatboxes
.
filter
(
'
minimized
'
).
length
).
toBe
(
1
);
expect
(
_converse
.
chatboxes
.
models
.
filter
(
c
=>
c
.
get
(
'
minimized
'
)).
pop
().
get
(
'
jid
'
)
).
toBe
(
contact_jid
);
contact_jid
=
mock
.
cur_names
[
1
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
await
mock
.
openChatBoxFor
(
_converse
,
contact_jid
);
...
...
@@ -33,8 +305,8 @@ describe("The Minimized Chats Widget", function () {
chatview
.
el
.
querySelector
(
'
.toggle-chatbox-button
'
).
click
();
expect
(
chatview
.
model
.
get
(
'
minimized
'
)).
toBeTruthy
();
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
)).
toBe
(
true
);
expect
(
_converse
.
minimized_chats
.
keys
(
).
length
).
toBe
(
2
);
expect
(
_
.
includes
(
_converse
.
minimized_chats
.
keys
(),
contact_jid
)).
toBeTruthy
();
expect
(
_converse
.
chatboxes
.
filter
(
'
minimized
'
).
length
).
toBe
(
2
);
expect
(
_
converse
.
chatboxes
.
filter
(
'
minimized
'
).
map
(
c
=>
c
.
get
(
'
jid
'
)).
includes
(
contact_jid
)).
toBeTruthy
();
done
();
}));
...
...
@@ -50,16 +322,18 @@ describe("The Minimized Chats Widget", function () {
const
contact_jid
=
mock
.
cur_names
[
0
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
await
mock
.
openChatBoxFor
(
_converse
,
contact_jid
);
const
chatview
=
_converse
.
chatboxviews
.
get
(
contact_jid
);
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
)).
toBeFalsy
();
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
.
firstElementChild
)).
toBe
(
false
);
chatview
.
model
.
set
({
'
minimized
'
:
true
});
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
)).
toBeTruthy
();
expect
(
_converse
.
minimized_chats
.
keys
().
length
).
toBe
(
1
);
expect
(
_converse
.
minimized_chats
.
keys
()[
0
]).
toBe
(
contact_jid
);
expect
(
_converse
.
chatboxes
.
filter
(
'
minimized
'
).
length
).
toBe
(
1
);
expect
(
_converse
.
chatboxes
.
models
.
filter
(
c
=>
c
.
get
(
'
minimized
'
)).
pop
().
get
(
'
jid
'
)).
toBe
(
contact_jid
);
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.minimized-chats-flyout
'
))).
toBeTruthy
();
expect
(
_converse
.
minimized_chats
.
toggleview
.
model
.
get
(
'
collapsed
'
)).
toBeFalsy
();
expect
(
_converse
.
minimized_chats
.
minchats
.
get
(
'
collapsed
'
)).
toBeFalsy
();
_converse
.
minimized_chats
.
el
.
querySelector
(
'
#toggle-minimized-chats
'
).
click
();
await
u
.
waitUntil
(()
=>
u
.
isVisible
(
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.minimized-chats-flyout
'
)));
expect
(
_converse
.
minimized_chats
.
toggleview
.
model
.
get
(
'
collapsed
'
)).
toBeTruthy
();
expect
(
_converse
.
minimized_chats
.
minchats
.
get
(
'
collapsed
'
)).
toBeTruthy
();
done
();
}));
...
...
@@ -72,22 +346,22 @@ describe("The Minimized Chats Widget", function () {
await
mock
.
openControlBox
(
_converse
);
_converse
.
minimized_chats
.
initToggle
();
var
i
,
contact_jid
,
chatview
,
msg
;
_converse
.
minimized_chats
.
toggleview
.
model
.
set
({
'
collapsed
'
:
true
});
_converse
.
minimized_chats
.
minchats
.
set
({
'
collapsed
'
:
true
});
const
unread_el
=
_converse
.
minimized_chats
.
toggleview
.
el
.
querySelector
(
'
.unread-message-count
'
);
expect
(
u
nread_el
===
null
).
toBe
(
tru
e
);
const
unread_el
=
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.unread-message-count
'
);
expect
(
u
.
isVisible
(
unread_el
)).
toBe
(
fals
e
);
let
i
,
contact_jid
;
for
(
i
=
0
;
i
<
3
;
i
++
)
{
contact_jid
=
mock
.
cur_names
[
i
].
replace
(
/ /g
,
'
.
'
).
toLowerCase
()
+
'
@montague.lit
'
;
mock
.
openChatBoxFor
(
_converse
,
contact_jid
);
}
await
u
.
waitUntil
(()
=>
_converse
.
chatboxes
.
length
==
4
);
chatview
=
_converse
.
chatboxviews
.
get
(
contact_jid
);
c
onst
c
hatview
=
_converse
.
chatboxviews
.
get
(
contact_jid
);
chatview
.
model
.
set
({
'
minimized
'
:
true
});
for
(
i
=
0
;
i
<
3
;
i
++
)
{
msg
=
$msg
({
const
msg
=
$msg
({
from
:
contact_jid
,
to
:
_converse
.
connection
.
jid
,
type
:
'
chat
'
,
...
...
@@ -98,8 +372,8 @@ describe("The Minimized Chats Widget", function () {
}
await
u
.
waitUntil
(()
=>
chatview
.
model
.
messages
.
length
===
3
,
500
);
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
toggleview
.
el
.
querySelector
(
'
.unread-message-count
'
))).
toBeTruthy
();
expect
(
_converse
.
minimized_chats
.
toggleview
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
((
3
).
toString
());
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.unread-message-count
'
))).
toBeTruthy
();
expect
(
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
((
3
).
toString
());
// Chat state notifications don't increment the unread messages counter
// <composing> state
_converse
.
handleMessageStanza
(
$msg
({
...
...
@@ -108,7 +382,7 @@ describe("The Minimized Chats Widget", function () {
type
:
'
chat
'
,
id
:
u
.
getUniqueId
()
}).
c
(
'
composing
'
,
{
'
xmlns
'
:
'
http://jabber.org/protocol/chatstates
'
}).
tree
());
expect
(
_converse
.
minimized_chats
.
toggleview
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
((
i
).
toString
());
expect
(
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
((
i
).
toString
());
// <paused> state
_converse
.
handleMessageStanza
(
$msg
({
...
...
@@ -117,7 +391,7 @@ describe("The Minimized Chats Widget", function () {
type
:
'
chat
'
,
id
:
u
.
getUniqueId
()
}).
c
(
'
paused
'
,
{
'
xmlns
'
:
'
http://jabber.org/protocol/chatstates
'
}).
tree
());
expect
(
_converse
.
minimized_chats
.
toggleview
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
((
i
).
toString
());
expect
(
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
((
i
).
toString
());
// <gone> state
_converse
.
handleMessageStanza
(
$msg
({
...
...
@@ -126,7 +400,7 @@ describe("The Minimized Chats Widget", function () {
type
:
'
chat
'
,
id
:
u
.
getUniqueId
()
}).
c
(
'
gone
'
,
{
'
xmlns
'
:
'
http://jabber.org/protocol/chatstates
'
}).
tree
());
expect
(
_converse
.
minimized_chats
.
toggleview
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
((
i
).
toString
());
expect
(
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
((
i
).
toString
());
// <inactive> state
_converse
.
handleMessageStanza
(
$msg
({
...
...
@@ -135,7 +409,7 @@ describe("The Minimized Chats Widget", function () {
type
:
'
chat
'
,
id
:
u
.
getUniqueId
()
}).
c
(
'
inactive
'
,
{
'
xmlns
'
:
'
http://jabber.org/protocol/chatstates
'
}).
tree
());
expect
(
_converse
.
minimized_chats
.
toggleview
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
((
i
).
toString
());
expect
(
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
((
i
).
toString
());
done
();
}));
...
...
@@ -158,8 +432,8 @@ describe("The Minimized Chats Widget", function () {
}).
c
(
'
body
'
).
t
(
message
).
tree
();
view
.
model
.
handleMessageStanza
(
msg
);
await
u
.
waitUntil
(()
=>
view
.
model
.
messages
.
length
);
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
toggleview
.
el
.
querySelector
(
'
.unread-message-count
'
))).
toBeTruthy
();
expect
(
_converse
.
minimized_chats
.
toggleview
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
(
'
1
'
);
expect
(
u
.
isVisible
(
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.unread-message-count
'
))).
toBeTruthy
();
expect
(
_converse
.
minimized_chats
.
el
.
querySelector
(
'
.unread-message-count
'
).
textContent
).
toBe
(
'
1
'
);
done
();
}));
});
spec/muc.js
View file @
c41bdac6
...
...
@@ -2862,38 +2862,6 @@ describe("Groupchats", function () {
done
();
}));
it
(
"
can be minimized by clicking a DOM element with class 'toggle-chatbox-button'
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
async
function
(
done
,
_converse
)
{
await
mock
.
openChatRoom
(
_converse
,
'
lounge
'
,
'
montague.lit
'
,
'
romeo
'
);
const
view
=
_converse
.
chatboxviews
.
get
(
'
lounge@montague.lit
'
),
trimmed_chatboxes
=
_converse
.
minimized_chats
;
spyOn
(
view
,
'
onMinimized
'
).
and
.
callThrough
();
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
'
));
button
.
click
();
expect
(
view
.
onMinimized
).
toHaveBeenCalled
();
expect
(
_converse
.
api
.
trigger
).
toHaveBeenCalledWith
(
'
chatBoxMinimized
'
,
jasmine
.
any
(
Object
));
expect
(
u
.
isVisible
(
view
.
el
)).
toBeFalsy
();
expect
(
view
.
model
.
get
(
'
minimized
'
)).
toBeTruthy
();
expect
(
view
.
onMinimized
).
toHaveBeenCalled
();
await
u
.
waitUntil
(()
=>
trimmed_chatboxes
.
get
(
view
.
model
.
get
(
'
id
'
)));
const
trimmedview
=
trimmed_chatboxes
.
get
(
view
.
model
.
get
(
'
id
'
));
trimmedview
.
el
.
querySelector
(
"
a.restore-chat
"
).
click
();
expect
(
view
.
onMaximized
).
toHaveBeenCalled
();
expect
(
_converse
.
api
.
trigger
).
toHaveBeenCalledWith
(
'
chatBoxMaximized
'
,
jasmine
.
any
(
Object
));
expect
(
view
.
model
.
get
(
'
minimized
'
)).
toBeFalsy
();
expect
(
_converse
.
api
.
trigger
.
calls
.
count
(),
3
);
done
();
}));
it
(
"
can be closed again by clicking a DOM element with class 'close-chatbox-button'
"
,
mock
.
initConverse
(
[
'
rosterGroupsFetched
'
,
'
chatBoxesFetched
'
],
{},
...
...
src/components/minimized_chat.js
0 → 100644
View file @
c41bdac6
import
{
CustomElement
}
from
'
./element.js
'
;
import
tpl_trimmed_chat
from
"
templates/trimmed_chat.js
"
;
import
{
api
,
_converse
}
from
"
@converse/headless/converse-core
"
;
export
default
class
MinimizedChat
extends
CustomElement
{
static
get
properties
()
{
return
{
model
:
{
type
:
Object
},
title
:
{
type
:
String
},
type
:
{
type
:
String
},
num_unread
:
{
type
:
Number
}
}
}
render
()
{
const
data
=
{
'
close
'
:
ev
=>
this
.
close
(
ev
),
'
num_unread
'
:
this
.
num_unread
,
'
restore
'
:
ev
=>
this
.
restore
(
ev
),
'
title
'
:
this
.
title
,
'
type
'
:
this
.
type
};
return
tpl_trimmed_chat
(
data
);
}
close
(
ev
)
{
ev
?.
preventDefault
();
const
view
=
_converse
.
chatboxviews
.
get
(
this
.
model
.
get
(
'
id
'
));
if
(
view
)
{
// This will call model.destroy(), removing it from the
// collection and will also emit 'chatBoxClosed'
view
.
close
();
}
else
{
this
.
model
.
destroy
();
api
.
trigger
(
'
chatBoxClosed
'
,
this
);
}
}
restore
(
ev
)
{
ev
?.
preventDefault
();
this
.
model
.
maximize
();
}
}
api
.
elements
.
define
(
'
converse-minimized-chat
'
,
MinimizedChat
);
src/converse-minimize.js
View file @
c41bdac6
...
...
@@ -3,16 +3,14 @@
* @copyright 2020, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import
"
converse-chatview
"
;
import
tpl_chats_panel
from
"
templates/chats_panel.html
"
;
import
tpl_toggle_chats
from
"
templates/toggle_chats.js
"
;
import
tpl_trimmed_chat
from
"
templates/trimmed_chat.js
"
;
import
'
./components/minimized_chat.js
'
;
import
'
converse-chatview
'
;
import
tpl_chats_panel
from
'
templates/chats_panel.js
'
;
import
{
Model
}
from
'
@converse/skeletor/src/model.js
'
;
import
{
Overview
}
from
"
@converse/skeletor/src/overview
"
;
import
{
View
}
from
"
@converse/skeletor/src/view
"
;
import
{
View
}
from
'
@converse/skeletor/src/view
'
;
import
{
__
}
from
'
@converse/headless/i18n
'
;
import
{
_converse
,
api
,
converse
}
from
"
@converse/headless/converse-core
"
;
import
{
debounce
,
sum
}
from
'
lodash-es
'
;
import
{
_converse
,
api
,
converse
}
from
'
@converse/headless/converse-core
'
;
import
{
debounce
}
from
'
lodash-es
'
;
import
{
render
}
from
'
lit-html
'
;
const
{
dayjs
}
=
converse
.
env
;
...
...
@@ -324,183 +322,58 @@ converse.plugins.add('converse-minimize', {
api
.
promises
.
add
(
'
minimizedChatsInitialized
'
);
_converse
.
MinimizedChatBoxView
=
View
.
extend
({
tagName
:
'
div
'
,
events
:
{
'
click .close-chatbox-button
'
:
'
close
'
,
'
click .restore-chat
'
:
'
restore
'
},
initialize
()
{
this
.
listenTo
(
this
.
model
,
'
change:num_unread
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
change:name
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
change:fullname
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
change:jid
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
destroy
'
,
this
.
remove
)
/**
* Triggered once a {@link _converse.MinimizedChatBoxView } has been initialized
* @event _converse#minimizedChatViewInitialized
* @type { _converse.MinimizedChatBoxView }
* @example _converse.api.listen.on('minimizedChatViewInitialized', view => { ... });
*/
api
.
trigger
(
'
minimizedChatViewInitialized
'
,
this
);
},
render
()
{
const
data
=
Object
.
assign
(
this
.
model
.
toJSON
(),
{
'
title
'
:
this
.
model
.
getDisplayName
()});
render
(
tpl_trimmed_chat
(
data
),
this
.
el
);
return
this
.
el
;
},
close
(
ev
)
{
if
(
ev
&&
ev
.
preventDefault
)
{
ev
.
preventDefault
();
}
this
.
remove
();
const
view
=
_converse
.
chatboxviews
.
get
(
this
.
model
.
get
(
'
id
'
));
if
(
view
)
{
// This will call model.destroy(), removing it from the
// collection and will also emit 'chatBoxClosed'
view
.
close
();
}
else
{
this
.
model
.
destroy
();
api
.
trigger
(
'
chatBoxClosed
'
,
this
);
}
return
this
;
},
restore
:
debounce
(
function
(
ev
)
{
if
(
ev
&&
ev
.
preventDefault
)
{
ev
.
preventDefault
();
}
this
.
model
.
off
(
'
change:num_unread
'
,
null
,
this
);
this
.
remove
();
this
.
model
.
maximize
();
},
200
,
{
'
leading
'
:
true
})
_converse
.
MinimizedChatsToggle
=
Model
.
extend
({
defaults
:
{
'
collapsed
'
:
false
}
});
_converse
.
MinimizedChats
=
Overview
.
extend
({
tagName
:
'
div
'
,
id
:
"
minimized-chats
"
,
className
:
'
hidden
'
,
events
:
{
"
click #toggle-minimized-chats
"
:
"
toggle
"
},
_converse
.
MinimizedChats
=
View
.
extend
({
tagName
:
'
span
'
,
async
initialize
()
{
this
.
render
();
await
this
.
initToggle
();
const
chats
=
this
.
model
.
where
({
'
minimized
'
:
true
});
chats
.
length
&&
this
.
addMultipleChats
(
chats
);
this
.
listenTo
(
this
.
model
,
"
add
"
,
this
.
onChanged
)
this
.
listenTo
(
this
.
model
,
"
destroy
"
,
this
.
removeChat
)
this
.
listenTo
(
this
.
model
,
"
change:minimized
"
,
this
.
onChanged
)
this
.
listenTo
(
this
.
model
,
'
change:num_unread
'
,
this
.
updateUnreadMessagesCounter
)
this
.
render
();
this
.
listenTo
(
this
.
minchats
,
'
change:collapsed
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
add
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
change:fullname
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
change:jid
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
change:minimized
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
change:name
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
change:num_unread
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
remove
'
,
this
.
render
)
},
render
()
{
const
chats
=
this
.
model
.
where
({
'
minimized
'
:
true
});
const
num_unread
=
chats
.
reduce
((
acc
,
chat
)
=>
(
acc
+
chat
.
get
(
'
num_unread
'
)),
0
);
const
num_minimized
=
chats
.
reduce
((
acc
,
chat
)
=>
(
acc
+
(
chat
.
get
(
'
minimized
'
)
?
1
:
0
)),
0
);
const
collapsed
=
this
.
minchats
.
get
(
'
collapsed
'
);
const
data
=
{
chats
,
num_unread
,
num_minimized
,
collapsed
};
data
.
toggle
=
ev
=>
this
.
toggle
(
ev
);
render
(
tpl_chats_panel
(
data
),
this
.
el
);
if
(
!
this
.
el
.
parentElement
)
{
this
.
el
.
innerHTML
=
tpl_chats_panel
();
_converse
.
chatboxviews
.
insertRowColumn
(
this
.
el
);
}
if
(
this
.
keys
().
length
===
0
)
{
this
.
el
.
classList
.
add
(
'
hidden
'
);
}
else
if
(
this
.
keys
().
length
>
0
&&
!
u
.
isVisible
(
this
.
el
))
{
this
.
el
.
classList
.
remove
(
'
hidden
'
);
}
return
this
.
el
;
},
async
initToggle
()
{
const
id
=
`converse.minchatstoggle-
${
_converse
.
bare_jid
}
`
;
const
model
=
new
_converse
.
MinimizedChatsToggle
({
id
});
model
.
browserStorage
=
_converse
.
createStore
(
id
);
await
new
Promise
(
resolve
=>
model
.
fetch
({
'
success
'
:
resolve
,
'
error
'
:
resolve
}));
this
.
toggleview
=
new
_converse
.
MinimizedChatsToggleView
({
model
});
this
.
minchats
=
new
_converse
.
MinimizedChatsToggle
({
id
});
this
.
minchats
.
browserStorage
=
_converse
.
createStore
(
id
);
await
new
Promise
(
resolve
=>
this
.
minchats
.
fetch
({
'
success
'
:
resolve
,
'
error
'
:
resolve
}));
},
toggle
(
ev
)
{
if
(
ev
&&
ev
.
preventDefault
)
{
ev
.
preventDefault
();
}
this
.
toggleview
.
model
.
save
({
'
collapsed
'
:
!
this
.
toggleview
.
model
.
get
(
'
collapsed
'
)});
u
.
slideToggleElement
(
this
.
el
.
querySelector
(
'
.minimized-chats-flyout
'
),
200
);
},
onChanged
(
item
)
{
if
(
item
.
get
(
'
id
'
)
===
'
controlbox
'
)
{
// The ControlBox has it's own minimize toggle
return
;
}
if
(
item
.
get
(
'
minimized
'
))
{
this
.
addChat
(
item
);
}
else
if
(
this
.
get
(
item
.
get
(
'
id
'
)))
{
this
.
removeChat
(
item
);
}
},
addChatView
(
item
)
{
const
existing
=
this
.
get
(
item
.
get
(
'
id
'
));
if
(
existing
&&
existing
.
el
.
parentNode
)
{
return
;
}
const
view
=
new
_converse
.
MinimizedChatBoxView
({
model
:
item
});
this
.
el
.
querySelector
(
'
.minimized-chats-flyout
'
).
insertAdjacentElement
(
'
beforeEnd
'
,
view
.
render
());
this
.
add
(
item
.
get
(
'
id
'
),
view
);
},
addMultipleChats
(
items
)
{
items
.
forEach
(
item
=>
this
.
addChatView
(
item
));
this
.
toggleview
.
model
.
set
({
'
num_minimized
'
:
this
.
keys
().
length
});
this
.
render
();
},
addChat
(
item
)
{
this
.
addChatView
(
item
);
this
.
toggleview
.
model
.
set
({
'
num_minimized
'
:
this
.
keys
().
length
});
this
.
render
();
},
removeChat
(
item
)
{
this
.
remove
(
item
.
get
(
'
id
'
));
this
.
toggleview
.
model
.
set
({
'
num_minimized
'
:
this
.
keys
().
length
});
this
.
render
();
},
updateUnreadMessagesCounter
()
{
this
.
toggleview
.
model
.
save
({
'
num_unread
'
:
sum
(
this
.
model
.
pluck
(
'
num_unread
'
))});
this
.
render
();
ev
?.
preventDefault
();
this
.
minchats
.
save
({
'
collapsed
'
:
!
this
.
minchats
.
get
(
'
collapsed
'
)});
}
});
_converse
.
MinimizedChatsToggle
=
Model
.
extend
({
defaults
:
{
'
collapsed
'
:
false
,
'
num_minimized
'
:
0
,
'
num_unread
'
:
0
}
});
_converse
.
MinimizedChatsToggleView
=
View
.
extend
({
_setElement
(){
this
.
el
=
_converse
.
root
.
querySelector
(
'
#toggle-minimized-chats
'
);
},
initialize
()
{
this
.
listenTo
(
this
.
model
,
'
change:num_minimized
'
,
this
.
render
)
this
.
listenTo
(
this
.
model
,
'
change:num_unread
'
,
this
.
render
)
this
.
flyout
=
this
.
el
.
parentElement
.
querySelector
(
'
.minimized-chats-flyout
'
);
},
render
()
{
render
(
tpl_toggle_chats
(
Object
.
assign
(
this
.
model
.
toJSON
())),
this
.
el
);
if
(
this
.
model
.
get
(
'
collapsed
'
))
{
u
.
hideElement
(
this
.
flyout
);
}
else
{
u
.
showElement
(
this
.
flyout
);
}
return
this
.
el
;
}
});
function
initMinimizedChats
()
{
_converse
.
minimized_chats
?.
remove
();
_converse
.
minimized_chats
=
new
_converse
.
MinimizedChats
({
model
:
_converse
.
chatboxes
});
...
...
src/templates/chats_panel.html
deleted
100644 → 0
View file @
d5c93eb0
<a
id=
"toggle-minimized-chats"
href=
"#"
class=
"row no-gutters"
></a>
<div
class=
"flyout minimized-chats-flyout row no-gutters"
></div>
src/templates/chats_panel.js
0 → 100644
View file @
c41bdac6
import
{
html
}
from
"
lit-html
"
;
import
{
__
}
from
'
@converse/headless/i18n
'
;
export
default
(
o
)
=>
html
`<div id="minimized-chats" class="
${
o
.
chats
.
length
?
''
:
'
hidden
'
}
">
<a id="toggle-minimized-chats" class="row no-gutters" @click=
${
o
.
toggle
}
>
${
o
.
num_minimized
}
${
__
(
'
Minimized
'
)}
<span class="unread-message-count
${
!
o
.
num_unread
?
'
unread-message-count-hidden
'
:
''
}
" href="#">
${
o
.
num_unread
}
</span>
</a>
<div class="flyout minimized-chats-flyout row no-gutters
${
o
.
collapsed
?
'
hidden
'
:
''
}
">
${
o
.
chats
.
map
(
chat
=>
html
`<converse-minimized-chat
.model=
${
chat
}
title=
${
chat
.
getDisplayName
()}
type=
${
chat
.
get
(
'
type
'
)}
num_unread=
${
chat
.
get
(
'
num_unread
'
)}
></converse-minimized-chat>`
)}
</div>
</div>`
;
src/templates/toggle_chats.js
deleted
100644 → 0
View file @
d5c93eb0
import
{
html
}
from
"
lit-html
"
;
import
{
__
}
from
'
@converse/headless/i18n
'
;
export
default
(
o
)
=>
html
`
${
o
.
num_minimized
}
${
__
(
'
Minimized
'
)}
<span class="unread-message-count
${
!
o
.
num_unread
?
'
unread-message-count-hidden
'
:
''
}
" href="#">
${
o
.
num_unread
}
</span>
`
;
src/templates/trimmed_chat.js
View file @
c41bdac6
...
...
@@ -6,10 +6,10 @@ export default (o) => {
const
i18n_tooltip
=
__
(
'
Click to restore this chat
'
);
return
html
`
<div class="chat-head-
${
o
.
type
}
chat-head row no-gutters">
<a class="restore-chat w-100 align-self-center" title="
${
i18n_tooltip
}
">
<a class="restore-chat w-100 align-self-center" title="
${
i18n_tooltip
}
"
@click=
${
o
.
restore
}
>
${
o
.
num_unread
?
html
`<span class="message-count badge badge-light">
${
o
.
num_unread
}
</span>`
:
''
}
${
o
.
title
}
</a>
<a class="chatbox-btn close-chatbox-button fa fa-times"></a>
<a class="chatbox-btn close-chatbox-button fa fa-times"
@click=
${
o
.
close
}
></a>
</div>`
;
}
webpack.html
View file @
c41bdac6
...
...
@@ -20,6 +20,7 @@
}
});
converse
.
initialize
({
// root: new DocumentFragment(),
show_send_button
:
true
,
auto_away
:
300
,
auto_register_muc_nickname
:
true
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment