From a6d73b83a7a60ca358e334d96bfcc2909b5c290c Mon Sep 17 00:00:00 2001
From: sligter <1771322848@qq.com>
Date: Wed, 29 Jul 2026 19:14:18 +0800
Subject: [PATCH] Harden AI optimize modals with safer DOM and a11y
---
.../projectSlidesEditor.quickEdit.css | 1 +
.../projectSlidesEditor.aiOptimize.js | 117 ++++++++----
.../components/project/detail/extra_js_1.html | 174 +++++++++++-------
.../project/todo_board/extra_js_1.html | 138 +++++++++-----
tests/test_project_workflow_regressions.py | 29 +++
5 files changed, 302 insertions(+), 157 deletions(-)
diff --git a/src/landppt/web/static/css/pages/project/slides_editor/projectSlidesEditor.quickEdit.css b/src/landppt/web/static/css/pages/project/slides_editor/projectSlidesEditor.quickEdit.css
index b261d2d..6547d1c 100644
--- a/src/landppt/web/static/css/pages/project/slides_editor/projectSlidesEditor.quickEdit.css
+++ b/src/landppt/web/static/css/pages/project/slides_editor/projectSlidesEditor.quickEdit.css
@@ -1279,6 +1279,7 @@
font-size: 0.8rem;
color: var(--text-primary);
cursor: pointer;
+ font-family: inherit;
transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
diff --git a/src/landppt/web/static/js/pages/project/slides_editor/projectSlidesEditor.aiOptimize.js b/src/landppt/web/static/js/pages/project/slides_editor/projectSlidesEditor.aiOptimize.js
index 8441541..cd44032 100644
--- a/src/landppt/web/static/js/pages/project/slides_editor/projectSlidesEditor.aiOptimize.js
+++ b/src/landppt/web/static/js/pages/project/slides_editor/projectSlidesEditor.aiOptimize.js
@@ -12,10 +12,13 @@
// 创建美化的AI优化需求输入弹窗
function showAIOptimizeModal(config) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
// 创建模态框遮罩
const modal = document.createElement('div');
modal.className = 'ai-optimize-modal';
+ modal.setAttribute('role', 'dialog');
+ modal.setAttribute('aria-modal', 'true');
+ modal.setAttribute('aria-label', String(config.title || 'AI优化'));
// 创建弹窗内容
const content = document.createElement('div');
@@ -35,11 +38,11 @@
@@ -49,15 +52,15 @@
当前内容
- ${config.currentInfo}
+
@@ -80,47 +81,85 @@
AI将根据您的需求智能优化内容
`;
- modal.appendChild(content);
- document.body.appendChild(modal);
+ const previousFocus = document.activeElement;
+ const input = content.querySelector('.ai-optimize-input');
+ const confirmBtn = content.querySelector('.ai-optimize-confirm');
+ const currentInfoElement = content.querySelector('.ai-optimize-current-info');
+ content.querySelector('.ai-optimize-title').textContent = String(config.title || '');
+ content.querySelector('.ai-optimize-modal__subtitle').textContent = String(config.subtitle || '');
+ currentInfoElement.textContent = String(config.currentInfo || '');
- // 聚焦输入框
- setTimeout(() => {
- const input = document.getElementById('aiOptimizeInput');
- if (input) input.focus();
- }, 100);
-
- // 点击背景关闭
- modal.addEventListener('click', (e) => {
- if (e.target === modal) {
- modal.remove();
- reject('用户取消');
- }
+ const suggestionList = content.querySelector('.suggestion-list');
+ suggestions.forEach((suggestion) => {
+ const suggestionButton = document.createElement('button');
+ suggestionButton.type = 'button';
+ suggestionButton.className = 'suggestion-tag';
+ suggestionButton.textContent = String(suggestion);
+ suggestionButton.addEventListener('click', () => {
+ input.value = String(suggestion);
+ input.focus();
+ });
+ suggestionList.appendChild(suggestionButton);
});
- // 确认按钮
- const confirmBtn = document.getElementById('confirmOptimizeBtn');
- confirmBtn.onclick = () => {
- const input = document.getElementById('aiOptimizeInput');
- const value = input?.value.trim();
- if (!value) {
- // 输入框抖动动画(通过 class 触发,动画定义在 quickEdit.css)
- input.classList.add('shake');
- setTimeout(() => { input.classList.remove('shake'); }, 500);
+ let closed = false;
+ function closeModal(value = null) {
+ if (closed) return;
+ closed = true;
+ document.removeEventListener('keydown', handleKeydown);
+ modal.remove();
+ previousFocus?.focus?.();
+ resolve(value);
+ }
+
+ function handleKeydown(event) {
+ if (event.key === 'Escape') {
+ event.preventDefault();
+ closeModal();
return;
}
- modal.remove();
- resolve(value);
- };
+ if (event.key !== 'Tab') return;
+ const focusable = content.querySelectorAll('button:not([disabled]), textarea:not([disabled])');
+ const first = focusable[0];
+ const last = focusable[focusable.length - 1];
+ if (event.shiftKey && document.activeElement === first) {
+ event.preventDefault();
+ last.focus();
+ } else if (!event.shiftKey && document.activeElement === last) {
+ event.preventDefault();
+ first.focus();
+ }
+ }
+
+ content.querySelector('.ai-optimize-modal__close').addEventListener('click', () => closeModal());
+ content.querySelector('.ai-optimize-cancel').addEventListener('click', () => closeModal());
+ modal.addEventListener('click', (event) => {
+ if (event.target === modal) closeModal();
+ });
+ confirmBtn.addEventListener('click', () => {
+ const value = input.value.trim();
+ if (!value) {
+ input.classList.add('shake');
+ setTimeout(() => input.classList.remove('shake'), 500);
+ return;
+ }
+ closeModal(value);
+ });
+
+ modal.appendChild(content);
+ document.body.appendChild(modal);
+ document.addEventListener('keydown', handleKeydown);
+ requestAnimationFrame(() => input.focus());
});
}
@@ -153,7 +192,7 @@
userRequest = await showAIOptimizeModal({
title: `AI优化 - 第${currentSlideIndex + 1}页`,
subtitle: '让AI帮助您优化这一页的内容',
- currentInfo: `标题:${title}
类型:${slideType}
内容要点:${contentPoints.length}个`,
+ currentInfo: `标题:${title}\n类型:${slideType}\n内容要点:${contentPoints.length}个`,
suggestions: [
'增加更多技术细节和实例',
'简化内容,突出核心要点',
diff --git a/src/landppt/web/templates/components/project/detail/extra_js_1.html b/src/landppt/web/templates/components/project/detail/extra_js_1.html
index 3f2ef80..f4ba622 100644
--- a/src/landppt/web/templates/components/project/detail/extra_js_1.html
+++ b/src/landppt/web/templates/components/project/detail/extra_js_1.html
@@ -661,13 +661,17 @@
// 创建美化的AI优化需求输入弹窗
function showAIOptimizeModal(config) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
// 创建模态框遮罩
const modal = document.createElement('div');
modal.className = 'ai-optimize-modal';
+ modal.setAttribute('role', 'dialog');
+ modal.setAttribute('aria-modal', 'true');
+ modal.setAttribute('aria-label', String(config.title || 'AI优化'));
modal.style.cssText = `
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
- background: rgba(255, 255, 255, 0.98);
+ background: rgba(15, 23, 42, 0.4);
+ backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
z-index: 10000; display: flex; align-items: center; justify-content: center;
padding: 20px; animation: fadeIn 0.3s ease;
`;
@@ -770,19 +774,19 @@
align-items: center;
gap: 8px;
}
- .input-group {
+ .ai-optimize-modal .input-group {
display: flex;
flex-direction: column;
gap: 10px;
}
- .input-label {
+ .ai-optimize-modal .input-label {
display: block;
color: var(--text-primary);
font-weight: 600;
font-size: 0.95rem;
letter-spacing: 0.01em;
}
- .input-textarea {
+ .ai-optimize-modal .input-textarea {
width: 100%;
min-height: 120px;
padding: 16px 18px;
@@ -795,33 +799,41 @@
color: var(--text-primary);
background: var(--surface);
}
- .input-textarea::placeholder {
+ .ai-optimize-modal .input-textarea::placeholder {
color: var(--text-muted);
opacity: 0.9;
}
- .input-textarea:focus {
+ .ai-optimize-modal .input-textarea:focus {
outline: none;
border-color: var(--surface-contrast);
box-shadow: 0 0 0 3px rgba(17, 17, 17, 0.08);
}
- .suggestions {
+ .ai-optimize-modal .input-textarea.shake {
+ animation: aiOptimizeShake 0.5s;
+ }
+ @keyframes aiOptimizeShake {
+ 0%, 100% { transform: translateX(0); }
+ 25% { transform: translateX(-10px); }
+ 75% { transform: translateX(10px); }
+ }
+ .ai-optimize-modal .suggestions {
display: flex;
flex-direction: column;
gap: 12px;
}
- .suggestion-label {
+ .ai-optimize-modal .suggestion-label {
display: block;
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 500;
letter-spacing: 0.02em;
}
- .suggestion-list {
+ .ai-optimize-modal .suggestion-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
- .suggestion-tag {
+ .ai-optimize-modal .suggestion-tag {
display: inline-flex;
align-items: center;
justify-content: center;
@@ -833,8 +845,9 @@
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s ease;
+ font-family: inherit;
}
- .suggestion-tag:hover {
+ .ai-optimize-modal .suggestion-tag:hover {
transform: translateY(-2px);
background: var(--surface-contrast);
border-color: var(--surface-contrast);
@@ -850,27 +863,27 @@
justify-content: space-between;
gap: 16px;
}
- .footer-hint {
+ .ai-optimize-modal .footer-hint {
color: var(--text-secondary);
font-size: 0.85rem;
display: inline-flex;
align-items: center;
gap: 8px;
}
- .footer-actions {
+ .ai-optimize-modal .footer-actions {
display: flex;
gap: 12px;
}
- .footer-actions .btn {
+ .ai-optimize-modal .footer-actions .btn {
min-width: 110px;
justify-content: center;
}
- .modal-cancel {
+ .ai-optimize-modal .ai-optimize-cancel {
background: var(--surface-subtle);
border-color: var(--border-color);
color: var(--text-primary);
}
- .modal-cancel:hover {
+ .ai-optimize-modal .ai-optimize-cancel:hover {
background: var(--surface);
}
@media (max-width: 520px) {
@@ -880,11 +893,11 @@
padding-left: 20px;
padding-right: 20px;
}
- .footer-actions {
+ .ai-optimize-modal .footer-actions {
width: 100%;
flex-direction: column;
}
- .footer-actions .btn {
+ .ai-optimize-modal .footer-actions .btn {
width: 100%;
}
}
@@ -894,11 +907,11 @@
@@ -908,15 +921,15 @@
当前内容
- ${config.currentInfo}
+
@@ -939,58 +950,85 @@
AI将根据您的需求智能优化内容
`;
- modal.appendChild(content);
- document.body.appendChild(modal);
+ const previousFocus = document.activeElement;
+ const input = content.querySelector('.ai-optimize-input');
+ const confirmBtn = content.querySelector('.ai-optimize-confirm');
+ const currentInfoElement = content.querySelector('.ai-optimize-current-info');
+ content.querySelector('.ai-optimize-title').textContent = String(config.title || '');
+ content.querySelector('.ai-optimize-modal__subtitle').textContent = String(config.subtitle || '');
+ currentInfoElement.textContent = String(config.currentInfo || '');
- // 聚焦输入框
- setTimeout(() => {
- const input = document.getElementById('aiOptimizeInput');
- if (input) input.focus();
- }, 100);
-
- // 点击背景关闭
- modal.addEventListener('click', (e) => {
- if (e.target === modal) {
- modal.remove();
- reject('用户取消');
- }
+ const suggestionList = content.querySelector('.suggestion-list');
+ suggestions.forEach((suggestion) => {
+ const suggestionButton = document.createElement('button');
+ suggestionButton.type = 'button';
+ suggestionButton.className = 'suggestion-tag';
+ suggestionButton.textContent = String(suggestion);
+ suggestionButton.addEventListener('click', () => {
+ input.value = String(suggestion);
+ input.focus();
+ });
+ suggestionList.appendChild(suggestionButton);
});
- // 确认按钮
- const confirmBtn = document.getElementById('confirmOptimizeBtn');
- confirmBtn.onclick = () => {
- const input = document.getElementById('aiOptimizeInput');
- const value = input?.value.trim();
- if (!value) {
- // 输入框抖动动画
- input.style.animation = 'shake 0.5s';
- setTimeout(() => { input.style.animation = ''; }, 500);
+ let closed = false;
+ function closeModal(value = null) {
+ if (closed) return;
+ closed = true;
+ document.removeEventListener('keydown', handleKeydown);
+ modal.remove();
+ previousFocus?.focus?.();
+ resolve(value);
+ }
+
+ function handleKeydown(event) {
+ if (event.key === 'Escape') {
+ event.preventDefault();
+ closeModal();
return;
}
- modal.remove();
- resolve(value);
- };
+ if (event.key !== 'Tab') return;
+ const focusable = content.querySelectorAll('button:not([disabled]), textarea:not([disabled])');
+ const first = focusable[0];
+ const last = focusable[focusable.length - 1];
+ if (event.shiftKey && document.activeElement === first) {
+ event.preventDefault();
+ last.focus();
+ } else if (!event.shiftKey && document.activeElement === last) {
+ event.preventDefault();
+ first.focus();
+ }
+ }
- // 添加shake动画
- const style = document.createElement('style');
- style.textContent = `
- @keyframes shake {
- 0%, 100% { transform: translateX(0); }
- 25% { transform: translateX(-10px); }
- 75% { transform: translateX(10px); }
- }
- `;
- document.head.appendChild(style);
+ content.querySelector('.ai-optimize-modal__close').addEventListener('click', () => closeModal());
+ content.querySelector('.ai-optimize-cancel').addEventListener('click', () => closeModal());
+ modal.addEventListener('click', (event) => {
+ if (event.target === modal) closeModal();
+ });
+ confirmBtn.addEventListener('click', () => {
+ const value = input.value.trim();
+ if (!value) {
+ input.classList.add('shake');
+ setTimeout(() => input.classList.remove('shake'), 500);
+ return;
+ }
+ closeModal(value);
+ });
+
+ modal.appendChild(content);
+ document.body.appendChild(modal);
+ document.addEventListener('keydown', handleKeydown);
+ requestAnimationFrame(() => input.focus());
});
}
@@ -1030,7 +1068,7 @@
userRequest = await showAIOptimizeModal({
title: '优化大纲',
subtitle: '让AI帮助您优化整个PPT大纲结构和内容',
- currentInfo: `大纲标题:${parsedOutline.title}
幻灯片数量:${parsedOutline.slides ? parsedOutline.slides.length : 0} 页
场景:${parsedOutline.metadata?.scenario || '通用'}`,
+ currentInfo: `大纲标题:${parsedOutline.title || ''}\n幻灯片数量:${parsedOutline.slides ? parsedOutline.slides.length : 0} 页\n场景:${parsedOutline.metadata?.scenario || '通用'}`,
suggestions: [
'增加更多文字说明和技术细节',
'重新组织结构,优化逻辑流程',
@@ -1146,7 +1184,7 @@
userRequest = await showAIOptimizeModal({
title: `AI优化 - 第${slideIndex + 1}页`,
subtitle: '让AI帮助您优化这一页的内容',
- currentInfo: `标题:${title}
${subtitle ? `副标题:${subtitle}
` : ''}类型:${slideType}
内容要点:${contentPoints.length}个`,
+ currentInfo: `标题:${title}\n${subtitle ? `副标题:${subtitle}\n` : ''}类型:${slideType}\n内容要点:${contentPoints.length}个`,
suggestions: [
'增加更多技术细节和实例',
'简化内容,突出核心要点',
diff --git a/src/landppt/web/templates/components/project/todo_board/extra_js_1.html b/src/landppt/web/templates/components/project/todo_board/extra_js_1.html
index 99827e5..18cd6c4 100644
--- a/src/landppt/web/templates/components/project/todo_board/extra_js_1.html
+++ b/src/landppt/web/templates/components/project/todo_board/extra_js_1.html
@@ -5423,13 +5423,17 @@
// 创建美化的AI优化需求输入弹窗
function showAIOptimizeModal(config) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
// 创建模态框遮罩
const modal = document.createElement('div');
modal.className = 'ai-optimize-modal';
+ modal.setAttribute('role', 'dialog');
+ modal.setAttribute('aria-modal', 'true');
+ modal.setAttribute('aria-label', String(config.title || 'AI优化'));
modal.style.cssText = `
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
- background: rgba(255, 255, 255, 0.98);
+ background: rgba(15, 23, 42, 0.4);
+ backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
z-index: 10000; display: flex; align-items: center; justify-content: center;
padding: 20px; animation: fadeIn 0.3s ease;
`;
@@ -5475,6 +5479,7 @@
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid #111;
+ font-family: inherit;
}
.ai-optimize-modal .suggestion-tag:hover {
transform: translateY(-2px);
@@ -5486,6 +5491,14 @@
border-color: #111;
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1);
}
+ .ai-optimize-modal textarea.shake {
+ animation: aiOptimizeShake 0.5s;
+ }
+ @keyframes aiOptimizeShake {
+ 0%, 100% { transform: translateX(0); }
+ 25% { transform: translateX(-10px); }
+ 75% { transform: translateX(10px); }
+ }
@@ -5494,11 +5507,11 @@
- ${config.title}
+
-
${config.subtitle}
+
-
@@ -5511,7 +5524,7 @@
当前内容
- ${config.currentInfo}
+
@@ -5520,7 +5533,7 @@
-
+
@@ -5528,9 +5541,7 @@
-
- ${suggestions.map(s => `${s}`).join('')}
-
+
@@ -5540,58 +5551,85 @@
AI将根据您的需求智能优化内容
-
+
取消
-
+
开始优化
`;
- modal.appendChild(content);
- document.body.appendChild(modal);
+ const previousFocus = document.activeElement;
+ const input = content.querySelector('.ai-optimize-input');
+ const confirmBtn = content.querySelector('.ai-optimize-confirm');
+ const currentInfoElement = content.querySelector('.ai-optimize-current-info');
+ content.querySelector('.ai-optimize-title').textContent = String(config.title || '');
+ content.querySelector('.ai-optimize-subtitle').textContent = String(config.subtitle || '');
+ currentInfoElement.textContent = String(config.currentInfo || '');
- // 聚焦输入框
- setTimeout(() => {
- const input = document.getElementById('aiOptimizeInput');
- if (input) input.focus();
- }, 100);
-
- // 点击背景关闭
- modal.addEventListener('click', (e) => {
- if (e.target === modal) {
- modal.remove();
- reject('用户取消');
- }
+ const suggestionList = content.querySelector('.ai-optimize-suggestion-list');
+ suggestions.forEach((suggestion) => {
+ const suggestionButton = document.createElement('button');
+ suggestionButton.type = 'button';
+ suggestionButton.className = 'suggestion-tag';
+ suggestionButton.textContent = String(suggestion);
+ suggestionButton.addEventListener('click', () => {
+ input.value = String(suggestion);
+ input.focus();
+ });
+ suggestionList.appendChild(suggestionButton);
});
- // 确认按钮
- const confirmBtn = document.getElementById('confirmOptimizeBtn');
- confirmBtn.onclick = () => {
- const input = document.getElementById('aiOptimizeInput');
- const value = input?.value.trim();
- if (!value) {
- // 输入框抖动动画
- input.style.animation = 'shake 0.5s';
- setTimeout(() => { input.style.animation = ''; }, 500);
+ let closed = false;
+ function closeModal(value = null) {
+ if (closed) return;
+ closed = true;
+ document.removeEventListener('keydown', handleKeydown);
+ modal.remove();
+ previousFocus?.focus?.();
+ resolve(value);
+ }
+
+ function handleKeydown(event) {
+ if (event.key === 'Escape') {
+ event.preventDefault();
+ closeModal();
return;
}
- modal.remove();
- resolve(value);
- };
-
- // 添加shake动画
- const style = document.createElement('style');
- style.textContent = `
- @keyframes shake {
- 0%, 100% { transform: translateX(0); }
- 25% { transform: translateX(-10px); }
- 75% { transform: translateX(10px); }
+ if (event.key !== 'Tab') return;
+ const focusable = content.querySelectorAll('button:not([disabled]), textarea:not([disabled])');
+ const first = focusable[0];
+ const last = focusable[focusable.length - 1];
+ if (event.shiftKey && document.activeElement === first) {
+ event.preventDefault();
+ last.focus();
+ } else if (!event.shiftKey && document.activeElement === last) {
+ event.preventDefault();
+ first.focus();
+ }
}
- `;
- document.head.appendChild(style);
+
+ content.querySelector('.ai-optimize-modal__close').addEventListener('click', () => closeModal());
+ content.querySelector('.ai-optimize-cancel').addEventListener('click', () => closeModal());
+ modal.addEventListener('click', (event) => {
+ if (event.target === modal) closeModal();
+ });
+ confirmBtn.addEventListener('click', () => {
+ const value = input.value.trim();
+ if (!value) {
+ input.classList.add('shake');
+ setTimeout(() => input.classList.remove('shake'), 500);
+ return;
+ }
+ closeModal(value);
+ });
+
+ modal.appendChild(content);
+ document.body.appendChild(modal);
+ document.addEventListener('keydown', handleKeydown);
+ requestAnimationFrame(() => input.focus());
});
}
@@ -5633,7 +5671,7 @@
userRequest = await showAIOptimizeModal({
title: `AI优化 - 第${slideIndex + 1}页`,
subtitle: '让AI帮助您优化这一页的内容',
- currentInfo: `标题:${escapeHtml(title)}
${subtitle ? `副标题:${escapeHtml(subtitle)}
` : ''}类型:${escapeHtml(slideType)}
内容要点:${contentPoints.length}个`,
+ currentInfo: `标题:${title}\n${subtitle ? `副标题:${subtitle}\n` : ''}类型:${slideType}\n内容要点:${contentPoints.length}个`,
suggestions: [
'增加更多技术细节和实例',
'简化内容,突出核心要点',
@@ -5792,7 +5830,7 @@
userRequest = await showAIOptimizeModal({
title: '优化大纲',
subtitle: '让AI帮助您优化整个PPT大纲结构和内容',
- currentInfo: `大纲标题:${escapeHtml(parsedOutline.title)}
幻灯片数量:${parsedOutline.slides ? parsedOutline.slides.length : 0} 页
场景:${escapeHtml(parsedOutline.metadata?.scenario) || '通用'}`,
+ currentInfo: `大纲标题:${parsedOutline.title || ''}\n幻灯片数量:${parsedOutline.slides ? parsedOutline.slides.length : 0} 页\n场景:${parsedOutline.metadata?.scenario || '通用'}`,
suggestions: [
'增加更多文字说明',
'重新组织结构,优化逻辑流程',
diff --git a/tests/test_project_workflow_regressions.py b/tests/test_project_workflow_regressions.py
index ca9f576..fe13627 100644
--- a/tests/test_project_workflow_regressions.py
+++ b/tests/test_project_workflow_regressions.py
@@ -335,3 +335,32 @@ def test_editor_sidebar_thumbnail_refresh_recalculates_scale_without_overriding_
assert "aspect-ratio: 16 / 9;" in css
assert "height: 95px" not in css
assert "scale(0.1875)" in css
+
+
+def test_ai_optimize_modals_settle_safely_and_use_scoped_dom():
+ sources = [
+ _read("src/landppt/web/templates/components/project/todo_board/extra_js_1.html"),
+ _read("src/landppt/web/templates/components/project/detail/extra_js_1.html"),
+ _read(
+ "src/landppt/web/static/js/pages/project/slides_editor/"
+ "projectSlidesEditor.aiOptimize.js"
+ ),
+ ]
+
+ for source in sources:
+ assert "function closeModal(value = null)" in source
+ assert "content.querySelector('.ai-optimize-input')" in source
+ assert "suggestionButton.textContent = String(suggestion)" in source
+ assert "currentInfoElement.textContent = String(config.currentInfo || '')" in source
+ assert "document.addEventListener('keydown', handleKeydown)" in source
+ assert ".ai-optimize-modal__close').addEventListener('click', () => closeModal())" in source
+ assert ".ai-optimize-cancel').addEventListener('click', () => closeModal())" in source
+ assert "if (event.target === modal) closeModal()" in source
+ assert 'id="aiOptimizeInput"' not in source
+ assert 'id="confirmOptimizeBtn"' not in source
+ assert "${suggestions.map" not in source
+ assert "${config.currentInfo}" not in source
+ assert "onclick=\"this.closest('.ai-optimize-modal').remove()\"" not in source
+
+ assert "document.head.appendChild(style)" not in sources[0]
+ assert "document.head.appendChild(style)" not in sources[1]