Corp/backend/test_reply_functionality.html
igor_nadein da7bf54c6c feat: Реализация полной функциональности опросов в чатах
- Исправлен PollSerializer для создания опросов с опциями
- Добавлено поле read_only_fields для корректной валидации
- Реализована отправка WebSocket уведомлений при создании опроса
- Исправлен импорт serialize_message из communications.serialization
- Добавлен обработчик poll_update в UserConsumer для WebSocket событий
- Исправлен эндпоинт /results/ для возврата user_voted_option_ids
- Добавлено поле voters в PollOptionSerializer для списка проголосовавших
- Реализована инициализация poll виджетов после рендера сообщений
- Добавлено игнорирование poll_update в notification-manager
- Исправлены стили для request list badges (height, word-break, flex)
- Восстановлены стили team-wheel (.wheel-focus, .scroll-arc) из git истории
- Реализовано отображение forwarded message info в messageRendererV2
- Множественные улучшения UI и UX для опросов

Fixes: создание опросов, голосование, отображение результатов, список проголосовавших
2026-01-22 01:33:42 +03:00

345 lines
15 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест функции ответа на сообщение</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<style>
body {
padding: 20px;
background: #f5f5f5;
}
.test-section {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.message-preview {
background: #e3f2fd;
border-radius: 12px;
padding: 12px;
margin: 10px 0;
}
.bubble {
background: #007bff;
color: white;
border-radius: 12px;
padding: 12px 16px;
max-width: 400px;
}
.reply-indicator {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 12px;
margin: 10px 0;
border-radius: 4px;
}
.log-output {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 12px;
font-family: 'Courier New', monospace;
font-size: 12px;
max-height: 300px;
overflow-y: auto;
margin-top: 10px;
}
.log-entry {
margin: 4px 0;
padding: 4px;
}
.log-entry.info { color: #0d6efd; }
.log-entry.warn { color: #ffc107; }
.log-entry.error { color: #dc3545; }
.log-entry.success { color: #198754; }
</style>
</head>
<body>
<div class="container">
<h1 class="mb-4">🧪 Тест функции ответа на сообщение</h1>
<!-- Статус загрузки реакций -->
<div class="test-section">
<h3>📊 Статус загрузки реакций</h3>
<div id="reactionsStatus" class="alert alert-info">
Проверка...
</div>
</div>
<!-- Тест 1: Контекстное меню -->
<div class="test-section">
<h3>1️⃣ Тест контекстного меню</h3>
<p>Симуляция вызова функции ответа из контекстного меню</p>
<div class="message-preview" data-message-id="123">
<div class="small text-secondary mb-2">
Иван Петров · 10:30
</div>
<div class="bubble">
Привет! Как дела с проектом?
</div>
</div>
<button id="testReplyBtn" class="btn btn-primary">
<i class="bi bi-reply-fill"></i> Ответить (через контекстное меню)
</button>
<div id="testReplyResult" class="mt-3"></div>
</div>
<!-- Тест 2: ChatFormManager -->
<div class="test-section">
<h3>2️⃣ Тест ChatFormManager</h3>
<p>Проверка состояния формы при ответе</p>
<form id="chatForm" action="/upload/" method="POST">
<input type="hidden" name="chat_id" value="1">
<div class="mb-3">
<textarea id="id_content" class="form-control" rows="3" placeholder="Напишите сообщение..."></textarea>
</div>
<div id="replyIndicatorContainer"></div>
<button type="submit" class="btn btn-success">
<i class="bi bi-send-fill"></i> Отправить
</button>
<button type="button" id="cancelBtn" class="btn btn-secondary">
Отмена
</button>
</form>
<div id="formState" class="mt-3 p-3 bg-light border rounded">
<strong>Состояние формы:</strong>
<pre id="formStateJson">{ "mode": "send" }</pre>
</div>
</div>
<!-- Тест 3: Превью ответа в сообщении -->
<div class="test-section">
<h3>3️⃣ Тест превью ответа в сообщении</h3>
<p>Проверка рендеринга превью при ответе на сообщение</p>
<div class="message-preview">
<div class="message-reply-preview border-start border-3 border-primary ps-2 mb-2 ms-2 rounded"
data-reply-to-id="100"
style="background-color: rgba(13, 110, 253, 0.05); padding: 6px 8px; cursor: pointer;">
<div class="small fw-semibold text-primary">
<i class="bi bi-reply-fill me-1"></i>
Мария Иванова
</div>
<div class="small text-muted text-truncate" style="max-width: 400px;">
Давайте встретимся завтра
</div>
</div>
<div class="bubble">
Отлично, в какое время удобно?
</div>
</div>
<button id="testPreviewClick" class="btn btn-primary">
<i class="bi bi-cursor-fill"></i> Клик по превью (переход к сообщению)
</button>
</div>
<!-- Логи -->
<div class="test-section">
<h3>📝 Журнал событий</h3>
<button id="clearLogs" class="btn btn-sm btn-outline-secondary mb-2">Очистить</button>
<div id="logOutput" class="log-output"></div>
</div>
</div>
<!-- Модули -->
<script type="module">
import { initChatFormManager } from './static/js/components/chatFormManager.js';
import MessageContextMenu from './static/js/components/messageContextMenu.js';
import reactionsConfig from './static/js/config/reactionsConfig.js';
// Логирование
const logOutput = document.getElementById('logOutput');
function addLog(message, type = 'info') {
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
logOutput.appendChild(entry);
logOutput.scrollTop = logOutput.scrollHeight;
}
document.getElementById('clearLogs').addEventListener('click', () => {
logOutput.innerHTML = '';
addLog('Логи очищены', 'info');
});
// Проверка загрузки реакций
async function checkReactions() {
addLog('Проверка загрузки реакций...', 'info');
try {
await reactionsConfig.load();
const emojis = reactionsConfig.getEmojis();
const statusEl = document.getElementById('reactionsStatus');
if (emojis && emojis.length > 0) {
statusEl.className = 'alert alert-success';
statusEl.innerHTML = `
<h5>✅ Реакции загружены успешно</h5>
<p>Всего доступно: <strong>${emojis.length}</strong> реакций</p>
<p>Эмодзи: ${emojis.join(' ')}</p>
`;
addLog(`✓ Загружено ${emojis.length} реакций: ${emojis.join(' ')}`, 'success');
} else {
statusEl.className = 'alert alert-warning';
statusEl.innerHTML = `
<h5>⚠️ Используются дефолтные реакции</h5>
<p>API эндпоинт не доступен, используются реакции по умолчанию</p>
`;
addLog('⚠ Используются дефолтные реакции (API недоступен)', 'warn');
}
} catch (error) {
const statusEl = document.getElementById('reactionsStatus');
statusEl.className = 'alert alert-danger';
statusEl.innerHTML = `
<h5>❌ Ошибка загрузки реакций</h5>
<p>${error.message}</p>
`;
addLog(`✗ Ошибка: ${error.message}`, 'error');
}
}
// Инициализация ChatFormManager
addLog('Инициализация ChatFormManager...', 'info');
const chatFormManager = initChatFormManager({
formId: 'chatForm',
textareaId: 'id_content',
chatId: 1,
uploadUrl: '/api/v1/communications/messages/upload/',
editUrlTemplate: '/api/v1/communications/messages/{id}/'
});
if (chatFormManager) {
window.chatFormManager = chatFormManager;
addLog('✓ ChatFormManager инициализирован', 'success');
} else {
addLog('✗ Ошибка инициализации ChatFormManager', 'error');
}
// Обновление отображения состояния формы
function updateFormState() {
if (!chatFormManager) return;
const state = chatFormManager.getFormData();
document.getElementById('formStateJson').textContent = JSON.stringify(state, null, 2);
}
// Тест 1: Ответ через контекстное меню
document.getElementById('testReplyBtn').addEventListener('click', () => {
addLog('Тест: Вызов handleReply из контекстного меню', 'info');
const messageEl = document.querySelector('[data-message-id="123"]');
if (!chatFormManager) {
addLog('✗ ChatFormManager не доступен', 'error');
return;
}
try {
// Симуляция логики MessageContextMenu.handleReply
const bubble = messageEl.querySelector('.bubble');
const messageText = bubble.textContent.trim();
const authorName = 'Иван Петров';
const messageId = messageEl.dataset.messageId;
addLog(`handleReply вызван с: ID=${messageId}, Автор="${authorName}"`, 'info');
chatFormManager.setModeToReply(messageId, authorName, messageText);
addLog('✓ Режим ответа активирован', 'success');
updateFormState();
// Показываем результат
const resultDiv = document.getElementById('testReplyResult');
resultDiv.innerHTML = `
<div class="alert alert-success">
<h5>✅ Тест пройден!</h5>
<ul>
<li>Режим формы: <strong>reply</strong></li>
<li>ID сообщения: <strong>${messageId}</strong></li>
<li>Placeholder обновлен: "${document.getElementById('id_content').placeholder}"</li>
<li>Hidden input reply_to добавлен: ${document.querySelector('input[name="reply_to"]') ? 'Да' : 'Нет'}</li>
</ul>
</div>
`;
} catch (error) {
addLog(`✗ Ошибка в handleReply: ${error.message}`, 'error');
const resultDiv = document.getElementById('testReplyResult');
resultDiv.innerHTML = `
<div class="alert alert-danger">
<h5>❌ Тест провален</h5>
<p>${error.message}</p>
</div>
`;
}
});
// Отмена
document.getElementById('cancelBtn').addEventListener('click', (e) => {
e.preventDefault();
addLog('Отмена действия (возврат в режим send)', 'info');
if (chatFormManager) {
chatFormManager.cancel();
addLog('✓ Режим send восстановлен', 'success');
updateFormState();
}
});
// Тест 3: Клик по превью
document.getElementById('testPreviewClick').addEventListener('click', () => {
const preview = document.querySelector('.message-reply-preview');
const replyToId = preview.dataset.replyToId;
addLog(`Клик по превью ответа, ID оригинального сообщения: ${replyToId}`, 'info');
addLog('В реальной ситуации произойдет переход к сообщению #' + replyToId, 'info');
// В реальности здесь будет вызов messageLoader.loadAroundMessage()
alert(`Переход к сообщению #${replyToId}\n\nВ реальной реализации вызывается:\nmessageLoader.loadAroundMessage(${replyToId})`);
});
// Мониторинг изменений в форме
document.getElementById('chatForm').addEventListener('submit', (e) => {
e.preventDefault();
addLog('=== ОТПРАВКА ФОРМЫ ===', 'info');
const formData = new FormData(e.target);
for (let [key, value] of formData.entries()) {
addLog(` ${key}: ${value}`, 'info');
}
const state = chatFormManager.getFormData();
addLog(`Режим: ${state.mode}`, 'info');
if (state.replyToMessageId) {
addLog(`Reply To: ${state.replyToMessageId}`, 'success');
}
addLog('=== КОНЕЦ ===', 'info');
});
// Запуск проверки реакций
checkReactions();
addLog('Тестовая страница загружена', 'success');
updateFormState();
</script>
</body>
</html>