890 lines
47 KiB
HTML
890 lines
47 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="ru">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
|||
|
|
<title>Тест мобильной адаптации AI Drawer</title>
|
|||
|
|
<style>
|
|||
|
|
body {
|
|||
|
|
font-family: Arial, sans-serif;
|
|||
|
|
margin: 0;
|
|||
|
|
padding: 20px;
|
|||
|
|
background: #f5f5f5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-container {
|
|||
|
|
max-width: 800px;
|
|||
|
|
margin: 0 auto;
|
|||
|
|
background: white;
|
|||
|
|
padding: 20px;
|
|||
|
|
border-radius: 10px;
|
|||
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-section {
|
|||
|
|
margin-bottom: 30px;
|
|||
|
|
padding: 20px;
|
|||
|
|
border: 1px solid #ddd;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-title {
|
|||
|
|
color: #007bff;
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-description {
|
|||
|
|
color: #666;
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-button {
|
|||
|
|
background: #007bff;
|
|||
|
|
color: white;
|
|||
|
|
border: none;
|
|||
|
|
padding: 12px 24px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
font-size: 16px;
|
|||
|
|
margin: 5px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-button:hover {
|
|||
|
|
background: #0056b3;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-button.secondary {
|
|||
|
|
background: #6c757d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.test-button.secondary:hover {
|
|||
|
|
background: #545b62;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status {
|
|||
|
|
padding: 10px;
|
|||
|
|
border-radius: 5px;
|
|||
|
|
margin: 10px 0;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status.success {
|
|||
|
|
background: #d4edda;
|
|||
|
|
color: #155724;
|
|||
|
|
border: 1px solid #c3e6cb;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status.info {
|
|||
|
|
background: #d1ecf1;
|
|||
|
|
color: #0c5460;
|
|||
|
|
border: 1px solid #bee5eb;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status.warning {
|
|||
|
|
background: #fff3cd;
|
|||
|
|
color: #856404;
|
|||
|
|
border: 1px solid #ffeaa7;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.device-info {
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
padding: 15px;
|
|||
|
|
border-radius: 5px;
|
|||
|
|
margin: 15px 0;
|
|||
|
|
font-family: monospace;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.instructions {
|
|||
|
|
background: #e7f3ff;
|
|||
|
|
padding: 15px;
|
|||
|
|
border-radius: 5px;
|
|||
|
|
border-left: 4px solid #007bff;
|
|||
|
|
margin: 15px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.instructions h4 {
|
|||
|
|
margin-top: 0;
|
|||
|
|
color: #007bff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.instructions ol {
|
|||
|
|
margin: 10px 0;
|
|||
|
|
padding-left: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.instructions li {
|
|||
|
|
margin: 5px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.log-container {
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
border: 1px solid #dee2e6;
|
|||
|
|
border-radius: 5px;
|
|||
|
|
padding: 15px;
|
|||
|
|
margin: 15px 0;
|
|||
|
|
font-family: 'Courier New', monospace;
|
|||
|
|
font-size: 12px;
|
|||
|
|
max-height: 300px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
white-space: pre-wrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.log-entry {
|
|||
|
|
margin: 2px 0;
|
|||
|
|
padding: 2px 5px;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.log-info {
|
|||
|
|
background: #d1ecf1;
|
|||
|
|
color: #0c5460;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.log-success {
|
|||
|
|
background: #d4edda;
|
|||
|
|
color: #155724;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.log-warning {
|
|||
|
|
background: #fff3cd;
|
|||
|
|
color: #856404;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.log-error {
|
|||
|
|
background: #f8d7da;
|
|||
|
|
color: #721c24;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.log-debug {
|
|||
|
|
background: #e2e3e5;
|
|||
|
|
color: #383d41;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="test-container">
|
|||
|
|
<h1>🧪 Тест мобильной адаптации AI Drawer</h1>
|
|||
|
|
|
|||
|
|
<div class="device-info">
|
|||
|
|
<strong>Информация об устройстве:</strong><br>
|
|||
|
|
Ширина экрана: <span id="screen-width">-</span>px<br>
|
|||
|
|
Высота экрана: <span id="screen-height">-</span>px<br>
|
|||
|
|
User Agent: <span id="user-agent">-</span><br>
|
|||
|
|
Мобильное устройство: <span id="is-mobile">-</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="test-section">
|
|||
|
|
<div class="test-title">📱 Тест 1: Открытие AI Drawer на мобильном</div>
|
|||
|
|
<div class="test-description">
|
|||
|
|
Проверяем, что AI Drawer корректно открывается на мобильных устройствах и занимает всю ширину экрана.
|
|||
|
|
</div>
|
|||
|
|
<button class="test-button" onclick="testOpenDrawer()">Открыть AI Drawer</button>
|
|||
|
|
<button class="test-button secondary" onclick="testCloseDrawer()">Закрыть AI Drawer</button>
|
|||
|
|
<div id="test1-status"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="test-section">
|
|||
|
|
<div class="test-title">⌨️ Тест 2: Адаптация к виртуальной клавиатуре</div>
|
|||
|
|
<div class="test-description">
|
|||
|
|
Проверяем, что поле ввода остается доступным при появлении виртуальной клавиатуры, правильно ужимается под размер экрана и поднимается выше от нижнего края.
|
|||
|
|
</div>
|
|||
|
|
<div class="instructions">
|
|||
|
|
<h4>Инструкции для тестирования:</h4>
|
|||
|
|
<ol>
|
|||
|
|
<li>Откройте AI Drawer</li>
|
|||
|
|
<li>Нажмите на поле ввода сообщения</li>
|
|||
|
|
<li>Проверьте, что поле ввода и кнопка "Отправить" остаются видимыми</li>
|
|||
|
|
<li>Проверьте, что поле ввода ужимается под размер экрана</li>
|
|||
|
|
<li>Проверьте, что поле ввода поднимается выше от нижнего края (20-40px)</li>
|
|||
|
|
<li>Проверьте, что кнопка "Отправить" не перекрывается</li>
|
|||
|
|
<li>Проверьте, что чат автоматически прокручивается к последнему сообщению</li>
|
|||
|
|
</ol>
|
|||
|
|
</div>
|
|||
|
|
<button class="test-button" onclick="testKeyboardAdaptation()">Начать тест клавиатуры</button>
|
|||
|
|
<div id="test2-status"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="test-section">
|
|||
|
|
<div class="test-title">📏 Тест 3: Размеры элементов на мобильном</div>
|
|||
|
|
<div class="test-description">
|
|||
|
|
Проверяем, что все элементы имеют подходящие размеры для мобильных устройств.
|
|||
|
|
</div>
|
|||
|
|
<button class="test-button" onclick="testElementSizes()">Проверить размеры</button>
|
|||
|
|
<div id="test3-status"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="test-section">
|
|||
|
|
<div class="test-title">🔄 Тест 4: Прокрутка и навигация</div>
|
|||
|
|
<div class="test-description">
|
|||
|
|
Проверяем плавность прокрутки и навигации в чате.
|
|||
|
|
</div>
|
|||
|
|
<button class="test-button" onclick="testScrolling()">Тест прокрутки</button>
|
|||
|
|
<div id="test4-status"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="test-section">
|
|||
|
|
<div class="test-title">🎨 Тест 5: Адаптивность интерфейса</div>
|
|||
|
|
<div class="test-description">
|
|||
|
|
Проверяем, что интерфейс корректно адаптируется к разным размерам экрана.
|
|||
|
|
</div>
|
|||
|
|
<button class="test-button" onclick="testResponsiveness()">Тест адаптивности</button>
|
|||
|
|
<div id="test5-status"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="instructions">
|
|||
|
|
<h4>🔧 Дополнительные тесты:</h4>
|
|||
|
|
<ol>
|
|||
|
|
<li><strong>Поворот экрана:</strong> Поверните устройство и проверьте адаптацию</li>
|
|||
|
|
<li><strong>Разные браузеры:</strong> Протестируйте в Chrome, Safari, Firefox</li>
|
|||
|
|
<li><strong>Разные размеры экрана:</strong> Протестируйте на разных устройствах</li>
|
|||
|
|
<li><strong>Производительность:</strong> Проверьте плавность анимаций</li>
|
|||
|
|
</ol>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="test-section">
|
|||
|
|
<div class="test-title">📋 Лог тестирования</div>
|
|||
|
|
<div class="test-description">
|
|||
|
|
Здесь отображаются подробные логи всех тестов и проверок.
|
|||
|
|
</div>
|
|||
|
|
<button class="test-button secondary" onclick="clearLog()">Очистить лог</button>
|
|||
|
|
<button class="test-button secondary" onclick="exportLog()">Экспорт лога</button>
|
|||
|
|
<button class="test-button secondary" onclick="loadServerLogs()">Загрузить с сервера</button>
|
|||
|
|
<button class="test-button secondary" onclick="clearServerLogs()">Очистить сервер</button>
|
|||
|
|
<button class="test-button secondary" onclick="checkServerStatus()">Статус сервера</button>
|
|||
|
|
<div id="test-log" class="log-container"></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Подключаем AI Drawer -->
|
|||
|
|
<link rel="stylesheet" href="layouts/v7/resources/css/ai-drawer.css">
|
|||
|
|
<script src="layouts/v7/resources/js/ai-drawer.js"></script>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
// Система логирования
|
|||
|
|
let testLog = [];
|
|||
|
|
|
|||
|
|
function addLog(message, type = 'info', details = null) {
|
|||
|
|
const timestamp = new Date().toLocaleTimeString();
|
|||
|
|
const logEntry = {
|
|||
|
|
timestamp: timestamp,
|
|||
|
|
message: message,
|
|||
|
|
type: type,
|
|||
|
|
details: details,
|
|||
|
|
userAgent: navigator.userAgent,
|
|||
|
|
screenSize: `${window.innerWidth}x${window.innerHeight}`,
|
|||
|
|
url: window.location.href
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
testLog.push(logEntry);
|
|||
|
|
|
|||
|
|
const logContainer = document.getElementById('test-log');
|
|||
|
|
if (logContainer) {
|
|||
|
|
const logElement = document.createElement('div');
|
|||
|
|
logElement.className = `log-entry log-${type}`;
|
|||
|
|
|
|||
|
|
let logText = `[${timestamp}] ${message}`;
|
|||
|
|
if (details) {
|
|||
|
|
logText += `\nДетали: ${JSON.stringify(details, null, 2)}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logElement.textContent = logText;
|
|||
|
|
logContainer.appendChild(logElement);
|
|||
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Также выводим в консоль браузера
|
|||
|
|
console.log(`[TEST LOG ${type.toUpperCase()}] ${message}`, details || '');
|
|||
|
|
|
|||
|
|
// Отправляем на сервер
|
|||
|
|
sendLogToServer(logEntry);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function sendLogToServer(logEntry) {
|
|||
|
|
fetch('/test_log_server.php?action=write', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify(logEntry)
|
|||
|
|
}).catch(error => {
|
|||
|
|
console.warn('Не удалось отправить лог на сервер:', error);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function clearLog() {
|
|||
|
|
testLog = [];
|
|||
|
|
const logContainer = document.getElementById('test-log');
|
|||
|
|
if (logContainer) {
|
|||
|
|
logContainer.innerHTML = '';
|
|||
|
|
}
|
|||
|
|
addLog('Лог очищен', 'info');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function exportLog() {
|
|||
|
|
const logText = testLog.map(entry =>
|
|||
|
|
`[${entry.timestamp}] ${entry.type.toUpperCase()}: ${entry.message}` +
|
|||
|
|
(entry.details ? `\nДетали: ${JSON.stringify(entry.details, null, 2)}` : '')
|
|||
|
|
).join('\n\n');
|
|||
|
|
|
|||
|
|
const blob = new Blob([logText], { type: 'text/plain' });
|
|||
|
|
const url = URL.createObjectURL(blob);
|
|||
|
|
const a = document.createElement('a');
|
|||
|
|
a.href = url;
|
|||
|
|
a.download = `ai-drawer-test-log-${new Date().toISOString().slice(0, 19)}.txt`;
|
|||
|
|
a.click();
|
|||
|
|
URL.revokeObjectURL(url);
|
|||
|
|
|
|||
|
|
addLog('Лог экспортирован', 'success');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function loadServerLogs() {
|
|||
|
|
fetch('/test_log_server.php?action=read&limit=50')
|
|||
|
|
.then(response => response.json())
|
|||
|
|
.then(data => {
|
|||
|
|
if (data.success) {
|
|||
|
|
addLog(`Загружено ${data.logs.length} записей с сервера`, 'info');
|
|||
|
|
|
|||
|
|
// Добавляем серверные логи в контейнер
|
|||
|
|
const logContainer = document.getElementById('test-log');
|
|||
|
|
if (logContainer) {
|
|||
|
|
data.logs.forEach(logEntry => {
|
|||
|
|
const logElement = document.createElement('div');
|
|||
|
|
logElement.className = `log-entry log-${logEntry.data.type || 'info'}`;
|
|||
|
|
logElement.style.opacity = '0.7'; // Помечаем как серверные
|
|||
|
|
|
|||
|
|
let logText = `[СЕРВЕР ${logEntry.timestamp}] ${logEntry.data.message}`;
|
|||
|
|
if (logEntry.data.details) {
|
|||
|
|
logText += `\nДетали: ${JSON.stringify(logEntry.data.details, null, 2)}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logElement.textContent = logText;
|
|||
|
|
logContainer.insertBefore(logElement, logContainer.firstChild);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
addLog('Ошибка загрузки серверных логов', 'error', data.message);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch(error => {
|
|||
|
|
addLog('Ошибка подключения к серверу логов', 'error', error.message);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function clearServerLogs() {
|
|||
|
|
fetch('/test_log_server.php?action=clear')
|
|||
|
|
.then(response => response.json())
|
|||
|
|
.then(data => {
|
|||
|
|
if (data.success) {
|
|||
|
|
addLog('Серверные логи очищены', 'success');
|
|||
|
|
} else {
|
|||
|
|
addLog('Ошибка очистки серверных логов', 'error', data.message);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch(error => {
|
|||
|
|
addLog('Ошибка подключения к серверу логов', 'error', error.message);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function checkServerStatus() {
|
|||
|
|
fetch('/test_log_server.php?action=status')
|
|||
|
|
.then(response => response.json())
|
|||
|
|
.then(data => {
|
|||
|
|
if (data.success) {
|
|||
|
|
addLog('Статус сервера логов', 'info', data.status);
|
|||
|
|
} else {
|
|||
|
|
addLog('Ошибка получения статуса сервера', 'error', data.message);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch(error => {
|
|||
|
|
addLog('Сервер логов недоступен', 'warning', error.message);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Обновляем информацию об устройстве
|
|||
|
|
function updateDeviceInfo() {
|
|||
|
|
document.getElementById('screen-width').textContent = window.innerWidth;
|
|||
|
|
document.getElementById('screen-height').textContent = window.innerHeight;
|
|||
|
|
document.getElementById('user-agent').textContent = navigator.userAgent.substring(0, 50) + '...';
|
|||
|
|
document.getElementById('is-mobile').textContent = window.innerWidth <= 768 ? 'Да' : 'Нет';
|
|||
|
|
|
|||
|
|
addLog('Информация об устройстве обновлена', 'info', {
|
|||
|
|
width: window.innerWidth,
|
|||
|
|
height: window.innerHeight,
|
|||
|
|
isMobile: window.innerWidth <= 768,
|
|||
|
|
userAgent: navigator.userAgent
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Обновляем информацию при изменении размера окна
|
|||
|
|
window.addEventListener('resize', updateDeviceInfo);
|
|||
|
|
updateDeviceInfo();
|
|||
|
|
|
|||
|
|
// Инициализируем AI Drawer
|
|||
|
|
let aiDrawerInstance;
|
|||
|
|
|
|||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|||
|
|
addLog('Начало инициализации AI Drawer', 'info');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
aiDrawerInstance = new AIDrawer();
|
|||
|
|
addLog('AI Drawer успешно инициализирован', 'success', {
|
|||
|
|
drawer: !!aiDrawerInstance.drawer,
|
|||
|
|
toggleBtn: !!aiDrawerInstance.toggleBtn,
|
|||
|
|
closeBtn: !!aiDrawerInstance.closeBtn,
|
|||
|
|
chatInput: !!aiDrawerInstance.chatInput,
|
|||
|
|
sendButton: !!aiDrawerInstance.sendButton
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
addLog('Ошибка инициализации AI Drawer', 'error', {
|
|||
|
|
error: error.message,
|
|||
|
|
stack: error.stack
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Тест 1: Открытие/закрытие drawer
|
|||
|
|
function testOpenDrawer() {
|
|||
|
|
const status = document.getElementById('test1-status');
|
|||
|
|
addLog('Начало теста: Открытие AI Drawer', 'info');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (aiDrawerInstance) {
|
|||
|
|
aiDrawerInstance.open();
|
|||
|
|
addLog('AI Drawer открыт', 'success');
|
|||
|
|
status.innerHTML = '<div class="status success">✅ AI Drawer успешно открыт</div>';
|
|||
|
|
|
|||
|
|
// Проверяем размеры
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const drawer = document.querySelector('.ai-drawer');
|
|||
|
|
if (drawer) {
|
|||
|
|
const rect = drawer.getBoundingClientRect();
|
|||
|
|
const isFullWidth = window.innerWidth <= 768 ? rect.width >= window.innerWidth * 0.95 : rect.width >= 350;
|
|||
|
|
|
|||
|
|
addLog('Проверка размеров drawer', 'debug', {
|
|||
|
|
drawerWidth: rect.width,
|
|||
|
|
screenWidth: window.innerWidth,
|
|||
|
|
isMobile: window.innerWidth <= 768,
|
|||
|
|
isFullWidth: isFullWidth,
|
|||
|
|
expectedMinWidth: window.innerWidth <= 768 ? window.innerWidth * 0.95 : 350
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (isFullWidth) {
|
|||
|
|
status.innerHTML += '<div class="status success">✅ Размеры drawer корректны</div>';
|
|||
|
|
addLog('Размеры drawer корректны', 'success');
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML += '<div class="status warning">⚠️ Размеры drawer могут быть некорректными</div>';
|
|||
|
|
addLog('Размеры drawer некорректны', 'warning', {
|
|||
|
|
actualWidth: rect.width,
|
|||
|
|
expectedMinWidth: window.innerWidth <= 768 ? window.innerWidth * 0.95 : 350
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
addLog('Drawer элемент не найден', 'error');
|
|||
|
|
}
|
|||
|
|
}, 500);
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ AI Drawer не инициализирован</div>';
|
|||
|
|
addLog('AI Drawer не инициализирован', 'error');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ Ошибка: ' + error.message + '</div>';
|
|||
|
|
addLog('Ошибка при открытии drawer', 'error', {
|
|||
|
|
error: error.message,
|
|||
|
|
stack: error.stack
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function testCloseDrawer() {
|
|||
|
|
const status = document.getElementById('test1-status');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (aiDrawerInstance) {
|
|||
|
|
aiDrawerInstance.close();
|
|||
|
|
status.innerHTML = '<div class="status success">✅ AI Drawer успешно закрыт</div>';
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ AI Drawer не инициализирован</div>';
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ Ошибка: ' + error.message + '</div>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Тест 2: Адаптация к клавиатуре
|
|||
|
|
function testKeyboardAdaptation() {
|
|||
|
|
const status = document.getElementById('test2-status');
|
|||
|
|
addLog('Начало теста: Адаптация к виртуальной клавиатуре', 'info');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (aiDrawerInstance) {
|
|||
|
|
aiDrawerInstance.open();
|
|||
|
|
addLog('AI Drawer открыт для теста клавиатуры', 'info');
|
|||
|
|
|
|||
|
|
// Добавляем тестовые сообщения
|
|||
|
|
aiDrawerInstance.addMessage('Тестовое сообщение 1', false);
|
|||
|
|
aiDrawerInstance.addMessage('Тестовое сообщение 2', true);
|
|||
|
|
aiDrawerInstance.addMessage('Тестовое сообщение 3', false);
|
|||
|
|
addLog('Добавлены тестовые сообщения', 'info');
|
|||
|
|
|
|||
|
|
status.innerHTML = '<div class="status info">ℹ️ Добавлены тестовые сообщения. Теперь нажмите на поле ввода для проверки адаптации к клавиатуре.</div>';
|
|||
|
|
|
|||
|
|
// Проверяем поле ввода
|
|||
|
|
const input = document.querySelector('#ai-chat-input');
|
|||
|
|
if (input) {
|
|||
|
|
addLog('Поле ввода найдено, добавляем обработчик фокуса', 'info');
|
|||
|
|
input.addEventListener('focus', function() {
|
|||
|
|
addLog('Поле ввода получило фокус', 'info');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const inputContainer = document.querySelector('.ai-chat-input-container');
|
|||
|
|
const inputField = document.querySelector('#ai-chat-input');
|
|||
|
|
const sendButton = document.querySelector('#ai-send-button');
|
|||
|
|
|
|||
|
|
addLog('Начало проверки элементов после фокуса', 'debug', {
|
|||
|
|
inputContainer: !!inputContainer,
|
|||
|
|
inputField: !!inputField,
|
|||
|
|
sendButton: !!sendButton
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Проверяем видимость контейнера
|
|||
|
|
const containerRect = inputContainer.getBoundingClientRect();
|
|||
|
|
const isContainerVisible = containerRect.top >= 0 && containerRect.bottom <= window.innerHeight;
|
|||
|
|
|
|||
|
|
// Проверяем размеры поля ввода
|
|||
|
|
const inputRect = inputField.getBoundingClientRect();
|
|||
|
|
const inputWidth = inputRect.width;
|
|||
|
|
const screenWidth = window.innerWidth;
|
|||
|
|
const inputWidthPercent = (inputWidth / screenWidth) * 100;
|
|||
|
|
|
|||
|
|
// Проверяем видимость кнопки отправки
|
|||
|
|
const buttonRect = sendButton.getBoundingClientRect();
|
|||
|
|
const isButtonVisible = buttonRect.top >= 0 && buttonRect.bottom <= window.innerHeight;
|
|||
|
|
|
|||
|
|
// Проверяем высоту поля ввода от нижнего края
|
|||
|
|
const inputBottom = inputRect.bottom;
|
|||
|
|
const screenHeight = window.innerHeight;
|
|||
|
|
const distanceFromBottom = screenHeight - inputBottom;
|
|||
|
|
|
|||
|
|
addLog('Измерения элементов', 'debug', {
|
|||
|
|
containerRect: {
|
|||
|
|
top: containerRect.top,
|
|||
|
|
bottom: containerRect.bottom,
|
|||
|
|
left: containerRect.left,
|
|||
|
|
right: containerRect.right,
|
|||
|
|
width: containerRect.width,
|
|||
|
|
height: containerRect.height
|
|||
|
|
},
|
|||
|
|
inputRect: {
|
|||
|
|
top: inputRect.top,
|
|||
|
|
bottom: inputRect.bottom,
|
|||
|
|
left: inputRect.left,
|
|||
|
|
right: inputRect.right,
|
|||
|
|
width: inputRect.width,
|
|||
|
|
height: inputRect.height
|
|||
|
|
},
|
|||
|
|
buttonRect: {
|
|||
|
|
top: buttonRect.top,
|
|||
|
|
bottom: buttonRect.bottom,
|
|||
|
|
left: buttonRect.left,
|
|||
|
|
right: buttonRect.right,
|
|||
|
|
width: buttonRect.width,
|
|||
|
|
height: buttonRect.height
|
|||
|
|
},
|
|||
|
|
screenDimensions: {
|
|||
|
|
width: screenWidth,
|
|||
|
|
height: screenHeight
|
|||
|
|
},
|
|||
|
|
calculations: {
|
|||
|
|
inputWidthPercent: inputWidthPercent,
|
|||
|
|
distanceFromBottom: distanceFromBottom,
|
|||
|
|
isContainerVisible: isContainerVisible,
|
|||
|
|
isButtonVisible: isButtonVisible
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (isContainerVisible) {
|
|||
|
|
status.innerHTML += '<div class="status success">✅ Поле ввода остается видимым при появлении клавиатуры</div>';
|
|||
|
|
addLog('Поле ввода остается видимым', 'success');
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML += '<div class="status warning">⚠️ Поле ввода может быть скрыто клавиатурой</div>';
|
|||
|
|
addLog('Поле ввода может быть скрыто клавиатурой', 'warning');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (inputWidthPercent >= 60 && inputWidthPercent <= 80) {
|
|||
|
|
status.innerHTML += '<div class="status success">✅ Поле ввода правильно ужимается под размер экрана (' + Math.round(inputWidthPercent) + '%)</div>';
|
|||
|
|
addLog('Поле ввода правильно ужимается', 'success', { widthPercent: inputWidthPercent });
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML += '<div class="status warning">⚠️ Поле ввода может быть слишком широким или узким (' + Math.round(inputWidthPercent) + '%)</div>';
|
|||
|
|
addLog('Поле ввода неправильно ужимается', 'warning', { widthPercent: inputWidthPercent });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isButtonVisible) {
|
|||
|
|
status.innerHTML += '<div class="status success">✅ Кнопка "Отправить" остается видимой</div>';
|
|||
|
|
addLog('Кнопка "Отправить" остается видимой', 'success');
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML += '<div class="status warning">⚠️ Кнопка "Отправить" может быть перекрыта</div>';
|
|||
|
|
addLog('Кнопка "Отправить" может быть перекрыта', 'warning');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (distanceFromBottom >= 200 && distanceFromBottom <= 400) {
|
|||
|
|
status.innerHTML += '<div class="status success">✅ Поле ввода поднято на правильную высоту (' + Math.round(distanceFromBottom) + 'px от низа)</div>';
|
|||
|
|
addLog('Поле ввода поднято на правильную высоту', 'success', { distanceFromBottom: distanceFromBottom });
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML += '<div class="status warning">⚠️ Поле ввода может быть слишком низко или высоко (' + Math.round(distanceFromBottom) + 'px от низа)</div>';
|
|||
|
|
addLog('Поле ввода неправильно позиционировано', 'warning', { distanceFromBottom: distanceFromBottom });
|
|||
|
|
}
|
|||
|
|
}, 500);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ AI Drawer не инициализирован</div>';
|
|||
|
|
addLog('AI Drawer не инициализирован для теста клавиатуры', 'error');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ Ошибка: ' + error.message + '</div>';
|
|||
|
|
addLog('Ошибка в тесте клавиатуры', 'error', {
|
|||
|
|
error: error.message,
|
|||
|
|
stack: error.stack
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Тест 3: Размеры элементов
|
|||
|
|
function testElementSizes() {
|
|||
|
|
const status = document.getElementById('test3-status');
|
|||
|
|
addLog('Начало теста: Размеры элементов', 'info');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (aiDrawerInstance) {
|
|||
|
|
aiDrawerInstance.open();
|
|||
|
|
addLog('AI Drawer открыт для теста размеров', 'info');
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const input = document.querySelector('#ai-chat-input');
|
|||
|
|
const button = document.querySelector('#ai-send-button');
|
|||
|
|
const header = document.querySelector('.ai-drawer-header');
|
|||
|
|
|
|||
|
|
addLog('Проверка размеров элементов', 'debug', {
|
|||
|
|
input: !!input,
|
|||
|
|
button: !!button,
|
|||
|
|
header: !!header
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
let results = [];
|
|||
|
|
|
|||
|
|
if (input) {
|
|||
|
|
const inputRect = input.getBoundingClientRect();
|
|||
|
|
const isInputSizeOK = inputRect.height >= 40 && inputRect.height <= 60;
|
|||
|
|
results.push(isInputSizeOK ? '✅ Поле ввода: размер OK' : '⚠️ Поле ввода: размер может быть некорректным');
|
|||
|
|
addLog('Размер поля ввода', isInputSizeOK ? 'success' : 'warning', {
|
|||
|
|
height: inputRect.height,
|
|||
|
|
expectedRange: '40-60px',
|
|||
|
|
isOK: isInputSizeOK
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (button) {
|
|||
|
|
const buttonRect = button.getBoundingClientRect();
|
|||
|
|
const isButtonSizeOK = buttonRect.height >= 40 && buttonRect.height <= 60;
|
|||
|
|
results.push(isButtonSizeOK ? '✅ Кнопка отправки: размер OK' : '⚠️ Кнопка отправки: размер может быть некорректным');
|
|||
|
|
addLog('Размер кнопки отправки', isButtonSizeOK ? 'success' : 'warning', {
|
|||
|
|
height: buttonRect.height,
|
|||
|
|
expectedRange: '40-60px',
|
|||
|
|
isOK: isButtonSizeOK
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (header) {
|
|||
|
|
const headerRect = header.getBoundingClientRect();
|
|||
|
|
const isHeaderSizeOK = headerRect.height >= 50 && headerRect.height <= 80;
|
|||
|
|
results.push(isHeaderSizeOK ? '✅ Заголовок: размер OK' : '⚠️ Заголовок: размер может быть некорректным');
|
|||
|
|
addLog('Размер заголовка', isHeaderSizeOK ? 'success' : 'warning', {
|
|||
|
|
height: headerRect.height,
|
|||
|
|
expectedRange: '50-80px',
|
|||
|
|
isOK: isHeaderSizeOK
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
status.innerHTML = '<div class="status info">📏 Результаты проверки размеров:</div>' +
|
|||
|
|
results.map(r => '<div class="status ' + (r.includes('✅') ? 'success' : 'warning') + '">' + r + '</div>').join('');
|
|||
|
|
}, 500);
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ AI Drawer не инициализирован</div>';
|
|||
|
|
addLog('AI Drawer не инициализирован для теста размеров', 'error');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ Ошибка: ' + error.message + '</div>';
|
|||
|
|
addLog('Ошибка в тесте размеров', 'error', {
|
|||
|
|
error: error.message,
|
|||
|
|
stack: error.stack
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Тест 4: Прокрутка
|
|||
|
|
function testScrolling() {
|
|||
|
|
const status = document.getElementById('test4-status');
|
|||
|
|
addLog('Начало теста: Прокрутка и навигация', 'info');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (aiDrawerInstance) {
|
|||
|
|
aiDrawerInstance.open();
|
|||
|
|
addLog('AI Drawer открыт для теста прокрутки', 'info');
|
|||
|
|
|
|||
|
|
// Добавляем много сообщений для тестирования прокрутки
|
|||
|
|
for (let i = 1; i <= 10; i++) {
|
|||
|
|
aiDrawerInstance.addMessage(`Тестовое сообщение ${i} для проверки прокрутки`, i % 2 === 0);
|
|||
|
|
}
|
|||
|
|
addLog('Добавлено 10 тестовых сообщений', 'info');
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const content = document.querySelector('.ai-drawer-content');
|
|||
|
|
if (content) {
|
|||
|
|
const canScroll = content.scrollHeight > content.clientHeight;
|
|||
|
|
|
|||
|
|
addLog('Проверка прокрутки', 'debug', {
|
|||
|
|
scrollHeight: content.scrollHeight,
|
|||
|
|
clientHeight: content.clientHeight,
|
|||
|
|
canScroll: canScroll
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (canScroll) {
|
|||
|
|
status.innerHTML = '<div class="status success">✅ Прокрутка работает корректно</div>';
|
|||
|
|
addLog('Прокрутка работает корректно', 'success');
|
|||
|
|
|
|||
|
|
// Проверяем автоматическую прокрутку к последнему сообщению
|
|||
|
|
const lastMessage = content.querySelector('.ai-message:last-child');
|
|||
|
|
if (lastMessage) {
|
|||
|
|
const lastMessageRect = lastMessage.getBoundingClientRect();
|
|||
|
|
const contentRect = content.getBoundingClientRect();
|
|||
|
|
const isLastMessageVisible = lastMessageRect.bottom <= contentRect.bottom;
|
|||
|
|
|
|||
|
|
addLog('Проверка автоматической прокрутки', 'debug', {
|
|||
|
|
lastMessageRect: {
|
|||
|
|
top: lastMessageRect.top,
|
|||
|
|
bottom: lastMessageRect.bottom
|
|||
|
|
},
|
|||
|
|
contentRect: {
|
|||
|
|
top: contentRect.top,
|
|||
|
|
bottom: contentRect.bottom
|
|||
|
|
},
|
|||
|
|
isLastMessageVisible: isLastMessageVisible
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (isLastMessageVisible) {
|
|||
|
|
status.innerHTML += '<div class="status success">✅ Автоматическая прокрутка к последнему сообщению работает</div>';
|
|||
|
|
addLog('Автоматическая прокрутка работает', 'success');
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML += '<div class="status warning">⚠️ Автоматическая прокрутка может работать некорректно</div>';
|
|||
|
|
addLog('Автоматическая прокрутка работает некорректно', 'warning');
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
addLog('Последнее сообщение не найдено', 'warning');
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ Прокрутка может работать некорректно</div>';
|
|||
|
|
addLog('Прокрутка работает некорректно', 'warning');
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
addLog('Контейнер контента не найден', 'error');
|
|||
|
|
}
|
|||
|
|
}, 500);
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ AI Drawer не инициализирован</div>';
|
|||
|
|
addLog('AI Drawer не инициализирован для теста прокрутки', 'error');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ Ошибка: ' + error.message + '</div>';
|
|||
|
|
addLog('Ошибка в тесте прокрутки', 'error', {
|
|||
|
|
error: error.message,
|
|||
|
|
stack: error.stack
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Тест 5: Адаптивность
|
|||
|
|
function testResponsiveness() {
|
|||
|
|
const status = document.getElementById('test5-status');
|
|||
|
|
addLog('Начало теста: Адаптивность интерфейса', 'info');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (aiDrawerInstance) {
|
|||
|
|
aiDrawerInstance.open();
|
|||
|
|
addLog('AI Drawer открыт для теста адаптивности', 'info');
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const drawer = document.querySelector('.ai-drawer');
|
|||
|
|
if (drawer) {
|
|||
|
|
const rect = drawer.getBoundingClientRect();
|
|||
|
|
const isResponsive = window.innerWidth <= 768 ?
|
|||
|
|
rect.width >= window.innerWidth * 0.95 :
|
|||
|
|
rect.width >= 350 && rect.width <= 450;
|
|||
|
|
|
|||
|
|
addLog('Проверка адаптивности', 'debug', {
|
|||
|
|
drawerWidth: rect.width,
|
|||
|
|
screenWidth: window.innerWidth,
|
|||
|
|
isMobile: window.innerWidth <= 768,
|
|||
|
|
isResponsive: isResponsive,
|
|||
|
|
expectedRange: window.innerWidth <= 768 ?
|
|||
|
|
`${window.innerWidth * 0.95}px+` :
|
|||
|
|
'350-450px'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (isResponsive) {
|
|||
|
|
status.innerHTML = '<div class="status success">✅ Адаптивность работает корректно</div>';
|
|||
|
|
addLog('Адаптивность работает корректно', 'success');
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ Адаптивность может работать некорректно</div>';
|
|||
|
|
addLog('Адаптивность работает некорректно', 'warning');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Проверяем медиа-запросы
|
|||
|
|
const mediaQuery = window.matchMedia('(max-width: 768px)');
|
|||
|
|
const isMobileQuery = mediaQuery.matches;
|
|||
|
|
|
|||
|
|
addLog('Проверка медиа-запросов', 'debug', {
|
|||
|
|
mediaQuery: '(max-width: 768px)',
|
|||
|
|
isActive: isMobileQuery,
|
|||
|
|
screenWidth: window.innerWidth
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
status.innerHTML += '<div class="status info">📱 Медиа-запрос (max-width: 768px): ' + (isMobileQuery ? 'Активен' : 'Неактивен') + '</div>';
|
|||
|
|
} else {
|
|||
|
|
addLog('Drawer элемент не найден для теста адаптивности', 'error');
|
|||
|
|
}
|
|||
|
|
}, 500);
|
|||
|
|
} else {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ AI Drawer не инициализирован</div>';
|
|||
|
|
addLog('AI Drawer не инициализирован для теста адаптивности', 'error');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
status.innerHTML = '<div class="status warning">⚠️ Ошибка: ' + error.message + '</div>';
|
|||
|
|
addLog('Ошибка в тесте адаптивности', 'error', {
|
|||
|
|
error: error.message,
|
|||
|
|
stack: error.stack
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Дополнительная информация для отладки
|
|||
|
|
addLog('Тестовая страница загружена', 'info', {
|
|||
|
|
screenSize: `${window.innerWidth}x${window.innerHeight}`,
|
|||
|
|
isMobile: window.innerWidth <= 768,
|
|||
|
|
userAgent: navigator.userAgent
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Проверяем статус сервера логов при загрузке
|
|||
|
|
setTimeout(() => {
|
|||
|
|
checkServerStatus();
|
|||
|
|
}, 1000);
|
|||
|
|
|
|||
|
|
console.log('Mobile Keyboard Fix Test Page loaded');
|
|||
|
|
console.log('Screen size:', window.innerWidth + 'x' + window.innerHeight);
|
|||
|
|
console.log('Is mobile:', window.innerWidth <= 768);
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|