feat: setup refactoring infrastructure (Phase 0)
- Created static file structure: * static/css/components/ - reusable CSS components * static/css/pages/ - page-specific styles * static/js/utils/ - utility functions * static/js/components/ - interactive components - Created infrastructure files: * static/css/variables.css - CSS custom properties * static/css/components/index.css - CSS entry point * static/js/utils/index.js - JS utilities entry point * static/js/components/index.js - JS components entry point - Added comprehensive tests: * tests/test_static_files.py - 12 tests for structure, syntax, encoding - Updated .gitignore: * Added staticfiles/ to ignore list * Clarified that static/ sources are tracked - Added REFACTORING_PLAN.md: * New strategy: extract CSS/JS before HTML decomposition * Documented lessons learned from previous attempts * Defined phases 0-6 for systematic refactoring
This commit is contained in:
parent
f91f81c9a7
commit
303fd54d17
3
.gitignore
vendored
3
.gitignore
vendored
@ -147,6 +147,9 @@ dmypy.json
|
||||
|
||||
# папки со статикой и медиа
|
||||
media/
|
||||
# Собранные статические файлы Django (collectstatic)
|
||||
staticfiles/
|
||||
# Исходные static файлы НЕ игнорируются (backend/static/)
|
||||
|
||||
# Others
|
||||
node_modules
|
||||
|
||||
2
backend/static/css/components/.gitkeep
Normal file
2
backend/static/css/components/.gitkeep
Normal file
@ -0,0 +1,2 @@
|
||||
# Placeholder for CSS components
|
||||
# This directory will contain reusable CSS components like feed-cards, ios-search, modals, etc.
|
||||
21
backend/static/css/components/index.css
Normal file
21
backend/static/css/components/index.css
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* CSS Components Index
|
||||
*
|
||||
* Этот файл служит единой точкой входа для всех CSS компонентов.
|
||||
* Импортируйте компоненты по мере их создания.
|
||||
*
|
||||
* Использование в шаблонах:
|
||||
* {% block extra_css %}
|
||||
* <link rel="stylesheet" href="{% static 'css/components/index.css' %}">
|
||||
* {% endblock %}
|
||||
*/
|
||||
|
||||
/* Переменные (будут созданы далее) */
|
||||
/* @import 'variables.css'; */
|
||||
|
||||
/* Общие компоненты (будут созданы в Фазе 3) */
|
||||
/* @import 'feed-cards.css'; */
|
||||
/* @import 'ios-search.css'; */
|
||||
/* @import 'modals.css'; */
|
||||
/* @import 'badges.css'; */
|
||||
/* @import 'buttons.css'; */
|
||||
2
backend/static/css/pages/.gitkeep
Normal file
2
backend/static/css/pages/.gitkeep
Normal file
@ -0,0 +1,2 @@
|
||||
# Placeholder for page-specific CSS
|
||||
# This directory will contain CSS files specific to individual pages
|
||||
137
backend/static/css/variables.css
Normal file
137
backend/static/css/variables.css
Normal file
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* CSS Variables
|
||||
*
|
||||
* Глобальные CSS переменные для обеспечения единообразия дизайна.
|
||||
* Использование: var(--variable-name)
|
||||
*
|
||||
* Подключение в base.html:
|
||||
* <link rel="stylesheet" href="{% static 'css/variables.css' %}">
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* ========== Цветовая схема ========== */
|
||||
|
||||
/* Основные цвета интерфейса (наследуются от Bootstrap) */
|
||||
/* --bs-primary, --bs-secondary, --bs-success, --bs-danger, --bs-warning, --bs-info */
|
||||
|
||||
/* Дополнительные цвета приложения */
|
||||
--app-primary: #0d6efd;
|
||||
--app-success: #198754;
|
||||
--app-danger: #dc3545;
|
||||
--app-warning: #ffc107;
|
||||
--app-info: #0dcaf0;
|
||||
|
||||
/* Фоновые цвета */
|
||||
--bg-light: #f8f9fa;
|
||||
--bg-dark: #212529;
|
||||
--bg-card: #ffffff;
|
||||
|
||||
/* Цвета текста */
|
||||
--text-primary: #212529;
|
||||
--text-secondary: #6c757d;
|
||||
--text-muted: #adb5bd;
|
||||
|
||||
/* ========== Размеры и отступы ========== */
|
||||
|
||||
/* Высота компонентов */
|
||||
--navbar-height: 60px;
|
||||
--sidebar-width: 260px;
|
||||
--rightbar-width: 280px;
|
||||
|
||||
/* Радиусы скругления */
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 18px;
|
||||
--radius-xl: 24px;
|
||||
--radius-full: 50%;
|
||||
|
||||
/* Отступы */
|
||||
--spacing-xs: 0.25rem; /* 4px */
|
||||
--spacing-sm: 0.5rem; /* 8px */
|
||||
--spacing-md: 1rem; /* 16px */
|
||||
--spacing-lg: 1.5rem; /* 24px */
|
||||
--spacing-xl: 2rem; /* 32px */
|
||||
|
||||
/* ========== Тени ========== */
|
||||
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.16);
|
||||
|
||||
/* ========== Компоненты Feed ========== */
|
||||
|
||||
--feed-radius: 18px;
|
||||
--feed-shadow: var(--shadow-md);
|
||||
--feed-padding: 1rem;
|
||||
--feed-avatar-size: 44px;
|
||||
--feed-gap: 0.75rem;
|
||||
|
||||
/* ========== Переходы и анимации ========== */
|
||||
|
||||
--transition-fast: 0.15s ease;
|
||||
--transition-base: 0.2s ease;
|
||||
--transition-slow: 0.3s ease;
|
||||
|
||||
/* ========== Z-индексы ========== */
|
||||
|
||||
--z-dropdown: 1000;
|
||||
--z-sticky: 1020;
|
||||
--z-fixed: 1030;
|
||||
--z-modal-backdrop: 1040;
|
||||
--z-modal: 1050;
|
||||
--z-popover: 1060;
|
||||
--z-tooltip: 1070;
|
||||
|
||||
/* ========== Типографика ========== */
|
||||
|
||||
--font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--font-family-mono: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
|
||||
--font-size-xs: 0.75rem; /* 12px */
|
||||
--font-size-sm: 0.875rem; /* 14px */
|
||||
--font-size-base: 1rem; /* 16px */
|
||||
--font-size-lg: 1.125rem; /* 18px */
|
||||
--font-size-xl: 1.25rem; /* 20px */
|
||||
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* ========== Границы ========== */
|
||||
|
||||
--border-width: 1px;
|
||||
--border-color: #dee2e6;
|
||||
--border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
/* ========== Тёмная тема (если потребуется) ========== */
|
||||
/*
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg-card: #2d3748;
|
||||
--text-primary: #f7fafc;
|
||||
--text-secondary: #e2e8f0;
|
||||
--border-color: #4a5568;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/* ========== Вспомогательные классы с переменными ========== */
|
||||
|
||||
.shadow-sm { box-shadow: var(--shadow-sm); }
|
||||
.shadow-md { box-shadow: var(--shadow-md); }
|
||||
.shadow-lg { box-shadow: var(--shadow-lg); }
|
||||
.shadow-xl { box-shadow: var(--shadow-xl); }
|
||||
|
||||
.radius-sm { border-radius: var(--radius-sm); }
|
||||
.radius-md { border-radius: var(--radius-md); }
|
||||
.radius-lg { border-radius: var(--radius-lg); }
|
||||
.radius-xl { border-radius: var(--radius-xl); }
|
||||
.radius-full { border-radius: var(--radius-full); }
|
||||
|
||||
.transition-fast { transition: all var(--transition-fast); }
|
||||
.transition-base { transition: all var(--transition-base); }
|
||||
.transition-slow { transition: all var(--transition-slow); }
|
||||
2
backend/static/js/components/.gitkeep
Normal file
2
backend/static/js/components/.gitkeep
Normal file
@ -0,0 +1,2 @@
|
||||
# Placeholder for JavaScript components
|
||||
# This directory will contain interactive components like employeeForm, calendarWidget, etc.
|
||||
20
backend/static/js/components/index.js
Normal file
20
backend/static/js/components/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* JavaScript Components Index
|
||||
*
|
||||
* Этот файл служит единой точкой входа для всех JS компонентов.
|
||||
* Экспортирует все компоненты для удобного импорта.
|
||||
*
|
||||
* Использование в шаблонах:
|
||||
* <script type="module">
|
||||
* import { EmployeeForm, CalendarWidget } from '{% static "js/components/index.js" %}';
|
||||
* </script>
|
||||
*/
|
||||
|
||||
// Компоненты (будут созданы в Фазе 4)
|
||||
// export { EmployeeForm } from './employeeForm.js';
|
||||
// export { CalendarWidget } from './calendarWidget.js';
|
||||
// export { TeamWheel } from './teamWheel.js';
|
||||
// export { ListFilter } from './listFilter.js';
|
||||
|
||||
// Placeholder export для валидности модуля
|
||||
export const COMPONENTS_VERSION = '1.0.0';
|
||||
2
backend/static/js/utils/.gitkeep
Normal file
2
backend/static/js/utils/.gitkeep
Normal file
@ -0,0 +1,2 @@
|
||||
# Placeholder for JavaScript utilities
|
||||
# This directory will contain utility functions like stringUtils, timing, scroll, etc.
|
||||
26
backend/static/js/utils/index.js
Normal file
26
backend/static/js/utils/index.js
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* JavaScript Utilities Index
|
||||
*
|
||||
* Этот файл служит единой точкой входа для всех JS утилит.
|
||||
* Экспортирует все утилиты для удобного импорта.
|
||||
*
|
||||
* Использование в шаблонах:
|
||||
* <script type="module">
|
||||
* import { esc, norm, debounce } from '{% static "js/utils/index.js" %}';
|
||||
* </script>
|
||||
*
|
||||
* Или импорт конкретной утилиты:
|
||||
* import { esc } from '{% static "js/utils/stringUtils.js" %}';
|
||||
*/
|
||||
|
||||
// Утилиты работы со строками (будут созданы в Фазе 2)
|
||||
// export { esc, norm } from './stringUtils.js';
|
||||
|
||||
// Утилиты для работы со временем (будут созданы в Фазе 2)
|
||||
// export { debounce, throttle } from './timing.js';
|
||||
|
||||
// Утилиты прокрутки (будут созданы в Фазе 2)
|
||||
// export { smoothScrollTo } from './scroll.js';
|
||||
|
||||
// Placeholder export для валидности модуля
|
||||
export const UTILS_VERSION = '1.0.0';
|
||||
1691
backend/templates/REFACTORING_PLAN.md
Normal file
1691
backend/templates/REFACTORING_PLAN.md
Normal file
File diff suppressed because it is too large
Load Diff
253
backend/tests/test_static_files.py
Normal file
253
backend/tests/test_static_files.py
Normal file
@ -0,0 +1,253 @@
|
||||
"""
|
||||
Тесты для статических файлов (CSS/JS)
|
||||
|
||||
Проверяют:
|
||||
1. Валидность структуры CSS файлов
|
||||
2. Корректность ES6 модулей JavaScript
|
||||
3. Наличие обязательных файлов
|
||||
4. Отсутствие синтаксических ошибок
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class StaticFilesStructureTest(TestCase):
|
||||
"""Тесты структуры статических файлов"""
|
||||
|
||||
def setUp(self):
|
||||
self.static_root = Path(settings.BASE_DIR) / 'static'
|
||||
self.css_root = self.static_root / 'css'
|
||||
self.js_root = self.static_root / 'js'
|
||||
|
||||
def test_css_directories_exist(self):
|
||||
"""Проверка существования CSS директорий"""
|
||||
self.assertTrue(
|
||||
self.css_root.exists(),
|
||||
"CSS директория должна существовать"
|
||||
)
|
||||
self.assertTrue(
|
||||
(self.css_root / 'components').exists(),
|
||||
"CSS components директория должна существовать"
|
||||
)
|
||||
self.assertTrue(
|
||||
(self.css_root / 'pages').exists(),
|
||||
"CSS pages директория должна существовать"
|
||||
)
|
||||
|
||||
def test_js_directories_exist(self):
|
||||
"""Проверка существования JS директорий"""
|
||||
self.assertTrue(
|
||||
self.js_root.exists(),
|
||||
"JS директория должна существовать"
|
||||
)
|
||||
self.assertTrue(
|
||||
(self.js_root / 'utils').exists(),
|
||||
"JS utils директория должна существовать"
|
||||
)
|
||||
self.assertTrue(
|
||||
(self.js_root / 'components').exists(),
|
||||
"JS components директория должна существовать"
|
||||
)
|
||||
|
||||
def test_index_files_exist(self):
|
||||
"""Проверка существования index файлов"""
|
||||
self.assertTrue(
|
||||
(self.css_root / 'components' / 'index.css').exists(),
|
||||
"CSS components/index.css должен существовать"
|
||||
)
|
||||
self.assertTrue(
|
||||
(self.js_root / 'utils' / 'index.js').exists(),
|
||||
"JS utils/index.js должен существовать"
|
||||
)
|
||||
self.assertTrue(
|
||||
(self.js_root / 'components' / 'index.js').exists(),
|
||||
"JS components/index.js должен существовать"
|
||||
)
|
||||
|
||||
def test_variables_css_exists(self):
|
||||
"""Проверка существования variables.css"""
|
||||
variables_file = self.css_root / 'variables.css'
|
||||
self.assertTrue(
|
||||
variables_file.exists(),
|
||||
"variables.css должен существовать"
|
||||
)
|
||||
|
||||
# Проверка содержания основных переменных
|
||||
content = variables_file.read_text(encoding='utf-8')
|
||||
required_vars = [
|
||||
'--navbar-height',
|
||||
'--sidebar-width',
|
||||
'--feed-radius',
|
||||
'--shadow-md',
|
||||
'--transition-base'
|
||||
]
|
||||
for var in required_vars:
|
||||
self.assertIn(
|
||||
var,
|
||||
content,
|
||||
f"Переменная {var} должна быть определена в variables.css"
|
||||
)
|
||||
|
||||
|
||||
class CSSFilesValidityTest(TestCase):
|
||||
"""Тесты валидности CSS файлов"""
|
||||
|
||||
def setUp(self):
|
||||
self.css_root = Path(settings.BASE_DIR) / 'static' / 'css'
|
||||
|
||||
def test_css_files_syntax(self):
|
||||
"""Базовая проверка синтаксиса CSS файлов"""
|
||||
css_files = list(self.css_root.rglob('*.css'))
|
||||
|
||||
for css_file in css_files:
|
||||
with self.subTest(file=css_file.name):
|
||||
content = css_file.read_text(encoding='utf-8')
|
||||
|
||||
# Проверка парности фигурных скобок
|
||||
open_braces = content.count('{')
|
||||
close_braces = content.count('}')
|
||||
self.assertEqual(
|
||||
open_braces,
|
||||
close_braces,
|
||||
f"{css_file.name}: несбалансированные фигурные скобки"
|
||||
)
|
||||
|
||||
# Проверка что нет незакрытых комментариев
|
||||
# Простая проверка: /* должно быть равно */
|
||||
open_comments = content.count('/*')
|
||||
close_comments = content.count('*/')
|
||||
self.assertEqual(
|
||||
open_comments,
|
||||
close_comments,
|
||||
f"{css_file.name}: незакрытые комментарии"
|
||||
)
|
||||
|
||||
def test_css_files_encoding(self):
|
||||
"""Проверка кодировки CSS файлов (UTF-8)"""
|
||||
css_files = list(self.css_root.rglob('*.css'))
|
||||
|
||||
for css_file in css_files:
|
||||
with self.subTest(file=css_file.name):
|
||||
try:
|
||||
css_file.read_text(encoding='utf-8')
|
||||
except UnicodeDecodeError:
|
||||
msg = f"{css_file.name}: неверная кодировка (не UTF-8)"
|
||||
self.fail(msg)
|
||||
|
||||
|
||||
class JSFilesValidityTest(TestCase):
|
||||
"""Тесты валидности JavaScript файлов"""
|
||||
|
||||
def setUp(self):
|
||||
self.js_root = Path(settings.BASE_DIR) / 'static' / 'js'
|
||||
|
||||
def test_js_files_are_modules(self):
|
||||
"""Проверка что JS файлы используют ES6 модули"""
|
||||
js_files = list(self.js_root.rglob('*.js'))
|
||||
|
||||
for js_file in js_files:
|
||||
with self.subTest(file=js_file.name):
|
||||
content = js_file.read_text(encoding='utf-8')
|
||||
|
||||
# Проверка что файл содержит export (ES6 модуль)
|
||||
self.assertTrue(
|
||||
'export' in content,
|
||||
f"{js_file.name}: должен содержать export для ES6 модуля"
|
||||
)
|
||||
|
||||
def test_js_files_syntax_basic(self):
|
||||
"""Базовая проверка синтаксиса JS файлов"""
|
||||
js_files = list(self.js_root.rglob('*.js'))
|
||||
|
||||
for js_file in js_files:
|
||||
with self.subTest(file=js_file.name):
|
||||
content = js_file.read_text(encoding='utf-8')
|
||||
|
||||
# Проверка парности фигурных скобок
|
||||
open_braces = content.count('{')
|
||||
close_braces = content.count('}')
|
||||
self.assertEqual(
|
||||
open_braces,
|
||||
close_braces,
|
||||
f"{js_file.name}: несбалансированные фигурные скобки"
|
||||
)
|
||||
|
||||
# Проверка парности круглых скобок
|
||||
open_parens = content.count('(')
|
||||
close_parens = content.count(')')
|
||||
self.assertEqual(
|
||||
open_parens,
|
||||
close_parens,
|
||||
f"{js_file.name}: несбалансированные круглые скобки"
|
||||
)
|
||||
|
||||
# Проверка парности квадратных скобок
|
||||
open_brackets = content.count('[')
|
||||
close_brackets = content.count(']')
|
||||
self.assertEqual(
|
||||
open_brackets,
|
||||
close_brackets,
|
||||
f"{js_file.name}: несбалансированные квадратные скобки"
|
||||
)
|
||||
|
||||
def test_js_files_encoding(self):
|
||||
"""Проверка кодировки JS файлов (UTF-8)"""
|
||||
js_files = list(self.js_root.rglob('*.js'))
|
||||
|
||||
for js_file in js_files:
|
||||
with self.subTest(file=js_file.name):
|
||||
try:
|
||||
js_file.read_text(encoding='utf-8')
|
||||
except UnicodeDecodeError:
|
||||
self.fail(f"{js_file.name}: неверная кодировка (не UTF-8)")
|
||||
|
||||
|
||||
class IndexFilesTest(TestCase):
|
||||
"""Тесты для index файлов"""
|
||||
|
||||
def test_css_index_imports(self):
|
||||
"""Проверка что CSS index.css готов к импорту компонентов"""
|
||||
index_file = (
|
||||
Path(settings.BASE_DIR) / 'static' / 'css' /
|
||||
'components' / 'index.css'
|
||||
)
|
||||
content = index_file.read_text(encoding='utf-8')
|
||||
|
||||
# Проверка что есть комментарии о подключении
|
||||
self.assertIn(
|
||||
'@import',
|
||||
content,
|
||||
"index.css должен содержать примеры @import"
|
||||
)
|
||||
|
||||
def test_js_utils_index_exports(self):
|
||||
"""Проверка что JS utils/index.js готов к экспорту утилит"""
|
||||
index_file = (
|
||||
Path(settings.BASE_DIR) / 'static' / 'js' /
|
||||
'utils' / 'index.js'
|
||||
)
|
||||
content = index_file.read_text(encoding='utf-8')
|
||||
|
||||
# Проверка что есть export
|
||||
self.assertIn(
|
||||
'export',
|
||||
content,
|
||||
"utils/index.js должен содержать export"
|
||||
)
|
||||
|
||||
def test_js_components_index_exports(self):
|
||||
"""Проверка что JS components/index.js готов к экспорту"""
|
||||
index_file = (
|
||||
Path(settings.BASE_DIR) / 'static' / 'js' /
|
||||
'components' / 'index.js'
|
||||
)
|
||||
content = index_file.read_text(encoding='utf-8')
|
||||
|
||||
# Проверка что есть export
|
||||
self.assertIn(
|
||||
'export',
|
||||
content,
|
||||
"components/index.js должен содержать export"
|
||||
)
|
||||
Loading…
x
Reference in New Issue
Block a user