feat: implement approved Hope primary conversation shell
This commit is contained in:
1 parent
2be5725fbd
commit
cd7d7d6251
4 files changed
+1192
-1747
No files matched your search
+30
-27
@@ -73,6 +73,7 @@ def test_chat_crud_replay_safe_markdown_and_preserved_branches():
|
||||
page.goto('https://hope.test/'); page.wait_for_load_state('networkidle')
|
||||
page.get_by_role('button',name='A saved thought',exact=True).click()
|
||||
expect(page.locator('#chatTimeline')).to_contain_text('Original question')
|
||||
page.evaluate("if (!document.getElementById('conversationActions').matches(':popover-open')) document.getElementById('conversationActions').showPopover()")
|
||||
page.locator('#pinConversation').click(); expect(page.locator('#pinConversation')).to_have_text('Unpin')
|
||||
page.locator('#primaryInput').fill('A new question'); page.locator('#primarySend').click()
|
||||
expect(page.locator('#chatTimeline')).to_contain_text('Answer')
|
||||
@@ -90,11 +91,12 @@ def test_chat_crud_replay_safe_markdown_and_preserved_branches():
|
||||
expect(page.locator('#chatTitle')).to_have_text('New branch')
|
||||
expect(page.locator('#chatTimeline')).to_contain_text('Revised question')
|
||||
assert messages['c1'][0]['content'] == 'Original question'
|
||||
page.locator('#openPreferences').click(); page.locator('#paletteSelect').select_option('ocean')
|
||||
page.locator('#openPreferences').click(); expect(page.locator('#preferencesMessage')).to_have_text(''); page.locator('#paletteSelect').select_option('ocean')
|
||||
page.get_by_role('button',name='Save appearance').click(); expect(page.locator('html')).to_have_attribute('data-palette','ocean')
|
||||
page.locator('#closePreferences').click(); page.screenshot(path='/tmp/hope-chat-desktop.png')
|
||||
page.locator('#settingsClose').click(); page.screenshot(path='/tmp/hope-chat-desktop.png')
|
||||
page.evaluate("if (!document.getElementById('conversationActions').matches(':popover-open')) document.getElementById('conversationActions').showPopover()")
|
||||
page.locator('#deleteConversation').click(); page.locator('#chatActionSave').click()
|
||||
expect(page.locator('#chatTitle')).to_have_text('New chat'); assert 'branch' not in conversations
|
||||
expect(page.locator('#chatTitle')).to_have_text('New conversation'); assert 'branch' not in conversations
|
||||
assert errors == []
|
||||
browser.close()
|
||||
|
||||
@@ -106,17 +108,9 @@ def test_mobile_drawer_keyboard_voice_and_logout():
|
||||
page.goto('https://hope.test/'); page.wait_for_load_state('networkidle')
|
||||
page.locator('#drawerOpen').click(); expect(page.locator('#drawerOpen')).to_have_attribute('aria-expanded','true')
|
||||
page.keyboard.press('Escape'); expect(page.locator('#drawerOpen')).to_be_focused()
|
||||
page.locator('#drawerOpen').click(); page.locator('#openVoice').click()
|
||||
expect(page.locator('#startBtn')).to_be_visible(); expect(page.locator('#settingsBtn')).to_be_visible()
|
||||
page.locator('#backToChat').click(); expect(page.locator('#primaryChat')).to_be_visible()
|
||||
page.evaluate("window.dispatchEvent(new CustomEvent('hope:voice-state',{detail:{active:true}}))")
|
||||
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()
|
||||
page.locator('#drawerOpen').click(); page.locator('#accountMenuBtn').click()
|
||||
expect(page.locator('#settingsDialog')).to_be_visible()
|
||||
page.locator('#logoutBtn').click()
|
||||
expect(page.locator('#loginGate')).to_be_visible()
|
||||
assert 'Original question' not in page.locator('#chatTimeline').inner_text()
|
||||
browser.close()
|
||||
@@ -138,12 +132,12 @@ def test_archived_move_sync_conflict_and_mobile_actions():
|
||||
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')
|
||||
expect(page.locator('#chatTitle')).to_have_text('New conversation')
|
||||
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')
|
||||
expect(page.locator('#chatTitle')).to_have_text('New conversation')
|
||||
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()
|
||||
@@ -154,7 +148,8 @@ def test_archived_move_sync_conflict_and_mobile_actions():
|
||||
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()
|
||||
page.locator('#conversationMenu').click(); page.evaluate("if (!document.getElementById('conversationActions').matches(':popover-open')) document.getElementById('conversationActions').showPopover()")
|
||||
page.locator('#pinConversation').click()
|
||||
expect(page.locator('#chatTitle')).to_have_text('Newest revision')
|
||||
expect(page.locator('#chatNotice')).to_contain_text('Conversation refreshed')
|
||||
browser.close()
|
||||
@@ -180,7 +175,7 @@ def test_voice_history_refresh_keyboard_focus_and_large_text():
|
||||
page.screenshot(path='/tmp/hope-chat-opal-desktop.png')
|
||||
page.keyboard.press('Control+k'); expect(page.locator('#conversationSearch')).to_be_focused()
|
||||
page.keyboard.press('Escape')
|
||||
page.locator('#openPreferences').click(); expect(page.locator('#preferencesDialog')).to_be_visible()
|
||||
page.locator('#openPreferences').click(); expect(page.locator('#settingsDialog')).to_be_visible()
|
||||
page.keyboard.press('Escape'); expect(page.locator('#openPreferences')).to_be_focused()
|
||||
# Native controls remain usable at large text and narrow effective viewport.
|
||||
page.set_viewport_size({'width':640,'height':900})
|
||||
@@ -202,8 +197,10 @@ def test_qualified_research_and_private_export():
|
||||
page.locator('#primaryInput').fill('Research this topic'); page.locator('#primarySend').click()
|
||||
expect(page.locator('#chatTimeline')).to_contain_text('Answer')
|
||||
assert next(body for method,path,body in calls if method == 'POST' and path.endswith('/messages'))['mode'] == 'research'
|
||||
page.evaluate("if (!document.getElementById('conversationActions').matches(':popover-open')) document.getElementById('conversationActions').showPopover()")
|
||||
with page.expect_download() as info: page.locator('#exportMarkdown').click()
|
||||
assert info.value.suggested_filename.endswith('.md')
|
||||
page.evaluate("if (!document.getElementById('conversationActions').matches(':popover-open')) document.getElementById('conversationActions').showPopover()")
|
||||
with page.expect_download() as info: page.locator('#exportJSON').click()
|
||||
assert info.value.suggested_filename.endswith('.json')
|
||||
assert all('/api/conversations/' in path for method,path,body in calls if path.endswith('/export'))
|
||||
@@ -219,6 +216,7 @@ def test_immediate_pin_after_response_uses_final_server_revision():
|
||||
expect(page.locator('#chatTitle')).to_have_text('A saved thought')
|
||||
page.locator('#primaryInput').fill('Finish and immediately pin'); page.locator('#primarySend').click()
|
||||
expect(page.locator('#chatTimeline')).to_contain_text('Answer')
|
||||
page.evaluate("if (!document.getElementById('conversationActions').matches(':popover-open')) document.getElementById('conversationActions').showPopover()")
|
||||
page.locator('#pinConversation').click()
|
||||
expect(page.locator('#pinConversation')).to_have_text('Unpin')
|
||||
assert conversations['c1']['pinned'] is True
|
||||
@@ -228,25 +226,30 @@ def test_immediate_pin_after_response_uses_final_server_revision():
|
||||
browser.close()
|
||||
|
||||
|
||||
def test_realtime_voice_is_prominent_on_desktop_and_mobile():
|
||||
def test_realtime_voice_is_inline_in_same_thread_and_creates_real_conversation():
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
for width in [1280,390]:
|
||||
for width in [1440, 390, 320]:
|
||||
page = browser.new_page(viewport={'width':width,'height':900})
|
||||
errors = []; page.on('pageerror', lambda error: errors.append(str(error)))
|
||||
calls, streams, conversations, messages = fixture(page)
|
||||
page.add_init_script("window.addEventListener('hope:start-voice', event => {event.stopImmediatePropagation(); window.startedVoice = event.detail.conversation_id; window.dispatchEvent(new CustomEvent('hope:voice-state', {detail:{active:true,conversation_id:event.detail.conversation_id}}));}, true)")
|
||||
page.goto('https://hope.test/'); page.wait_for_load_state('networkidle')
|
||||
expect(page.get_by_role('button',name='Talk to Hope',exact=True)).to_be_visible()
|
||||
# Replace only voice transport; durable conversation creation still uses real chat.js.
|
||||
|
||||
expect(page.locator('#primaryComposer #talkToHope')).to_be_visible()
|
||||
assert page.locator('.chat-topbar #talkToHope').count() == 0
|
||||
page.locator('#primaryInput').fill('Keep this draft while I talk')
|
||||
page.locator('#talkToHope').click()
|
||||
expect(page.locator('#legacyVoice')).to_be_visible()
|
||||
expect(page.locator('#startBtn')).to_be_visible()
|
||||
expect(page.locator('#voiceModeBtn')).to_have_attribute('aria-pressed','true')
|
||||
expect(page.locator('#backToChat')).to_be_visible()
|
||||
page.locator('#backToChat').click()
|
||||
expect(page.locator('#primaryChat')).to_be_visible()
|
||||
expect(page.locator('#primaryInput')).to_have_value('Keep this draft while I talk')
|
||||
expect(page.locator('#talkToHope')).to_be_visible()
|
||||
assert not any(path == '/token' for method,path,body in calls)
|
||||
expect(page.locator('#primaryInput')).to_be_enabled()
|
||||
expect(page.locator('#primarySend')).to_be_disabled()
|
||||
assert page.evaluate('window.startedVoice') == 'c2'
|
||||
assert any(method == 'POST' and path == '/api/conversations' for method,path,body in calls)
|
||||
assert page.evaluate('document.documentElement.scrollWidth <= innerWidth')
|
||||
page.screenshot(path=f'/tmp/hope-talk-visible-{width}.png')
|
||||
assert not errors
|
||||
page.close()
|
||||
browser.close()
|
||||
Reference in new issue
Block a user