feat: complete chat archive sync and mobile actions
This commit is contained in:
1 parent
bfbaef8c68
commit
fdf905eb7e
4 files changed
+90
-18
No files matched your search
@@ -1,6 +1,6 @@
|
||||
"""Offline Chromium tests of real Hope assets; no NAS or live provider."""
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from playwright.sync_api import sync_playwright, expect
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -22,15 +22,16 @@ def fixture(page, replay=False):
|
||||
elif path == '/api/preferences':
|
||||
if method == 'PUT': preferences.update(body); preferences['revision'] += 1
|
||||
data = {'preferences': preferences}
|
||||
elif path == '/api/workspaces': data = {'workspaces': []}
|
||||
elif path == '/api/workspaces': data = {'workspaces': [dict(id='w1',name='Writing',revision=1,instructions='Keep it concise')]}
|
||||
elif path == '/api/sync': data = {'changes':[{'kind':'conversation','id':'c1','data':conversations['c1']},{'kind':'preferences','data':preferences}], 'cursor':1}
|
||||
elif path == '/api/conversations':
|
||||
if method == 'POST':
|
||||
conversations['c2'] = dict(conversation, id='c2', title=body['title']); messages['c2'] = []
|
||||
data = {'conversation': conversations['c2']}
|
||||
else: data = {'conversations': list(conversations.values()), 'next_cursor': None}
|
||||
else: data = {'conversations': [c for c in conversations.values() if c['archived'] == (parse_qs(urlparse(r.request.url).query).get('archived',['false'])[0] == 'true')], 'next_cursor': None}
|
||||
elif path.endswith('/events'):
|
||||
streams.append(r.request.url)
|
||||
message = 'id: 1\nevent: message\ndata: {"message":{"id":"a1","role":"assistant","content":"## Answer\\n**Safe** and `clear`.\\n<script>window.injected=true</script>\\n[bad](javascript:alert(1))","status":"completed"}}\n\n'
|
||||
message = 'id: 1\nevent: message\ndata: {"message":{"id":"a1","role":"assistant","content":"## Answer\\n| Name | Value |\\n| --- | --- |\\n| <img src=x> | **safe** |\\n**Safe** and `clear`.\\n<script>window.injected=true</script>\\n[bad](javascript:alert(1))","status":"completed"}}\n\n'
|
||||
done = 'id: 2\nevent: done\ndata: {"run":{"id":"r1","status":"completed"}}\n\n'
|
||||
r.fulfill(body=message if replay and len(streams) == 1 else message + done, content_type='text/event-stream'); return
|
||||
elif path.endswith('/messages'):
|
||||
@@ -70,6 +71,8 @@ def test_chat_crud_replay_safe_markdown_and_preserved_branches():
|
||||
expect(page.locator('#primaryInput')).to_be_enabled(timeout=10000)
|
||||
assert len(streams) == 2 and streams[-1].endswith('after=1')
|
||||
assert page.locator('[data-message-id="a1"]').count() == 1
|
||||
assert page.locator('#chatTimeline table').count() == 1
|
||||
assert page.locator('#chatTimeline img').count() == 0
|
||||
assert page.locator('#chatTimeline script').count() == 0
|
||||
assert page.locator('#chatTimeline a[href^="javascript:"]').count() == 0
|
||||
assert page.evaluate('window.injected') is None
|
||||
@@ -102,9 +105,48 @@ def test_mobile_drawer_keyboard_voice_and_logout():
|
||||
expect(page.locator('#primaryInput')).to_be_disabled()
|
||||
page.evaluate("window.dispatchEvent(new CustomEvent('hope:voice-state',{detail:{active:false}}))")
|
||||
expect(page.locator('#primaryInput')).to_be_enabled()
|
||||
page.wait_for_function("document.getElementById('chatSidebar').getBoundingClientRect().right <= 0")
|
||||
page.screenshot(path='/tmp/hope-chat-mobile.png')
|
||||
assert page.evaluate('document.documentElement.scrollWidth <= innerWidth')
|
||||
page.locator('#drawerOpen').click(); page.locator('#openVoice').click(); page.locator('#logoutBtn').click()
|
||||
expect(page.locator('#loginGate')).to_be_visible()
|
||||
assert 'Original question' not in page.locator('#chatTimeline').inner_text()
|
||||
browser.close()
|
||||
|
||||
|
||||
def test_archived_move_sync_conflict_and_mobile_actions():
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page(viewport={'width':390,'height':844})
|
||||
calls, streams, conversations, messages = fixture(page)
|
||||
page.goto('https://hope.test/'); page.wait_for_load_state('networkidle')
|
||||
expect(page.locator('#conversationMenu')).to_be_hidden()
|
||||
page.locator('#drawerOpen').click(); page.get_by_role('button',name='A saved thought',exact=True).click()
|
||||
page.locator('#conversationMenu').click(); expect(page.locator('#moveConversation')).to_be_visible()
|
||||
page.locator('#moveConversation').click(); page.locator('#chatWorkspaceSelect').select_option('w1'); page.locator('#chatActionSave').click()
|
||||
expect(page.locator('#chatActionDialog')).not_to_be_visible()
|
||||
assert conversations['c1']['workspace_id'] == 'w1'
|
||||
conversations['c1'].update(title='Changed elsewhere', revision=4)
|
||||
page.evaluate("document.dispatchEvent(new Event('visibilitychange'))")
|
||||
expect(page.locator('#chatTitle')).to_have_text('Changed elsewhere')
|
||||
page.locator('#conversationMenu').click(); page.locator('#archiveConversation').click(); page.locator('#chatActionSave').click()
|
||||
expect(page.locator('#chatTitle')).to_have_text('New chat')
|
||||
page.locator('#drawerOpen').click(); page.locator('#showArchived').click()
|
||||
page.get_by_role('button',name='Changed elsewhere',exact=True).click()
|
||||
page.locator('#conversationMenu').click(); expect(page.locator('#archiveConversation')).to_have_text('Unarchive')
|
||||
page.locator('#archiveConversation').click(); page.locator('#chatActionSave').click()
|
||||
expect(page.locator('#chatTitle')).to_have_text('New chat')
|
||||
assert conversations['c1']['archived'] is False
|
||||
# A stale optimistic write refreshes rather than overwriting another client's title.
|
||||
page.locator('#drawerOpen').click(); page.locator('#showArchived').click()
|
||||
page.get_by_role('button',name='Changed elsewhere',exact=True).click()
|
||||
def conflict(route):
|
||||
if route.request.method == 'PATCH':
|
||||
conversations['c1'].update(title='Newest revision',revision=9)
|
||||
route.fulfill(status=409,json={'error':'Stale revision'})
|
||||
else: route.fallback()
|
||||
page.route('https://hope.test/api/conversations/c1', conflict)
|
||||
page.locator('#conversationMenu').click(); page.locator('#pinConversation').click()
|
||||
expect(page.locator('#chatTitle')).to_have_text('Newest revision')
|
||||
expect(page.locator('#chatNotice')).to_contain_text('Conversation refreshed')
|
||||
browser.close()
|
||||
Reference in new issue
Block a user