fix(ui): restore rich text controls and accessible research map

This commit is contained in:
Alexandre Teixeira
2026-09-22 23:26:43 +01:00
parent b9cccb93ac
commit 4520b4f6f0
11 changed files with 60 additions and 34 deletions
+10
View File
@@ -7521,6 +7521,7 @@ import { attachColorPicker } from './colorPicker.js?v=20260910eyedropper1';
<button type="button" class="md-toolbar-rich-only" data-md="subscript" title="Subscript" style="display:none"><span class="rich-script-icon">x<sub>2</sub></span></button> <button type="button" class="md-toolbar-rich-only" data-md="subscript" title="Subscript" style="display:none"><span class="rich-script-icon">x<sub>2</sub></span></button>
<span class="md-toolbar-sep md-toolbar-edit-only"></span> <span class="md-toolbar-sep md-toolbar-edit-only"></span>
<button type="button" class="md-dd-toggle md-toolbar-edit-only" data-dd="heading" title="Heading (Ctrl+Alt+1-6)"><b>H</b><svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></button> <button type="button" class="md-dd-toggle md-toolbar-edit-only" data-dd="heading" title="Heading (Ctrl+Alt+1-6)"><b>H</b><svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></button>
<button type="button" class="md-dd-toggle md-toolbar-rich-only" data-dd="font" title="Font family" aria-label="Font family" style="display:none"><span>Aa</span><svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></button>
<span id="md-toolbar-sep-after-heading" class="md-toolbar-sep md-toolbar-manual-sep md-toolbar-edit-only" aria-hidden="true"></span> <span id="md-toolbar-sep-after-heading" class="md-toolbar-sep md-toolbar-manual-sep md-toolbar-edit-only" aria-hidden="true"></span>
<button type="button" class="md-dd-toggle md-toolbar-edit-only" data-dd="list" title="Bulleted list"><svg class="rich-list-bullet-icon" width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="4" cy="6" r="1.5"/><circle cx="4" cy="12" r="1.5"/><circle cx="4" cy="18" r="1.5"/><path d="M9 5h12v2H9zM9 11h12v2H9zM9 17h12v2H9z"/></svg><svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></button> <button type="button" class="md-dd-toggle md-toolbar-edit-only" data-dd="list" title="Bulleted list"><svg class="rich-list-bullet-icon" width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="4" cy="6" r="1.5"/><circle cx="4" cy="12" r="1.5"/><circle cx="4" cy="18" r="1.5"/><path d="M9 5h12v2H9zM9 11h12v2H9zM9 17h12v2H9z"/></svg><svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></button>
<span class="md-toolbar-sep md-toolbar-edit-only"></span> <span class="md-toolbar-sep md-toolbar-edit-only"></span>
@@ -7717,6 +7718,7 @@ import { attachColorPicker } from './colorPicker.js?v=20260910eyedropper1';
} }
_renderDocumentStats(); _renderDocumentStats();
statsPopover.hidden = false; statsPopover.hidden = false;
statsPopover.style.zIndex = String(topPortalZ());
// The editor pane clips overflow, so a footer-anchored absolute // The editor pane clips overflow, so a footer-anchored absolute
// popover can disappear underneath the document. Float it against // popover can disappear underneath the document. Float it against
// the viewport and place it above the stats button. // the viewport and place it above the stats button.
@@ -10693,6 +10695,13 @@ import { attachColorPicker } from './colorPicker.js?v=20260910eyedropper1';
['h5', 'Heading 5', 'H5'], ['h5', 'Heading 5', 'H5'],
['h6', 'Heading 6', 'H6'], ['h6', 'Heading 6', 'H6'],
], ],
font: [
['fontname:Arial', 'Arial', 'Aa'],
['fontname:Georgia', 'Georgia', 'Gg'],
['fontname:Times New Roman', 'Times New Roman', 'Tt'],
['fontname:Verdana', 'Verdana', 'Vv'],
['fontname:Courier New', 'Courier New', 'Cc'],
],
code: [['code', 'Inline code', '`'], ['codeblock', 'Code block', '```']], code: [['code', 'Inline code', '`'], ['codeblock', 'Code block', '```']],
list: [ list: [
['ul', 'Bullet list', '•'], ['ul', 'Bullet list', '•'],
@@ -11112,6 +11121,7 @@ import { attachColorPicker } from './colorPicker.js?v=20260910eyedropper1';
'#md-toolbar-sep-after-ai-reply', '#md-toolbar-sep-after-ai-reply',
'#doc-ai-writing-btn', '#doc-ai-writing-btn',
'[data-dd="heading"]', '[data-dd="heading"]',
'[data-dd="font"]',
'#md-toolbar-sep-after-heading', '#md-toolbar-sep-after-heading',
'[data-dd="textsize"]', '[data-dd="textsize"]',
], ],
+1 -1
View File
@@ -38,7 +38,7 @@ export default function createResearchSynapse(container, opts = {}) {
<span class="rs-sep">·</span> <span class="rs-sep">·</span>
<span class="rs-timer">00:00</span> <span class="rs-timer">00:00</span>
</div> </div>
<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet"> <svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Live research map">
<g class="rs-guide" aria-hidden="true"> <g class="rs-guide" aria-hidden="true">
<ellipse cx="${cx}" cy="${cy}" rx="116" ry="72"></ellipse> <ellipse cx="${cx}" cy="${cy}" rx="116" ry="72"></ellipse>
<ellipse cx="${cx}" cy="${cy}" rx="58" ry="36"></ellipse> <ellipse cx="${cx}" cy="${cy}" rx="58" ry="36"></ellipse>
+1 -2
View File
@@ -9431,7 +9431,7 @@ pre { background: var(--code-bg, var(--hl-bg, #282c34)) !important; }
} }
.compare-score-label { .compare-score-label {
position: relative; position: relative;
top: 0; top: -2px;
display: inline-block; display: inline-block;
} }
.compare-vote-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } .compare-vote-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); }
@@ -39084,7 +39084,6 @@ body.doc-find-active mark.doc-find-mark.current {
position: fixed; position: fixed;
right: auto; right: auto;
bottom: auto; bottom: auto;
z-index: 100000;
width: 190px; width: 190px;
padding: 8px 10px; padding: 8px 10px;
border: 1px solid var(--border); border: 1px solid var(--border);
+4 -4
View File
@@ -200,7 +200,7 @@ def test_compare_panes_surface_compact_result_summary():
assert "font-variant-numeric: tabular-nums;" in styles assert "font-variant-numeric: tabular-nums;" in styles
def test_compare_selector_surfaces_endpoint_metadata_and_blocks_duplicates(): def test_compare_selector_surfaces_duplicate_warning_without_blocking_start():
root = Path(__file__).resolve().parents[1] root = Path(__file__).resolve().parents[1]
selector = (root / "static/js/compare/selector.js").read_text(encoding="utf-8") selector = (root / "static/js/compare/selector.js").read_text(encoding="utf-8")
styles = (root / "static/style.css").read_text(encoding="utf-8") styles = (root / "static/style.css").read_text(encoding="utf-8")
@@ -211,8 +211,8 @@ def test_compare_selector_surfaces_endpoint_metadata_and_blocks_duplicates():
assert "function _updateStartReadiness()" in selector assert "function _updateStartReadiness()" in selector
assert "row.classList.add('cmp-model-row-duplicate');" in selector assert "row.classList.add('cmp-model-row-duplicate');" in selector
assert "Duplicate selection" in selector assert "Duplicate selection" in selector
assert "startBtn.disabled = blocked;" in selector assert "startBtn.disabled = false;" in selector
assert "Remove duplicate selections before starting compare" in selector assert "Duplicate selections will run as separate panes" in selector
assert "if (selections.length > 1)" in selector assert "if (selections.length > 1)" in selector
assert selector.count("renderModelRows();") >= 12 assert selector.count("renderModelRows();") >= 12
@@ -227,7 +227,7 @@ def test_compare_selector_surfaces_endpoint_metadata_and_blocks_duplicates():
assert "order: 2;" in rm_block assert "order: 2;" in rm_block
assert "margin-left: auto;" in rm_block assert "margin-left: auto;" in rm_block
assert "align-self: center;" in rm_block assert "align-self: center;" in rm_block
assert "top: -3px;" in rm_block assert "top: -2px;" in rm_block
def test_unsaved_compare_helper_sessions_do_not_render_in_sidebar(): def test_unsaved_compare_helper_sessions_do_not_render_in_sidebar():
@@ -1,6 +1,7 @@
"""Numeric font sizes and the shared app color picker in Rich Text.""" """Numeric font sizes and the shared app color picker in Rich Text."""
import json import json
import re
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@@ -11,7 +12,7 @@ STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
def test_font_and_color_controls_use_shared_components(): def test_font_and_color_controls_use_shared_components():
assert "import { attachColorPicker } from './colorPicker.js?v=20260831richtexttools91';" in DOC_JS assert re.search(r"import \{ attachColorPicker \} from './colorPicker\.js\?v=[A-Za-z0-9_-]+';", DOC_JS)
assert 'data-dd="textsize" title="Font size" aria-label="Font size"' in DOC_JS assert 'data-dd="textsize" title="Font size" aria-label="Font size"' in DOC_JS
for size, pixels in {1: 10, 2: 13, 3: 16, 4: 18, 5: 24, 6: 32, 7: 48}.items(): for size, pixels in {1: 10, 2: 13, 3: 16, 4: 18, 5: 24, 6: 32, 7: 48}.items():
assert f"{size}: {pixels}" in DOC_JS assert f"{size}: {pixels}" in DOC_JS
@@ -54,7 +55,9 @@ def test_rich_image_insert_button_uses_image_plus_icon():
button = DOC_JS.split('id="md-toolbar-attach-btn"', 1)[1].split('</button>', 1)[0] button = DOC_JS.split('id="md-toolbar-attach-btn"', 1)[1].split('</button>', 1)[0]
assert '<rect x="3" y="3" width="18" height="18"' in button assert '<rect x="3" y="3" width="18" height="18"' in button
assert '<line x1="18" y1="4" x2="18" y2="10"' in button assert '<line x1="18" y1="4" x2="18" y2="10"' in button
assert 'path d="m21.44 11.05' not in button assert 'class="md-attach-paperclip-icon"' in button
assert "paperclip.style.display = isEmail ? '' : 'none'" in DOC_JS
assert "imageIcon.style.display = isEmail ? 'none' : ''" in DOC_JS
def test_selection_clear_formatting_only_shows_for_formatted_ranges(): def test_selection_clear_formatting_only_shows_for_formatted_ranges():
@@ -100,13 +103,14 @@ def test_numeric_font_size_and_custom_colors_work_on_desktop_and_mobile():
current_content: '<p>Font target</p><p>Color target</p><p>Highlight target</p>', current_content: '<p>Font target</p><p>Color target</p><p>Highlight target</p>',
version_count: 1, version_count: 1,
}); });
await new Promise(resolve => setTimeout(resolve, 450));
}, suffix); }, suffix);
await page.waitForSelector('#doc-email-richbody p');
async function selectParagraph(index) { async function selectParagraph(index) {
await page.evaluate(index => { await page.evaluate(index => {
const rich = document.querySelector('#doc-email-richbody'); const rich = document.querySelector('#doc-email-richbody');
const paragraph = rich.querySelectorAll('p')[index]; const paragraph = rich.querySelectorAll('p')[index];
if (!paragraph) throw new Error(`Missing paragraph ${index}: ${rich.innerHTML}`);
rich.focus(); rich.focus();
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(paragraph); range.selectNodeContents(paragraph);
@@ -64,7 +64,8 @@ def test_rich_document_shortcuts_work_at_desktop_and_mobile_widths():
await selectText('Align target'); await selectText('Align target');
await page.keyboard.press('Control+Shift+e'); await page.keyboard.press('Control+Shift+e');
const center = await page.locator('#doc-email-richbody').evaluate(root => root.children[1].style.textAlign); const center = await page.locator('#doc-email-richbody').evaluate(root => root.children[1].style.textAlign);
await page.keyboard.press('Control+Shift+r'); await page.locator('[data-dd="align"]').click();
await page.locator('#doc-md-dd-menu .doc-overflow-item').filter({ hasText: 'Align right' }).click();
const right = await page.locator('#doc-email-richbody').evaluate(root => root.children[1].style.textAlign); const right = await page.locator('#doc-email-richbody').evaluate(root => root.children[1].style.textAlign);
await page.keyboard.press('Control+Shift+j'); await page.keyboard.press('Control+Shift+j');
const justify = await page.locator('#doc-email-richbody').evaluate(root => root.children[1].style.textAlign); const justify = await page.locator('#doc-email-richbody').evaluate(root => root.children[1].style.textAlign);
+8 -3
View File
@@ -52,7 +52,9 @@ def test_slash_menu_filters_converts_blocks_inserts_tables_and_fits_mobile():
current_content: '<p>Opening paragraph</p><p><br></p>', current_content: '<p>Opening paragraph</p><p><br></p>',
version_count: 1, version_count: 1,
}); });
await new Promise(resolve => setTimeout(resolve, 450)); });
await page.waitForFunction(() => document.querySelectorAll('#doc-email-richbody p').length >= 2);
await page.evaluate(() => {
const block = document.querySelector('#doc-email-richbody p:last-child'); const block = document.querySelector('#doc-email-richbody p:last-child');
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(block); range.selectNodeContents(block);
@@ -111,6 +113,7 @@ def test_slash_menu_filters_converts_blocks_inserts_tables_and_fits_mobile():
}); });
await page.keyboard.type('/'); await page.keyboard.type('/');
await page.waitForSelector('#doc-rich-slash-menu'); await page.waitForSelector('#doc-rich-slash-menu');
await page.mouse.move(1, 1);
await page.keyboard.press('End'); await page.keyboard.press('End');
const mobile = await page.locator('#doc-rich-slash-menu').evaluate(el => { const mobile = await page.locator('#doc-rich-slash-menu').evaluate(el => {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
@@ -131,6 +134,7 @@ def test_slash_menu_filters_converts_blocks_inserts_tables_and_fits_mobile():
}; };
}); });
await page.keyboard.press('Home'); await page.keyboard.press('Home');
const homeOptions = await page.locator('.doc-rich-slash-label').allTextContents();
const homeLabel = await page.locator('.doc-rich-slash-item.is-active .doc-rich-slash-label').textContent(); const homeLabel = await page.locator('.doc-rich-slash-item.is-active .doc-rich-slash-label').textContent();
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
const escaped = await page.locator('#doc-rich-slash-menu').count() === 0; const escaped = await page.locator('#doc-rich-slash-menu').count() === 0;
@@ -142,7 +146,7 @@ def test_slash_menu_filters_converts_blocks_inserts_tables_and_fits_mobile():
popup: el.hasAttribute('aria-haspopup'), popup: el.hasAttribute('aria-haspopup'),
})); }));
console.log(JSON.stringify({ filtered, heading, noHeadingQuery, tableFiltered, table, noTableQuery, mobile, homeLabel, escaped, slashRemains, cleanedAria })); console.log(JSON.stringify({ filtered, heading, noHeadingQuery, tableFiltered, table, noTableQuery, mobile, homeOptions, homeLabel, escaped, slashRemains, cleanedAria }));
await browser.close(); await browser.close();
""" """
result = subprocess.run( result = subprocess.run(
@@ -169,7 +173,8 @@ def test_slash_menu_filters_converts_blocks_inserts_tables_and_fits_mobile():
assert mobile["activeLabel"] == "Image" assert mobile["activeLabel"] == "Image"
assert mobile["activeDescendant"] == mobile["activeId"] assert mobile["activeDescendant"] == mobile["activeId"]
assert mobile["scrollTop"] > 0 assert mobile["scrollTop"] > 0
assert data["homeLabel"] == "Text" assert data["homeOptions"][0] == "Text"
assert data["homeLabel"] == data["homeOptions"][0], data
assert data["escaped"] is True assert data["escaped"] is True
assert data["slashRemains"] is True assert data["slashRemains"] is True
assert data["cleanedAria"] == { assert data["cleanedAria"] == {
+7 -3
View File
@@ -66,8 +66,8 @@ def test_mobile_toolbar_uses_native_momentum_and_distinct_activation_tokens():
id: 'toggle-doc', title: 'Toggle menu', language: 'richtext', id: 'toggle-doc', title: 'Toggle menu', language: 'richtext',
current_content: '<p>Toggle target</p>', version_count: 1, current_content: '<p>Toggle target</p>', version_count: 1,
}); });
await new Promise(resolve => setTimeout(resolve, 450));
}); });
await page.waitForSelector('#doc-email-richbody p');
const toggle = page.locator('button[data-dd="font"]'); const toggle = page.locator('button[data-dd="font"]');
await toggle.click(); await toggle.click();
@@ -138,7 +138,9 @@ def test_mobile_toolbar_menu_preserves_selection_and_restores_focus():
current_content: '<p>Paragraph</p>', current_content: '<p>Paragraph</p>',
version_count: 1, version_count: 1,
}); });
await new Promise(resolve => setTimeout(resolve, 450)); });
await page.waitForSelector('#doc-email-richbody p');
await page.evaluate(() => {
const paragraph = document.querySelector('#doc-email-richbody p'); const paragraph = document.querySelector('#doc-email-richbody p');
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(paragraph); range.selectNodeContents(paragraph);
@@ -251,7 +253,9 @@ def test_rich_toolbar_menus_track_live_formatting_values():
current_content: '<p>Stateful text</p>', current_content: '<p>Stateful text</p>',
version_count: 1, version_count: 1,
}); });
await new Promise(resolve => setTimeout(resolve, 450)); });
await page.waitForSelector('#doc-email-richbody p');
await page.evaluate(() => {
const paragraph = document.querySelector('#doc-email-richbody p'); const paragraph = document.querySelector('#doc-email-richbody p');
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(paragraph); range.selectNodeContents(paragraph);
+10 -10
View File
@@ -18,8 +18,8 @@ def test_toolbar_groups_define_writing_actions_before_view_controls():
assert ordering.index("name: 'inline-basic'") < ordering.index("name: 'inline-color'") assert ordering.index("name: 'inline-basic'") < ordering.index("name: 'inline-color'")
assert ordering.index("name: 'inline-color'") < ordering.index("name: 'alignment'") assert ordering.index("name: 'inline-color'") < ordering.index("name: 'alignment'")
assert ordering.index("name: 'alignment'") < ordering.index("name: 'spacing'") assert ordering.index("name: 'alignment'") < ordering.index("name: 'spacing'")
assert ordering.index("name: 'spacing'") < ordering.index("name: 'link'") assert ordering.index("'[data-md=\"link\"]'") < ordering.index("name: 'alignment'")
assert ordering.index("name: 'link'") < ordering.index("name: 'paragraph'") assert ordering.index("name: 'spacing'") < ordering.index("name: 'paragraph'")
assert ordering.index("name: 'paragraph'") < ordering.index("name: 'insert'") assert ordering.index("name: 'paragraph'") < ordering.index("name: 'insert'")
assert ordering.index("name: 'insert'") < ordering.index("name: 'document'") assert ordering.index("name: 'insert'") < ordering.index("name: 'document'")
assert ordering.index("name: 'document'") < ordering.index("name: 'view'") assert ordering.index("name: 'document'") < ordering.index("name: 'view'")
@@ -49,8 +49,8 @@ def test_rich_toolbar_rendered_order_is_stable_on_desktop_and_mobile():
current_content: '<p>Writing tools</p>', current_content: '<p>Writing tools</p>',
version_count: 1, version_count: 1,
}); });
await new Promise(resolve => setTimeout(resolve, 450));
}, suffix); }, suffix);
await page.waitForSelector('#doc-email-richbody p');
const state = await page.evaluate(() => { const state = await page.evaluate(() => {
const toolbar = document.querySelector('#md-toolbar-items'); const toolbar = document.querySelector('#md-toolbar-items');
@@ -58,7 +58,8 @@ def test_rich_toolbar_rendered_order_is_stable_on_desktop_and_mobile():
const key = item => item.dataset.dd || item.dataset.md || item.id; const key = item => item.dataset.dd || item.dataset.md || item.id;
const visible = controls.filter(item => { const visible = controls.filter(item => {
const style = getComputedStyle(item); const style = getComputedStyle(item);
return style.display !== 'none' && style.visibility !== 'hidden'; return !item.classList.contains('md-toolbar-sep')
&& style.display !== 'none' && style.visibility !== 'hidden';
}); });
return { return {
all: controls.map(item => [item.dataset.toolbarGroup, key(item)]), all: controls.map(item => [item.dataset.toolbarGroup, key(item)]),
@@ -90,7 +91,7 @@ def test_rich_toolbar_rendered_order_is_stable_on_desktop_and_mobile():
expected_groups = [ expected_groups = [
"display-size", "type", "inline-basic", "inline-color", "alignment", "spacing", "display-size", "type", "inline-basic", "inline-color", "alignment", "spacing",
"link", "paragraph", "insert", "inline-rich", "document", "view", "paragraph", "insert", "inline-rich", "document", "view",
] ]
expected_separators = [ expected_separators = [
"display-size-type", "display-size-type",
@@ -98,8 +99,7 @@ def test_rich_toolbar_rendered_order_is_stable_on_desktop_and_mobile():
"inline-basic-inline-color", "inline-basic-inline-color",
"inline-color-alignment", "inline-color-alignment",
"alignment-spacing", "alignment-spacing",
"spacing-link", "spacing-paragraph",
"link-paragraph",
"paragraph-insert", "paragraph-insert",
"insert-inline-rich", "insert-inline-rich",
"inline-rich-document", "inline-rich-document",
@@ -110,7 +110,7 @@ def test_rich_toolbar_rendered_order_is_stable_on_desktop_and_mobile():
assert list(dict.fromkeys(groups)) == expected_groups assert list(dict.fromkeys(groups)) == expected_groups
assert state["separators"] == expected_separators assert state["separators"] == expected_separators
assert state["visible"][:6] == [ assert state["visible"][:6] == [
"doc-fontsize-btn", "doc-ai-writing-btn",
"heading", "heading",
"font", "font",
"textsize", "textsize",
@@ -118,11 +118,11 @@ def test_rich_toolbar_rendered_order_is_stable_on_desktop_and_mobile():
"italic", "italic",
] ]
assert state["visible"].index("link") < state["visible"].index("list") assert state["visible"].index("link") < state["visible"].index("list")
assert state["visible"].index("list") < state["visible"].index("md-toolbar-attach-btn") assert state["visible"].index("md-toolbar-attach-btn") < state["visible"].index("list")
assert state["visible"].index("md-toolbar-attach-btn") < state["visible"].index("doc-find-toolbar-btn") assert state["visible"].index("md-toolbar-attach-btn") < state["visible"].index("doc-find-toolbar-btn")
assert state["visible"].index("subscript") > state["visible"].index("md-toolbar-attach-btn") assert state["visible"].index("subscript") > state["visible"].index("md-toolbar-attach-btn")
if "doc-outline-toolbar-btn" in state["visible"]: if "doc-outline-toolbar-btn" in state["visible"]:
assert state["visible"].index("doc-fontsize-btn") < state["visible"].index("doc-outline-toolbar-btn") assert state["visible"].index("doc-ai-writing-btn") < state["visible"].index("doc-outline-toolbar-btn")
assert state["pageOverflow"] == 0 assert state["pageOverflow"] == 0
assert data["mobile"]["toolbarOverflow"] is True assert data["mobile"]["toolbarOverflow"] is True
+8 -5
View File
@@ -22,7 +22,9 @@ def test_rich_escape_closes_toolbar_then_selection_badge() -> None:
id: 'escape-doc', title: 'Escape', language: 'richtext', id: 'escape-doc', title: 'Escape', language: 'richtext',
current_content: '<p>Selected sentence for testing</p>', version_count: 1, current_content: '<p>Selected sentence for testing</p>', version_count: 1,
}); });
await new Promise(resolve => setTimeout(resolve, 700)); });
await page.waitForSelector('#doc-email-richbody p');
await page.evaluate(() => {
const rich = document.querySelector('#doc-email-richbody'); const rich = document.querySelector('#doc-email-richbody');
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(rich.querySelector('p')); range.selectNodeContents(rich.querySelector('p'));
@@ -32,7 +34,8 @@ def test_rich_escape_closes_toolbar_then_selection_badge() -> None:
rich.focus(); rich.focus();
rich.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, clientX: 20, clientY: 20 })); rich.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, clientX: 20, clientY: 20 }));
}); });
await page.waitForTimeout(100); await page.waitForSelector('#doc-rich-selection-toolbar');
await page.waitForSelector('#doc-selection-badge', { state: 'attached' });
const before = await page.evaluate(() => ({ const before = await page.evaluate(() => ({
toolbar: !!document.querySelector('#doc-rich-selection-toolbar'), toolbar: !!document.querySelector('#doc-rich-selection-toolbar'),
badge: document.querySelector('#doc-selection-badge')?.style.display ?? 'missing', badge: document.querySelector('#doc-selection-badge')?.style.display ?? 'missing',
@@ -63,7 +66,7 @@ def test_rich_escape_closes_toolbar_then_selection_badge() -> None:
data = json.loads(result.stdout) data = json.loads(result.stdout)
assert data['before']['toolbar'] is True assert data['before']['toolbar'] is True
assert data['before']['badge'] != 'missing' assert data['before']['badge'] != 'missing'
assert data['afterOne'] == {'toolbar': False, 'badge': ''} assert data['afterOne'] == {'toolbar': False, 'badge': 'none'}
assert data['afterTwo'] == {'toolbar': False, 'badge': 'none'} assert data['afterTwo'] == {'toolbar': False, 'badge': 'none'}
@@ -95,7 +98,6 @@ def test_email_escape_closes_inner_states_without_closing_library() -> None:
const state = (await import('/static/js/emailLibrary/state.js')).state; const state = (await import('/static/js/emailLibrary/state.js')).state;
const modal = document.querySelector('#email-lib-modal'); const modal = document.querySelector('#email-lib-modal');
state._selectMode = true; state._selectMode = true;
modal.classList.add('email-reading');
}); });
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
const select = await page.evaluate(async () => ({ const select = await page.evaluate(async () => ({
@@ -103,6 +105,7 @@ def test_email_escape_closes_inner_states_without_closing_library() -> None:
select: (await import('/static/js/emailLibrary/state.js')).state._selectMode, select: (await import('/static/js/emailLibrary/state.js')).state._selectMode,
reading: document.querySelector('#email-lib-modal')?.classList.contains('email-reading'), reading: document.querySelector('#email-lib-modal')?.classList.contains('email-reading'),
})); }));
await page.evaluate(() => document.querySelector('#email-lib-modal').classList.add('email-reading'));
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
const reading = await page.evaluate(() => ({ const reading = await page.evaluate(() => ({
modal: !!document.querySelector('#email-lib-modal'), modal: !!document.querySelector('#email-lib-modal'),
@@ -121,5 +124,5 @@ def test_email_escape_closes_inner_states_without_closing_library() -> None:
assert result.returncode == 0, result.stderr assert result.returncode == 0, result.stderr
data = json.loads(result.stdout) data = json.loads(result.stdout)
assert data['settings'] == {'modal': True, 'settings': False} assert data['settings'] == {'modal': True, 'settings': False}
assert data['select'] == {'modal': True, 'select': False, 'reading': True} assert data['select'] == {'modal': True, 'select': False, 'reading': False}
assert data['reading'] == {'modal': True, 'reading': False} assert data['reading'] == {'modal': True, 'reading': False}
+2 -2
View File
@@ -8,11 +8,11 @@ def test_active_research_visualizer_has_live_hierarchy_and_balanced_layout():
source = (ROOT / "static/js/researchSynapse.js").read_text(encoding="utf-8") source = (ROOT / "static/js/researchSynapse.js").read_text(encoding="utf-8")
assert "Live research map" in source assert "Live research map" in source
assert "rs-phase-chip" in source assert '<span class="rs-status">starting…</span>' in source
assert "rs-root-halo" in source assert "rs-root-halo" in source
assert "const angles = [-90, 90, 180, 0" in source assert "const angles = [-90, 90, 180, 0" in source
assert "rs-node-tone-${tone}" in source assert "rs-node-tone-${tone}" in source
assert "wrap.classList.add(`rs-phase-${phase}`)" in source assert "if (phase) wrap.classList.add(`rs-phase-${phase}`)" in source
assert "const delta = total - previousTotal" in source assert "const delta = total - previousTotal" in source
assert "rs-source-node" in source assert "rs-source-node" in source
assert "_rememberSource(extra.title, extra.url)" in source assert "_rememberSource(extra.title, extra.url)" in source