feat: add header + button with inline name input for session creation
- Add showNewSessionInput(btn): creates inline text input with class
'new-session-input', placeholder 'Session name…', autocomplete off,
spellcheck false; hides button, inserts input before it, focuses it.
Handles Enter (create), Escape (cancel), and blur (150ms delayed cleanup).
- Add createNewSession(name): POSTs to /api/sessions, shows toast with
created session name, calls pollSessions(), and if ss.auto_open_created
!== false calls openSession(data.name) after 500ms delay.
- Bind #new-session-btn click to showNewSessionInput in bindStaticEventListeners.
- Export showNewSessionInput and createNewSession from module.exports.
- Add .new-session-input CSS (bg, border 1px solid accent, border-radius 4px,
font-size 13px, font-ui, padding 4px 10px, width 180px, outline none) and
::placeholder { color: var(--text-dim) } in style.css.
- Add 28 new tests covering all spec requirements (19 JS structural, 9 CSS).
This commit is contained in:
@@ -1274,12 +1274,78 @@ function updateSessionPill(sessions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Header + button with inline name input ────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the header + button with an inline text input for session naming.
|
||||||
|
* Hides the button, inserts the input before it, and focuses it.
|
||||||
|
* On Enter: if name is non-empty after trim, calls createNewSession(name).
|
||||||
|
* On Escape: restores the button (cleanup only).
|
||||||
|
* On blur: delayed cleanup (150ms) to allow click handlers.
|
||||||
|
* @param {HTMLElement} btn - The button element to replace temporarily.
|
||||||
|
*/
|
||||||
|
function showNewSessionInput(btn) {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'text';
|
||||||
|
input.className = 'new-session-input';
|
||||||
|
input.placeholder = 'Session name\u2026';
|
||||||
|
input.autocomplete = 'off';
|
||||||
|
input.spellcheck = false;
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
if (input.parentNode) input.parentNode.removeChild(input);
|
||||||
|
btn.style.display = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
input.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
const name = input.value.trim();
|
||||||
|
cleanup();
|
||||||
|
if (name) createNewSession(name);
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('blur', function () {
|
||||||
|
setTimeout(cleanup, 150);
|
||||||
|
});
|
||||||
|
|
||||||
|
btn.style.display = 'none';
|
||||||
|
btn.parentNode.insertBefore(input, btn);
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new tmux session via POST /api/sessions.
|
||||||
|
* Shows a toast with the created session name, calls pollSessions() to refresh,
|
||||||
|
* and if ss.auto_open_created !== false, calls openSession(data.name) after 500ms.
|
||||||
|
* @param {string} name - The session name to create.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function createNewSession(name) {
|
||||||
|
try {
|
||||||
|
const res = await api('POST', '/api/sessions', { name });
|
||||||
|
const data = await res.json();
|
||||||
|
showToast('Created: ' + (data.name || name));
|
||||||
|
await pollSessions();
|
||||||
|
const ss = _serverSettings || {};
|
||||||
|
if (ss.auto_open_created !== false) {
|
||||||
|
setTimeout(() => openSession(data.name || name), 500);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showToast(err.message || 'Failed to create session');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind all static (once-only) event listeners for the app UI.
|
* Bind all static (once-only) event listeners for the app UI.
|
||||||
* Called once after restoreState() resolves.
|
* Called once after restoreState() resolves.
|
||||||
*/
|
*/
|
||||||
function bindStaticEventListeners() {
|
function bindStaticEventListeners() {
|
||||||
on($('back-btn'), 'click', closeSession);
|
on($('back-btn'), 'click', closeSession);
|
||||||
|
var newSessionBtn = $('new-session-btn');
|
||||||
|
if (newSessionBtn) on(newSessionBtn, 'click', function() { showNewSessionInput(newSessionBtn); });
|
||||||
on($('sidebar-toggle-btn'), 'click', toggleSidebar);
|
on($('sidebar-toggle-btn'), 'click', toggleSidebar);
|
||||||
on($('sidebar-collapse-btn'), 'click', toggleSidebar);
|
on($('sidebar-collapse-btn'), 'click', toggleSidebar);
|
||||||
bindSidebarClickAway();
|
bindSidebarClickAway();
|
||||||
@@ -1503,6 +1569,9 @@ if (typeof module !== 'undefined' && module.exports) {
|
|||||||
// Server settings
|
// Server settings
|
||||||
loadServerSettings,
|
loadServerSettings,
|
||||||
patchServerSetting,
|
patchServerSetting,
|
||||||
|
// Header + button with inline name input
|
||||||
|
showNewSessionInput,
|
||||||
|
createNewSession,
|
||||||
// Test-only helpers
|
// Test-only helpers
|
||||||
_setCurrentSessions,
|
_setCurrentSessions,
|
||||||
_setViewMode,
|
_setViewMode,
|
||||||
|
|||||||
@@ -1154,6 +1154,26 @@ body {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Header + button inline name input (new-session-btn)
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.new-session-input {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
padding: 4px 10px;
|
||||||
|
width: 180px;
|
||||||
|
outline: none;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-session-input::placeholder {
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
Responsive overlay sidebar at <960px
|
Responsive overlay sidebar at <960px
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|||||||
@@ -1252,3 +1252,141 @@ def test_css_settings_helper_italic() -> None:
|
|||||||
assert "font-style: italic" in body or "font-style:italic" in body, (
|
assert "font-style: italic" in body or "font-style:italic" in body, (
|
||||||
".settings-helper must have font-style: italic"
|
".settings-helper must have font-style: italic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── .new-session-input (task-4-header-plus-button) ──────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_css_new_session_input_rule_exists() -> None:
|
||||||
|
""".new-session-input CSS rule must exist in style.css."""
|
||||||
|
css = read_css()
|
||||||
|
assert ".new-session-input" in css, (
|
||||||
|
".new-session-input CSS rule must exist in style.css"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_css_new_session_input_has_border() -> None:
|
||||||
|
""".new-session-input must have a border (1px solid accent)."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
css = read_css()
|
||||||
|
match = re.search(
|
||||||
|
r"\.new-session-input\s*\{([^}]*)\}",
|
||||||
|
css,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, ".new-session-input rule not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "border" in body, ".new-session-input must have a border property"
|
||||||
|
assert "accent" in body or "#00D9F5" in body or "1px solid" in body, (
|
||||||
|
".new-session-input border must reference accent color"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_css_new_session_input_has_border_radius() -> None:
|
||||||
|
""".new-session-input must have border-radius: 4px."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
css = read_css()
|
||||||
|
match = re.search(
|
||||||
|
r"\.new-session-input\s*\{([^}]*)\}",
|
||||||
|
css,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, ".new-session-input rule not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "border-radius" in body, ".new-session-input must have border-radius"
|
||||||
|
assert "4px" in body, ".new-session-input border-radius must be 4px"
|
||||||
|
|
||||||
|
|
||||||
|
def test_css_new_session_input_has_font_size() -> None:
|
||||||
|
""".new-session-input must have font-size: 13px."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
css = read_css()
|
||||||
|
match = re.search(
|
||||||
|
r"\.new-session-input\s*\{([^}]*)\}",
|
||||||
|
css,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, ".new-session-input rule not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "font-size" in body, ".new-session-input must have font-size"
|
||||||
|
assert "13px" in body, ".new-session-input font-size must be 13px"
|
||||||
|
|
||||||
|
|
||||||
|
def test_css_new_session_input_has_padding() -> None:
|
||||||
|
""".new-session-input must have padding: 4px 10px."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
css = read_css()
|
||||||
|
match = re.search(
|
||||||
|
r"\.new-session-input\s*\{([^}]*)\}",
|
||||||
|
css,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, ".new-session-input rule not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "padding" in body, ".new-session-input must have padding"
|
||||||
|
assert "4px" in body and "10px" in body, (
|
||||||
|
".new-session-input padding must be 4px 10px"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_css_new_session_input_has_width() -> None:
|
||||||
|
""".new-session-input must have width: 180px."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
css = read_css()
|
||||||
|
match = re.search(
|
||||||
|
r"\.new-session-input\s*\{([^}]*)\}",
|
||||||
|
css,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, ".new-session-input rule not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "width" in body, ".new-session-input must have width"
|
||||||
|
assert "180px" in body, ".new-session-input width must be 180px"
|
||||||
|
|
||||||
|
|
||||||
|
def test_css_new_session_input_has_outline_none() -> None:
|
||||||
|
""".new-session-input must have outline: none."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
css = read_css()
|
||||||
|
match = re.search(
|
||||||
|
r"\.new-session-input\s*\{([^}]*)\}",
|
||||||
|
css,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, ".new-session-input rule not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "outline" in body and "none" in body, (
|
||||||
|
".new-session-input must have outline: none"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_css_new_session_input_placeholder_rule_exists() -> None:
|
||||||
|
""".new-session-input::placeholder CSS rule must exist."""
|
||||||
|
css = read_css()
|
||||||
|
assert ".new-session-input::placeholder" in css or \
|
||||||
|
".new-session-input::-webkit-input-placeholder" in css, (
|
||||||
|
".new-session-input::placeholder CSS rule must exist"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_css_new_session_input_placeholder_color() -> None:
|
||||||
|
""".new-session-input::placeholder must have color: var(--text-dim)."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
css = read_css()
|
||||||
|
match = re.search(
|
||||||
|
r"\.new-session-input::placeholder\s*\{([^}]*)\}",
|
||||||
|
css,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, ".new-session-input::placeholder rule not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "color" in body and ("text-dim" in body or "--text-dim" in body), (
|
||||||
|
".new-session-input::placeholder must have color: var(--text-dim)"
|
||||||
|
)
|
||||||
|
|||||||
@@ -1523,3 +1523,256 @@ def test_bind_static_event_listeners_reset_patches_server() -> None:
|
|||||||
assert "NEW_SESSION_DEFAULT_TEMPLATE" in body, (
|
assert "NEW_SESSION_DEFAULT_TEMPLATE" in body, (
|
||||||
"bindStaticEventListeners reset handler must use NEW_SESSION_DEFAULT_TEMPLATE"
|
"bindStaticEventListeners reset handler must use NEW_SESSION_DEFAULT_TEMPLATE"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Header + button with inline name input (task-4-header-plus-button) ──────
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_new_session_input_function_exists() -> None:
|
||||||
|
"""showNewSessionInput function must exist in app.js."""
|
||||||
|
assert "function showNewSessionInput" in _JS, (
|
||||||
|
"showNewSessionInput must be defined in app.js"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_new_session_function_exists() -> None:
|
||||||
|
"""createNewSession function must exist in app.js."""
|
||||||
|
assert "function createNewSession" in _JS, (
|
||||||
|
"createNewSession must be defined in app.js"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_new_session_input_creates_input_with_class() -> None:
|
||||||
|
"""showNewSessionInput must create an input element with class 'new-session-input'."""
|
||||||
|
match = re.search(
|
||||||
|
r"function showNewSessionInput\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "showNewSessionInput function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "new-session-input" in body, (
|
||||||
|
"showNewSessionInput must create an input with class 'new-session-input'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_new_session_input_sets_placeholder() -> None:
|
||||||
|
"""showNewSessionInput must set placeholder to 'Session name…'."""
|
||||||
|
match = re.search(
|
||||||
|
r"function showNewSessionInput\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "showNewSessionInput function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "Session name" in body, (
|
||||||
|
"showNewSessionInput must set placeholder containing 'Session name'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_new_session_input_disables_autocomplete() -> None:
|
||||||
|
"""showNewSessionInput must set autocomplete off on the input."""
|
||||||
|
match = re.search(
|
||||||
|
r"function showNewSessionInput\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "showNewSessionInput function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "autocomplete" in body.lower() and "off" in body.lower(), (
|
||||||
|
"showNewSessionInput must set autocomplete off"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_new_session_input_disables_spellcheck() -> None:
|
||||||
|
"""showNewSessionInput must set spellcheck false on the input."""
|
||||||
|
match = re.search(
|
||||||
|
r"function showNewSessionInput\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "showNewSessionInput function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "spellcheck" in body.lower() and "false" in body.lower(), (
|
||||||
|
"showNewSessionInput must set spellcheck false"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_new_session_input_hides_button() -> None:
|
||||||
|
"""showNewSessionInput must hide the button."""
|
||||||
|
match = re.search(
|
||||||
|
r"function showNewSessionInput\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "showNewSessionInput function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
# Must hide the button (display none or style.display)
|
||||||
|
assert "display" in body or "style.display" in body or "hidden" in body, (
|
||||||
|
"showNewSessionInput must hide the button"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_new_session_input_focuses_input() -> None:
|
||||||
|
"""showNewSessionInput must call focus() on the input."""
|
||||||
|
match = re.search(
|
||||||
|
r"function showNewSessionInput\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "showNewSessionInput function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert ".focus()" in body or "focus()" in body, (
|
||||||
|
"showNewSessionInput must call focus() on the input"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_new_session_input_handles_enter_key() -> None:
|
||||||
|
"""showNewSessionInput must handle Enter key to create session."""
|
||||||
|
match = re.search(
|
||||||
|
r"function showNewSessionInput\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "showNewSessionInput function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "Enter" in body, "showNewSessionInput must handle Enter key"
|
||||||
|
assert "createNewSession" in body, (
|
||||||
|
"showNewSessionInput must call createNewSession on Enter"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_new_session_input_handles_escape_key() -> None:
|
||||||
|
"""showNewSessionInput must handle Escape key to cancel."""
|
||||||
|
match = re.search(
|
||||||
|
r"function showNewSessionInput\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "showNewSessionInput function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "Escape" in body, "showNewSessionInput must handle Escape key"
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_new_session_input_handles_blur_with_delay() -> None:
|
||||||
|
"""showNewSessionInput must handle blur with 150ms delay."""
|
||||||
|
match = re.search(
|
||||||
|
r"function showNewSessionInput\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "showNewSessionInput function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "blur" in body, "showNewSessionInput must handle blur event"
|
||||||
|
assert "150" in body, (
|
||||||
|
"showNewSessionInput must use 150ms delay on blur"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_new_session_posts_to_api_sessions() -> None:
|
||||||
|
"""createNewSession must POST to /api/sessions."""
|
||||||
|
match = re.search(
|
||||||
|
r"async function createNewSession\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "createNewSession function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "/api/sessions" in body, "createNewSession must POST to /api/sessions"
|
||||||
|
assert "POST" in body, "createNewSession must use POST method"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_new_session_shows_toast() -> None:
|
||||||
|
"""createNewSession must call showToast."""
|
||||||
|
match = re.search(
|
||||||
|
r"async function createNewSession\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "createNewSession function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "showToast" in body, "createNewSession must call showToast"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_new_session_calls_poll_sessions() -> None:
|
||||||
|
"""createNewSession must call pollSessions."""
|
||||||
|
match = re.search(
|
||||||
|
r"async function createNewSession\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "createNewSession function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "pollSessions" in body, "createNewSession must call pollSessions"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_new_session_auto_opens_session() -> None:
|
||||||
|
"""createNewSession must call openSession when auto_open_created is not false."""
|
||||||
|
match = re.search(
|
||||||
|
r"async function createNewSession\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "createNewSession function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "openSession" in body, "createNewSession must call openSession"
|
||||||
|
assert "auto_open_created" in body, (
|
||||||
|
"createNewSession must check ss.auto_open_created"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_new_session_auto_open_delay() -> None:
|
||||||
|
"""createNewSession must use a 500ms delay before calling openSession."""
|
||||||
|
match = re.search(
|
||||||
|
r"async function createNewSession\s*\(\w+\)\s*\{(.*?)(?=\nasync function |\nfunction |\n// )",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "createNewSession function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "500" in body, "createNewSession must use 500ms delay before openSession"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bind_static_event_listeners_binds_new_session_btn() -> None:
|
||||||
|
"""bindStaticEventListeners must bind click on new-session-btn to showNewSessionInput."""
|
||||||
|
match = re.search(
|
||||||
|
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "bindStaticEventListeners function not found"
|
||||||
|
body = match.group(1)
|
||||||
|
assert "new-session-btn" in body, (
|
||||||
|
"bindStaticEventListeners must bind new-session-btn click"
|
||||||
|
)
|
||||||
|
assert "showNewSessionInput" in body, (
|
||||||
|
"bindStaticEventListeners must call showNewSessionInput for new-session-btn"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_exports_show_new_session_input() -> None:
|
||||||
|
"""module.exports must export showNewSessionInput."""
|
||||||
|
match = re.search(
|
||||||
|
r"module\.exports\s*=\s*\{(.*?)\};",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "module.exports block not found"
|
||||||
|
exports = match.group(1)
|
||||||
|
assert "showNewSessionInput" in exports, (
|
||||||
|
"module.exports must export showNewSessionInput"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_exports_create_new_session() -> None:
|
||||||
|
"""module.exports must export createNewSession."""
|
||||||
|
match = re.search(
|
||||||
|
r"module\.exports\s*=\s*\{(.*?)\};",
|
||||||
|
_JS,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
assert match, "module.exports block not found"
|
||||||
|
exports = match.group(1)
|
||||||
|
assert "createNewSession" in exports, (
|
||||||
|
"module.exports must export createNewSession"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user