@@ -0,0 +1,380 @@
|
||||
"""Unit tests for MIREA TZ checker and numbering helpers."""
|
||||
|
||||
from md2gost.checker import check_markdown
|
||||
from md2gost.numberer import Numberer
|
||||
from md2gost.profiles import preprocess_markdown, find_formula_refs
|
||||
from md2gost.bibliography import find_citations, extract_year
|
||||
|
||||
|
||||
SAMPLE_OK = """
|
||||
# *СОДЕРЖАНИЕ
|
||||
[TOC]
|
||||
|
||||
# *ВВЕДЕНИЕ
|
||||
|
||||
Текст введения без ссылок на источники.
|
||||
|
||||
# Анализ предметной области
|
||||
|
||||
Согласно исследованиям [1] применяется подход, показанный на @Рисунок:arch.
|
||||
|
||||

|
||||
|
||||
%tbl1 Сравнение подходов
|
||||
|
||||
| Подход | Оценка |
|
||||
|--------|--------|
|
||||
| A | 1 |
|
||||
| B | 2 |
|
||||
|
||||
Далее см. Таблицу — @Таблица:tbl1.
|
||||
|
||||
# *ЗАКЛЮЧЕНИЕ
|
||||
|
||||
Выводы по работе.
|
||||
|
||||
# *СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
|
||||
|
||||
[1]: Иванов И. И. Информационные системы. — М.: Наука, 2023. — 120 с.
|
||||
[2]: Петров П. П. Базы данных. — СПб.: Питер, 2022. — 200 с.
|
||||
[3]: ГОСТ 7.32-2017. Отчет о НИР. — М., 2022.
|
||||
[4]: Сидоров С. С. Python. — М.: ДМК, 2024. — 300 с.
|
||||
[5]: Козлов К. К. Анализ данных. — М.: Бином, 2022. — 150 с.
|
||||
|
||||
# *ПРИЛОЖЕНИЯ
|
||||
|
||||
## Приложение А Листинг модуля
|
||||
|
||||
Код.
|
||||
"""
|
||||
|
||||
|
||||
def test_numberer_section_scoped():
|
||||
n = Numberer()
|
||||
n.enter_section(1)
|
||||
assert n.next_number("Рисунок", "a") == "1.1"
|
||||
assert n.next_number("Рисунок", "b") == "1.2"
|
||||
n.enter_section(2)
|
||||
assert n.next_number("Рисунок") == "2.1"
|
||||
n.enter_appendix("Б")
|
||||
assert n.next_number("Рисунок", "x") == "Б.1"
|
||||
|
||||
|
||||
def test_numberer_continuous_pis():
|
||||
n = Numberer(mode="continuous")
|
||||
n.enter_section(1)
|
||||
assert n.next_number("Рисунок", "a") == "1"
|
||||
assert n.next_number("Таблица", "t") == "1"
|
||||
n.enter_section(2)
|
||||
assert n.next_number("Рисунок", "b") == "2"
|
||||
n.enter_appendix("А")
|
||||
assert n.next_number("Рисунок", "c") == "3"
|
||||
|
||||
|
||||
SAMPLE_PIS = """
|
||||
# *СОДЕРЖАНИЕ
|
||||
[TOC]
|
||||
|
||||
# Практическая работа №1. Формирование требований к системе
|
||||
|
||||
Цель работы — сформировать требования.
|
||||
|
||||
## Описание предметной области
|
||||
|
||||
Текст с рисунком @Рисунок:arch.
|
||||
|
||||

|
||||
|
||||
# Практическая работа №2. Моделирование процессов
|
||||
|
||||
## Построение модели
|
||||
|
||||
%tbl1 Сравнение подходов
|
||||
|
||||
| A | B |
|
||||
|---|---|
|
||||
| 1 | 2 |
|
||||
|
||||
См. @Таблица:tbl1.
|
||||
"""
|
||||
|
||||
|
||||
def test_checker_ok_pis_custom():
|
||||
issues = check_markdown(SAMPLE_PIS, "PIS_custom")
|
||||
errors = [i for i in issues if i.severity == "error"]
|
||||
assert not errors, errors
|
||||
|
||||
|
||||
def test_checker_pis_requires_practical():
|
||||
text = SAMPLE_PIS.replace("Практическая работа №1.", "Раздел 1.")
|
||||
text = text.replace("Практическая работа №2.", "Раздел 2.")
|
||||
issues = check_markdown(text, "PIS_custom")
|
||||
assert any(i.id == "structure.practical" for i in issues)
|
||||
|
||||
|
||||
def test_pis_styles_h1_centered():
|
||||
import docx
|
||||
from md2gost.styles import apply_pis_custom_styles
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
|
||||
doc = docx.Document(r"md2gost/Template.docx")
|
||||
apply_pis_custom_styles(doc)
|
||||
h1 = doc.styles["Heading 1"]
|
||||
assert h1.paragraph_format.alignment == WD_ALIGN_PARAGRAPH.CENTER
|
||||
assert h1.font.all_caps is True
|
||||
h2 = doc.styles["Heading 2"]
|
||||
assert h2.paragraph_format.first_line_indent is not None
|
||||
assert abs(h2.paragraph_format.first_line_indent.cm - 1.25) < 0.01
|
||||
assert h2.paragraph_format.left_indent is None or h2.paragraph_format.left_indent.cm == 0
|
||||
|
||||
|
||||
def test_soderzhanie_excluded_from_toc_outline():
|
||||
import docx
|
||||
from md2gost.renderable.heading import Heading
|
||||
from md2gost.styles import apply_mirea_styles
|
||||
|
||||
doc = docx.Document(r"md2gost/Template.docx")
|
||||
doc._body.clear_content()
|
||||
apply_mirea_styles(doc)
|
||||
h = Heading(doc._body, 1, False)
|
||||
h.add_run("СОДЕРЖАНИЕ")
|
||||
h.exclude_from_toc()
|
||||
levels = h._docx_paragraph._p.pPr.xpath("./w:outlineLvl")
|
||||
assert levels and levels[0].get(
|
||||
"{http://schemas.openxmlformats.org/wordprocessingml/2006/main}val"
|
||||
) == "9"
|
||||
|
||||
|
||||
def test_heading_font_is_tnr_black():
|
||||
import docx
|
||||
from docx.oxml.ns import qn
|
||||
from md2gost.styles import apply_mirea_styles
|
||||
|
||||
doc = docx.Document(r"md2gost/Template.docx")
|
||||
apply_mirea_styles(doc)
|
||||
rPr = doc.styles["Heading 1"].element.find(qn("w:rPr"))
|
||||
rFonts = rPr.find(qn("w:rFonts"))
|
||||
assert rFonts.get(qn("w:ascii")) == "Times New Roman"
|
||||
assert rFonts.get(qn("w:hAnsi")) == "Times New Roman"
|
||||
assert rFonts.get(qn("w:eastAsia")) == "Times New Roman"
|
||||
color = rPr.find(qn("w:color"))
|
||||
assert color is not None
|
||||
assert color.get(qn("w:val")) == "000000"
|
||||
assert color.get(qn("w:themeColor")) is None
|
||||
|
||||
|
||||
def test_checker_ok_coursework():
|
||||
issues = check_markdown(SAMPLE_OK, "coursework")
|
||||
errors = [i for i in issues if i.severity == "error"]
|
||||
assert not errors, errors
|
||||
|
||||
|
||||
def test_checker_ris_forbidden():
|
||||
text = SAMPLE_OK.replace("@Рисунок:arch", "рис. 1.1")
|
||||
issues = check_markdown(text, "coursework")
|
||||
assert any(i.id == "ref.ris" for i in issues)
|
||||
|
||||
|
||||
def test_checker_cite_in_intro():
|
||||
text = SAMPLE_OK.replace("Текст введения без ссылок на источники.", "См. [1].")
|
||||
issues = check_markdown(text, "coursework")
|
||||
assert any(i.id == "cite.intro" for i in issues)
|
||||
|
||||
|
||||
def test_checker_vkr_graphic():
|
||||
issues = check_markdown(SAMPLE_OK, "vkr")
|
||||
assert any(i.id == "structure.graphic" for i in issues)
|
||||
|
||||
|
||||
def test_preprocess_quotes():
|
||||
assert "«тест»" in preprocess_markdown('Он сказал "тест" вслух.')
|
||||
|
||||
|
||||
def test_formula_refs():
|
||||
assert find_formula_refs("см. @Формула:eq1 и далее") == {"eq1"}
|
||||
|
||||
|
||||
def test_citations_order():
|
||||
assert find_citations("a [2] b [1] c [2, 3]") == ["2", "1", "3"]
|
||||
|
||||
|
||||
def test_extract_year():
|
||||
assert extract_year("М.: Наука, 2023. — 120 с.") == 2023
|
||||
|
||||
|
||||
def test_biblio_entries_from_concatenated_paragraph():
|
||||
from md2gost.biblio_processor import entries_from_text, extract_bibliography_from_markdown
|
||||
entries = entries_from_text(
|
||||
"[1]: Первый источник. — М., 2024.[2]: Второй. URL: https://a.com/x-y/[3]: Третий."
|
||||
)
|
||||
assert [e.key for e in entries] == ["1", "2", "3"]
|
||||
assert "https://a.com/x-y/" in entries[1].text
|
||||
|
||||
md = """# *ВВЕДЕНИЕ
|
||||
|
||||
Текст.
|
||||
|
||||
# *СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
|
||||
|
||||
[1]: Ааа. — М., 2024.
|
||||
[2]: Ббб. — СПб., 2023.
|
||||
|
||||
# *ПРИЛОЖЕНИЯ
|
||||
"""
|
||||
flat = extract_bibliography_from_markdown(md)
|
||||
assert len(flat) == 2
|
||||
assert flat[0].key == "1"
|
||||
|
||||
|
||||
def test_emdash_to_hyphen_preprocess():
|
||||
from md2gost.profiles import preprocess_markdown, set_emdash_to_hyphen, dash_separator
|
||||
out = preprocess_markdown("Цель — тест. Слово - слово.", emdash_to_hyphen=True)
|
||||
assert "—" not in out
|
||||
assert "Цель - тест" in out
|
||||
assert dash_separator() == " - "
|
||||
out2 = preprocess_markdown("Цель — тест.", emdash_to_hyphen=False)
|
||||
assert "—" in out2
|
||||
assert dash_separator() == " — "
|
||||
set_emdash_to_hyphen(False)
|
||||
|
||||
|
||||
def test_native_toc_inserts_word_field():
|
||||
import docx
|
||||
from md2gost.renderable.toc import ToC
|
||||
doc = docx.Document(r"md2gost/Template.docx")
|
||||
doc._body.clear_content()
|
||||
toc = ToC(doc._body, toc_mode="native")
|
||||
toc.fill()
|
||||
xml = toc._paragraph._docx_paragraph._p.xml
|
||||
assert "TOC" in xml
|
||||
assert "fldChar" in xml
|
||||
|
||||
|
||||
def test_reference_ignores_trailing_punctuation():
|
||||
from md2gost.extended_markdown import markdown
|
||||
|
||||
def refs(text):
|
||||
doc = markdown.parse(text)
|
||||
out = []
|
||||
|
||||
def walk(node):
|
||||
if node.__class__.__name__ == "Reference":
|
||||
out.append((node.type, node.name))
|
||||
kids = getattr(node, "children", None)
|
||||
if isinstance(kids, list):
|
||||
for c in kids:
|
||||
walk(c)
|
||||
|
||||
walk(doc)
|
||||
return out
|
||||
|
||||
assert ("Рисунок", "submitted") in refs("@Рисунок:submitted, @Рисунок:lmsafter")
|
||||
assert ("Рисунок", "deploy") in refs("см. (@Рисунок:deploy). Далее")
|
||||
|
||||
def test_heading_manual_kills_word_numbering():
|
||||
import docx
|
||||
from md2gost.renderable.heading import Heading
|
||||
doc = docx.Document(r'md2gost/Template.docx')
|
||||
doc._body.clear_content()
|
||||
h = Heading(doc._body, 1, True, numbering_mode='manual')
|
||||
h.add_run('1 Разработка')
|
||||
h.apply_numbering_mode('manual')
|
||||
assert h.text.startswith('1 ')
|
||||
num_pr = h._docx_paragraph._p.pPr.xpath('.//w:numId')
|
||||
assert num_pr and num_pr[0].get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}val') == '0'
|
||||
|
||||
|
||||
def test_heading_auto_strips_leading_number():
|
||||
import docx
|
||||
from md2gost.renderable.heading import Heading
|
||||
doc = docx.Document(r'md2gost/Template.docx')
|
||||
doc._body.clear_content()
|
||||
h = Heading(doc._body, 2, True, numbering_mode='auto')
|
||||
h.add_run('1.1 Описание')
|
||||
h.apply_numbering_mode('auto')
|
||||
assert h.text == 'Описание'
|
||||
|
||||
|
||||
def test_toc_tab_stops_match_text_area():
|
||||
import docx
|
||||
from md2gost.styles import apply_mirea_styles
|
||||
|
||||
doc = docx.Document(r'md2gost/Template.docx')
|
||||
apply_mirea_styles(doc)
|
||||
section = doc.sections[0]
|
||||
usable = section.page_width.mm - section.left_margin.mm - section.right_margin.mm
|
||||
for name in ('toc 1', 'toc 2'):
|
||||
tabs = list(doc.styles[name].paragraph_format.tab_stops)
|
||||
right_tabs = [t for t in tabs if 'RIGHT' in str(t.alignment)]
|
||||
assert len(right_tabs) == 1, (name, tabs)
|
||||
assert abs(right_tabs[0].position.mm - usable) < 0.05, (name, right_tabs[0].position.mm, usable)
|
||||
edge = section.left_margin.mm + right_tabs[0].position.mm
|
||||
assert abs(edge - (section.page_width.mm - section.right_margin.mm)) < 0.05
|
||||
|
||||
|
||||
SAMPLE_APID = """
|
||||
# *СОДЕРЖАНИЕ
|
||||
[TOC]
|
||||
|
||||
# *ВВЕДЕНИЕ
|
||||
|
||||
Текст введения без ссылок.
|
||||
|
||||
# 1 Теоретические аспекты разработки архитектуры приложений и данных: описание микросервисной архитектуры
|
||||
|
||||
Согласно [1] архитектура задаёт структуру системы. Далее [2].
|
||||
|
||||
# 2 Прикладные аспекты разработки архитектуры приложений и данных web-сервиса «SMEZHNO»
|
||||
|
||||
## 2.1 Описание проекта команды и разрабатываемого программного приложения «SMEZHNO»
|
||||
|
||||
Текст проекта [3].
|
||||
|
||||
## 2.2 Описание роли «Неавторизованный пользователь» web-сервиса «SMEZHNO»
|
||||
|
||||
Роль.
|
||||
|
||||
## 2.3 Описание архитектуры программного приложения и данных web-сервиса «SMEZHNO» для роли «Неавторизованный пользователь»
|
||||
|
||||
Архитектура [4].
|
||||
|
||||
## 2.4 Варианты развития архитектуры программного приложения «SMEZHNO»
|
||||
|
||||
Альтернативы [5].
|
||||
|
||||
# *ЗАКЛЮЧЕНИЕ
|
||||
|
||||
Выводы.
|
||||
|
||||
# *СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
|
||||
|
||||
[1]: ISO/IEC/IEEE 42010:2022. Architecture description. — Geneva: ISO, 2022.
|
||||
[2]: The Open Group. ArchiMate 3.2 Specification. — 2023.
|
||||
[3]: Аншина М. Л. Материалы лекционного курса. — М.: РТУ МИРЭА, 2024.
|
||||
[4]: Ньюмен С. Создание микросервисов. — СПб.: Питер, 2023. — 624 с.
|
||||
[5]: The Open Group. The TOGAF Standard, 10th Edition. — 2022.
|
||||
[6]: PostgreSQL 16 Documentation. URL: https://www.postgresql.org/docs/16/ (дата обращения: 01.05.2025).
|
||||
[7]: Kubernetes Documentation. URL: https://kubernetes.io/docs/home/ (дата обращения: 01.05.2025).
|
||||
"""
|
||||
|
||||
|
||||
def test_checker_apid_ok():
|
||||
issues = check_markdown(SAMPLE_APID, "APID_coursework")
|
||||
errors = [i for i in issues if i.severity == "error"]
|
||||
assert not errors, errors
|
||||
|
||||
|
||||
def test_checker_apid_requires_sections():
|
||||
text = SAMPLE_APID.replace("## 2.4 Варианты развития", "## 2.4 Прочее")
|
||||
issues = check_markdown(text, "APID_coursework")
|
||||
assert any(i.id == "structure.apid" for i in issues)
|
||||
|
||||
|
||||
def test_apid_biblio_min_seven():
|
||||
text = SAMPLE_APID
|
||||
# drop last three sources -> 4 left
|
||||
text = text.split("[5]:")[0] + "\n"
|
||||
issues = check_markdown(text, "APID_coursework")
|
||||
assert any(i.id == "biblio.count" for i in issues)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user