solid-chat 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -30
- package/bin/cli.js +77 -0
- package/icons/icon-128.png +0 -0
- package/icons/icon-144.png +0 -0
- package/icons/icon-152.png +0 -0
- package/icons/icon-192.png +0 -0
- package/icons/icon-384.png +0 -0
- package/icons/icon-512.png +0 -0
- package/icons/icon-72.png +0 -0
- package/icons/icon-96.png +0 -0
- package/icons/icon.svg +22 -0
- package/index.html +1065 -0
- package/manifest.json +57 -0
- package/package.json +20 -4
- package/src/chatListPane.js +233 -41
- package/src/index.test.js +71 -0
- package/src/longChatPane.js +870 -28
package/manifest.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Solid Chat",
|
|
3
|
+
"short_name": "Solid Chat",
|
|
4
|
+
"description": "Decentralized messaging for the web",
|
|
5
|
+
"start_url": "/app/",
|
|
6
|
+
"display": "standalone",
|
|
7
|
+
"background_color": "#f7f8fc",
|
|
8
|
+
"theme_color": "#667eea",
|
|
9
|
+
"orientation": "portrait-primary",
|
|
10
|
+
"icons": [
|
|
11
|
+
{
|
|
12
|
+
"src": "icons/icon-72.png",
|
|
13
|
+
"sizes": "72x72",
|
|
14
|
+
"type": "image/png"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"src": "icons/icon-96.png",
|
|
18
|
+
"sizes": "96x96",
|
|
19
|
+
"type": "image/png"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"src": "icons/icon-128.png",
|
|
23
|
+
"sizes": "128x128",
|
|
24
|
+
"type": "image/png"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"src": "icons/icon-144.png",
|
|
28
|
+
"sizes": "144x144",
|
|
29
|
+
"type": "image/png"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"src": "icons/icon-152.png",
|
|
33
|
+
"sizes": "152x152",
|
|
34
|
+
"type": "image/png"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"src": "icons/icon-192.png",
|
|
38
|
+
"sizes": "192x192",
|
|
39
|
+
"type": "image/png",
|
|
40
|
+
"purpose": "any maskable"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"src": "icons/icon-384.png",
|
|
44
|
+
"sizes": "384x384",
|
|
45
|
+
"type": "image/png"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"src": "icons/icon-512.png",
|
|
49
|
+
"sizes": "512x512",
|
|
50
|
+
"type": "image/png",
|
|
51
|
+
"purpose": "any maskable"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"categories": ["social", "communication"],
|
|
55
|
+
"lang": "en",
|
|
56
|
+
"dir": "ltr"
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solid-chat",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Modern chat panes for Solid pods - longChatPane and chatListPane",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"module": "src/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"solid-chat": "./bin/cli.js"
|
|
10
|
+
},
|
|
8
11
|
"exports": {
|
|
9
12
|
".": "./src/index.js",
|
|
10
13
|
"./longChatPane": "./src/longChatPane.js",
|
|
11
14
|
"./chatListPane": "./src/chatListPane.js"
|
|
12
15
|
},
|
|
13
16
|
"files": [
|
|
14
|
-
"src/"
|
|
17
|
+
"src/",
|
|
18
|
+
"bin/",
|
|
19
|
+
"index.html",
|
|
20
|
+
"icons/",
|
|
21
|
+
"manifest.json"
|
|
15
22
|
],
|
|
16
23
|
"scripts": {
|
|
17
24
|
"start": "npx serve .",
|
|
18
|
-
"dev": "npx serve . -p 8080"
|
|
25
|
+
"dev": "npx serve . -p 8080",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest"
|
|
19
28
|
},
|
|
20
29
|
"peerDependencies": {
|
|
21
30
|
"rdflib": ">=2.2.0"
|
|
@@ -38,5 +47,12 @@
|
|
|
38
47
|
},
|
|
39
48
|
"homepage": "https://solid-chat.com",
|
|
40
49
|
"author": "Solid Chat Contributors",
|
|
41
|
-
"license": "AGPL-3.0"
|
|
50
|
+
"license": "AGPL-3.0",
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"open": "^10.1.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"jsdom": "^27.4.0",
|
|
56
|
+
"vitest": "^4.0.16"
|
|
57
|
+
}
|
|
42
58
|
}
|
package/src/chatListPane.js
CHANGED
|
@@ -20,13 +20,21 @@ const MEETING = {
|
|
|
20
20
|
// Storage key for localStorage
|
|
21
21
|
const STORAGE_KEY = 'solidchat-chats'
|
|
22
22
|
|
|
23
|
-
// Default global
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
// Default global chats
|
|
24
|
+
const DEFAULT_CHATS = [
|
|
25
|
+
{
|
|
26
|
+
uri: 'https://solid-chat.solidweb.org/public/global/chat.ttl',
|
|
27
|
+
title: 'Solid Chat Global (solidweb.org)',
|
|
28
|
+
lastMessage: 'Welcome! Faster server.',
|
|
29
|
+
timestamp: '2025-12-31T12:00:00Z'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
uri: 'https://solid-chat.solidcommunity.net/public/global/chat.ttl',
|
|
33
|
+
title: 'Solid Chat Global (solidcommunity.net)',
|
|
34
|
+
lastMessage: 'Welcome to the global chat!',
|
|
35
|
+
timestamp: '2025-12-31T09:00:00Z'
|
|
36
|
+
}
|
|
37
|
+
]
|
|
30
38
|
|
|
31
39
|
// CSS styles
|
|
32
40
|
const styles = `
|
|
@@ -308,6 +316,47 @@ const styles = `
|
|
|
308
316
|
box-shadow: none;
|
|
309
317
|
}
|
|
310
318
|
|
|
319
|
+
/* Modal tabs */
|
|
320
|
+
.modal-tabs {
|
|
321
|
+
display: flex;
|
|
322
|
+
gap: 8px;
|
|
323
|
+
margin-bottom: 16px;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.modal-tab {
|
|
327
|
+
flex: 1;
|
|
328
|
+
padding: 8px 12px;
|
|
329
|
+
border: 1px solid var(--border);
|
|
330
|
+
border-radius: 6px;
|
|
331
|
+
background: var(--bg);
|
|
332
|
+
color: var(--text-secondary);
|
|
333
|
+
font-size: 13px;
|
|
334
|
+
font-weight: 500;
|
|
335
|
+
cursor: pointer;
|
|
336
|
+
transition: all 0.2s;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.modal-tab:hover {
|
|
340
|
+
background: var(--bg-hover);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.modal-tab.active {
|
|
344
|
+
background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%);
|
|
345
|
+
color: white;
|
|
346
|
+
border-color: transparent;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.modal-url-preview {
|
|
350
|
+
font-size: 11px;
|
|
351
|
+
color: var(--text-muted);
|
|
352
|
+
word-break: break-all;
|
|
353
|
+
padding: 8px 12px;
|
|
354
|
+
background: var(--bg-hover);
|
|
355
|
+
border-radius: 6px;
|
|
356
|
+
margin-bottom: 16px;
|
|
357
|
+
display: none;
|
|
358
|
+
}
|
|
359
|
+
|
|
311
360
|
/* Delete button on chat item */
|
|
312
361
|
.chat-item-delete {
|
|
313
362
|
width: 24px;
|
|
@@ -364,18 +413,21 @@ function loadChatList() {
|
|
|
364
413
|
chatList = []
|
|
365
414
|
}
|
|
366
415
|
|
|
367
|
-
// Add default global
|
|
416
|
+
// Add default global chats if list is empty
|
|
368
417
|
if (chatList.length === 0) {
|
|
369
|
-
chatList.push({ ...
|
|
418
|
+
chatList.push(...DEFAULT_CHATS.map(c => ({ ...c })))
|
|
370
419
|
saveChatList()
|
|
371
420
|
}
|
|
372
421
|
|
|
373
|
-
// Ensure default
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
chatList.
|
|
377
|
-
|
|
422
|
+
// Ensure all default chats exist in list
|
|
423
|
+
let added = false
|
|
424
|
+
for (const defaultChat of DEFAULT_CHATS) {
|
|
425
|
+
if (!chatList.some(c => c.uri === defaultChat.uri)) {
|
|
426
|
+
chatList.unshift({ ...defaultChat })
|
|
427
|
+
added = true
|
|
428
|
+
}
|
|
378
429
|
}
|
|
430
|
+
if (added) saveChatList()
|
|
379
431
|
|
|
380
432
|
return chatList
|
|
381
433
|
}
|
|
@@ -535,31 +587,108 @@ function renderChatList() {
|
|
|
535
587
|
})
|
|
536
588
|
}
|
|
537
589
|
|
|
538
|
-
// Show add chat modal
|
|
539
|
-
function showAddModal(dom) {
|
|
590
|
+
// Show add/create chat modal
|
|
591
|
+
function showAddModal(dom, webId) {
|
|
540
592
|
const overlay = dom.createElement('div')
|
|
541
593
|
overlay.className = 'modal-overlay'
|
|
542
594
|
|
|
543
595
|
const modal = dom.createElement('div')
|
|
544
596
|
modal.className = 'modal'
|
|
545
597
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
<
|
|
552
|
-
|
|
553
|
-
|
|
598
|
+
// Check if user is logged in (can create chats)
|
|
599
|
+
const canCreate = !!webId
|
|
600
|
+
|
|
601
|
+
if (canCreate) {
|
|
602
|
+
modal.innerHTML = `
|
|
603
|
+
<div class="modal-title">Add or Create Chat</div>
|
|
604
|
+
<div class="modal-tabs">
|
|
605
|
+
<button class="modal-tab active" data-tab="add">Add Existing</button>
|
|
606
|
+
<button class="modal-tab" data-tab="create">Create New</button>
|
|
607
|
+
</div>
|
|
608
|
+
<div class="modal-tab-content" data-content="add">
|
|
609
|
+
<input type="url" class="modal-input" id="addUrlInput" placeholder="Enter chat URL..." />
|
|
610
|
+
</div>
|
|
611
|
+
<div class="modal-tab-content" data-content="create" style="display: none;">
|
|
612
|
+
<input type="text" class="modal-input" id="createTitleInput" placeholder="Chat title (e.g. Team Standup)" />
|
|
613
|
+
<select class="modal-input" id="createLocationSelect">
|
|
614
|
+
<option value="/public/chats/">Public (anyone with link)</option>
|
|
615
|
+
<option value="/private/chats/">Private (only you)</option>
|
|
616
|
+
</select>
|
|
617
|
+
<div class="modal-url-preview" id="urlPreview"></div>
|
|
618
|
+
</div>
|
|
619
|
+
<div class="modal-buttons">
|
|
620
|
+
<button class="modal-btn modal-btn-cancel">Cancel</button>
|
|
621
|
+
<button class="modal-btn modal-btn-add" id="actionBtn">Add</button>
|
|
622
|
+
</div>
|
|
623
|
+
`
|
|
624
|
+
} else {
|
|
625
|
+
modal.innerHTML = `
|
|
626
|
+
<div class="modal-title">Add Chat</div>
|
|
627
|
+
<input type="url" class="modal-input" id="addUrlInput" placeholder="Enter chat URL..." />
|
|
628
|
+
<p style="font-size: 12px; color: #a0aec0; margin-bottom: 16px;">Login to create new chats on your pod.</p>
|
|
629
|
+
<div class="modal-buttons">
|
|
630
|
+
<button class="modal-btn modal-btn-cancel">Cancel</button>
|
|
631
|
+
<button class="modal-btn modal-btn-add" id="actionBtn">Add</button>
|
|
632
|
+
</div>
|
|
633
|
+
`
|
|
634
|
+
}
|
|
554
635
|
|
|
555
636
|
overlay.appendChild(modal)
|
|
556
637
|
dom.body.appendChild(overlay)
|
|
557
638
|
|
|
558
|
-
const input = modal.querySelector('.modal-input')
|
|
559
639
|
const cancelBtn = modal.querySelector('.modal-btn-cancel')
|
|
560
|
-
const
|
|
640
|
+
const actionBtn = modal.querySelector('#actionBtn')
|
|
641
|
+
const addUrlInput = modal.querySelector('#addUrlInput')
|
|
642
|
+
|
|
643
|
+
let currentTab = 'add'
|
|
644
|
+
|
|
645
|
+
// Tab switching
|
|
646
|
+
if (canCreate) {
|
|
647
|
+
const tabs = modal.querySelectorAll('.modal-tab')
|
|
648
|
+
const createTitleInput = modal.querySelector('#createTitleInput')
|
|
649
|
+
const createLocationSelect = modal.querySelector('#createLocationSelect')
|
|
650
|
+
const urlPreview = modal.querySelector('#urlPreview')
|
|
651
|
+
|
|
652
|
+
// Update URL preview
|
|
653
|
+
const updatePreview = () => {
|
|
654
|
+
const title = createTitleInput.value.trim()
|
|
655
|
+
const location = createLocationSelect.value
|
|
656
|
+
if (title && webId) {
|
|
657
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
|
|
658
|
+
const podRoot = getPodRootFromWebId(webId)
|
|
659
|
+
const fullUrl = `${podRoot}${location.slice(1)}${slug}.ttl`
|
|
660
|
+
urlPreview.textContent = fullUrl
|
|
661
|
+
urlPreview.style.display = 'block'
|
|
662
|
+
} else {
|
|
663
|
+
urlPreview.style.display = 'none'
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
createTitleInput.addEventListener('input', updatePreview)
|
|
668
|
+
createLocationSelect.addEventListener('change', updatePreview)
|
|
669
|
+
|
|
670
|
+
tabs.forEach(tab => {
|
|
671
|
+
tab.onclick = () => {
|
|
672
|
+
tabs.forEach(t => t.classList.remove('active'))
|
|
673
|
+
tab.classList.add('active')
|
|
674
|
+
currentTab = tab.dataset.tab
|
|
675
|
+
|
|
676
|
+
modal.querySelectorAll('.modal-tab-content').forEach(c => {
|
|
677
|
+
c.style.display = c.dataset.content === currentTab ? 'block' : 'none'
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
actionBtn.textContent = currentTab === 'add' ? 'Add' : 'Create'
|
|
561
681
|
|
|
562
|
-
|
|
682
|
+
if (currentTab === 'add') {
|
|
683
|
+
addUrlInput.focus()
|
|
684
|
+
} else {
|
|
685
|
+
createTitleInput.focus()
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
})
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
addUrlInput?.focus()
|
|
563
692
|
|
|
564
693
|
const close = () => {
|
|
565
694
|
overlay.remove()
|
|
@@ -571,26 +700,89 @@ function showAddModal(dom) {
|
|
|
571
700
|
|
|
572
701
|
cancelBtn.onclick = close
|
|
573
702
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
onSelectCallback
|
|
703
|
+
actionBtn.onclick = async () => {
|
|
704
|
+
if (currentTab === 'add') {
|
|
705
|
+
const uri = addUrlInput.value.trim()
|
|
706
|
+
if (uri) {
|
|
707
|
+
addChat(uri)
|
|
708
|
+
activeUri = uri
|
|
709
|
+
renderChatList()
|
|
710
|
+
if (onSelectCallback) {
|
|
711
|
+
onSelectCallback(uri)
|
|
712
|
+
}
|
|
713
|
+
close()
|
|
714
|
+
}
|
|
715
|
+
} else {
|
|
716
|
+
// Create new chat
|
|
717
|
+
const createTitleInput = modal.querySelector('#createTitleInput')
|
|
718
|
+
const createLocationSelect = modal.querySelector('#createLocationSelect')
|
|
719
|
+
|
|
720
|
+
const title = createTitleInput.value.trim()
|
|
721
|
+
const location = createLocationSelect.value
|
|
722
|
+
|
|
723
|
+
if (!title) {
|
|
724
|
+
createTitleInput.focus()
|
|
725
|
+
return
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
|
|
729
|
+
const podRoot = getPodRootFromWebId(webId)
|
|
730
|
+
const chatUrl = `${podRoot}${location.slice(1)}${slug}.ttl`
|
|
731
|
+
|
|
732
|
+
actionBtn.disabled = true
|
|
733
|
+
actionBtn.textContent = 'Creating...'
|
|
734
|
+
|
|
735
|
+
try {
|
|
736
|
+
// Use the createChat from index.html
|
|
737
|
+
if (window.solidChat?.createChat) {
|
|
738
|
+
await window.solidChat.createChat(chatUrl, title)
|
|
739
|
+
} else {
|
|
740
|
+
throw new Error('createChat not available')
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
addChat(chatUrl, title)
|
|
744
|
+
activeUri = chatUrl
|
|
745
|
+
renderChatList()
|
|
746
|
+
|
|
747
|
+
// Copy share link
|
|
748
|
+
if (window.solidChat?.copyShareLink) {
|
|
749
|
+
window.solidChat.copyShareLink(chatUrl)
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (onSelectCallback) {
|
|
753
|
+
onSelectCallback(chatUrl)
|
|
754
|
+
}
|
|
755
|
+
close()
|
|
756
|
+
} catch (e) {
|
|
757
|
+
console.error('Failed to create chat:', e)
|
|
758
|
+
alert('Failed to create chat: ' + e.message)
|
|
759
|
+
actionBtn.disabled = false
|
|
760
|
+
actionBtn.textContent = 'Create'
|
|
582
761
|
}
|
|
583
|
-
close()
|
|
584
762
|
}
|
|
585
763
|
}
|
|
586
764
|
|
|
587
|
-
|
|
765
|
+
// Handle Enter key
|
|
766
|
+
const handleKeydown = (e) => {
|
|
588
767
|
if (e.key === 'Enter') {
|
|
589
|
-
|
|
768
|
+
actionBtn.click()
|
|
590
769
|
} else if (e.key === 'Escape') {
|
|
591
770
|
close()
|
|
592
771
|
}
|
|
593
772
|
}
|
|
773
|
+
|
|
774
|
+
addUrlInput?.addEventListener('keydown', handleKeydown)
|
|
775
|
+
modal.querySelector('#createTitleInput')?.addEventListener('keydown', handleKeydown)
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Get pod root from WebID (simple extraction)
|
|
779
|
+
function getPodRootFromWebId(webId) {
|
|
780
|
+
try {
|
|
781
|
+
const url = new URL(webId)
|
|
782
|
+
return `${url.protocol}//${url.host}/`
|
|
783
|
+
} catch {
|
|
784
|
+
return ''
|
|
785
|
+
}
|
|
594
786
|
}
|
|
595
787
|
|
|
596
788
|
// Discover chats from Type Index
|
|
@@ -670,8 +862,8 @@ export const chatListPane = {
|
|
|
670
862
|
const addBtn = dom.createElement('button')
|
|
671
863
|
addBtn.className = 'add-chat-btn'
|
|
672
864
|
addBtn.textContent = '+'
|
|
673
|
-
addBtn.title = 'Add chat'
|
|
674
|
-
addBtn.onclick = () => showAddModal(dom)
|
|
865
|
+
addBtn.title = 'Add or create chat'
|
|
866
|
+
addBtn.onclick = () => showAddModal(dom, options.webId)
|
|
675
867
|
|
|
676
868
|
header.appendChild(title)
|
|
677
869
|
header.appendChild(addBtn)
|
|
@@ -739,4 +931,4 @@ export const chatListPane = {
|
|
|
739
931
|
}
|
|
740
932
|
|
|
741
933
|
// Export for use in index.html
|
|
742
|
-
export { addChat, updateChatPreview }
|
|
934
|
+
export { addChat, removeChat, updateChatPreview }
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { longChatPane, chatListPane, addChat, removeChat } from './index.js'
|
|
3
|
+
|
|
4
|
+
describe('solid-chat exports', () => {
|
|
5
|
+
it('exports longChatPane', () => {
|
|
6
|
+
expect(longChatPane).toBeDefined()
|
|
7
|
+
expect(longChatPane.name).toBe('long-chat')
|
|
8
|
+
expect(typeof longChatPane.render).toBe('function')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('exports chatListPane', () => {
|
|
12
|
+
expect(chatListPane).toBeDefined()
|
|
13
|
+
expect(chatListPane.name).toBe('chat-list')
|
|
14
|
+
expect(typeof chatListPane.render).toBe('function')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('exports addChat and removeChat', () => {
|
|
18
|
+
expect(typeof addChat).toBe('function')
|
|
19
|
+
expect(typeof removeChat).toBe('function')
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('chatListPane', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
localStorage.clear()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('renders a container element', () => {
|
|
29
|
+
const context = { dom: document, session: { store: {} } }
|
|
30
|
+
const options = { onSelectChat: () => {} }
|
|
31
|
+
const element = chatListPane.render(context, options)
|
|
32
|
+
|
|
33
|
+
expect(element).toBeInstanceOf(HTMLElement)
|
|
34
|
+
expect(element.className).toBe('chat-list-pane')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('includes default global chat', () => {
|
|
38
|
+
const context = { dom: document, session: { store: {} } }
|
|
39
|
+
const options = { onSelectChat: () => {} }
|
|
40
|
+
const element = chatListPane.render(context, options)
|
|
41
|
+
|
|
42
|
+
expect(element.textContent).toContain('Solid Chat Global')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('addChat persists to localStorage', () => {
|
|
46
|
+
const context = { dom: document, session: { store: {} } }
|
|
47
|
+
chatListPane.render(context, { onSelectChat: () => {} })
|
|
48
|
+
|
|
49
|
+
addChat('https://example.com/chat.ttl', 'Test Chat')
|
|
50
|
+
|
|
51
|
+
const stored = JSON.parse(localStorage.getItem('solidchat-chats'))
|
|
52
|
+
expect(stored.some(c => c.uri === 'https://example.com/chat.ttl')).toBe(true)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('removeChat removes from localStorage', () => {
|
|
56
|
+
const context = { dom: document, session: { store: {} } }
|
|
57
|
+
chatListPane.render(context, { onSelectChat: () => {} })
|
|
58
|
+
|
|
59
|
+
addChat('https://example.com/chat.ttl', 'Test Chat')
|
|
60
|
+
removeChat('https://example.com/chat.ttl')
|
|
61
|
+
|
|
62
|
+
const stored = JSON.parse(localStorage.getItem('solidchat-chats'))
|
|
63
|
+
expect(stored.some(c => c.uri === 'https://example.com/chat.ttl')).toBe(false)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('longChatPane', () => {
|
|
68
|
+
it('has label function', () => {
|
|
69
|
+
expect(typeof longChatPane.label).toBe('function')
|
|
70
|
+
})
|
|
71
|
+
})
|