Files
md_to_gost/tests/test_mirea_tz.py
T
Igor20264 b38661f588
Python application / build (push) Has been cancelled
Update 0.4.0
- Add\Rework UI
- Add Split Table and Listing
- Add Support Customazeble schems
2026-09-04 22:28:39 +03:00

542 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
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.
"""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.
![схема](img.png "%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.
![схема](img.png "%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)
def test_special_heading_alignment_soderzhanie_vs_vvedenie():
import docx
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Cm
from md2gost.renderable.heading import Heading
from md2gost.renderer import Renderer
from md2gost.styles import apply_mirea_styles
doc = docx.Document(r"md2gost/Template.docx")
doc._body.clear_content()
apply_mirea_styles(doc)
renderer = Renderer(doc, skip_numbering=True)
h_toc = Heading(doc._body, 1, False)
h_toc.add_run("СОДЕРЖАНИЕ")
renderer._handle_heading(h_toc)
assert h_toc._docx_paragraph.alignment == WD_ALIGN_PARAGRAPH.CENTER
assert abs(h_toc._docx_paragraph.paragraph_format.left_indent.cm - 0) < 0.01
h_intro = Heading(doc._body, 1, False)
h_intro.add_run("ВВЕДЕНИЕ")
renderer._handle_heading(h_intro)
# Left like Heading 1 (style indent 1.25), not forced center
assert h_intro._docx_paragraph.alignment != WD_ALIGN_PARAGRAPH.CENTER
def test_appendix_item_becomes_h3_plus_title():
import docx
from docx.enum.text import WD_ALIGN_PARAGRAPH
from md2gost.renderable.heading import Heading
from md2gost.renderer import Renderer
from md2gost.styles import apply_mirea_styles
doc = docx.Document(r"md2gost/Template.docx")
doc._body.clear_content()
apply_mirea_styles(doc)
renderer = Renderer(doc, skip_numbering=True)
h = Heading(doc._body, 2, True)
h.add_run("Приложение А Листинг модуля")
renderer._handle_heading(h)
assert h.style.name == "Heading 3"
assert h.text == "Приложение А"
assert h._docx_paragraph.alignment == WD_ALIGN_PARAGRAPH.CENTER
assert len(renderer._after_current) == 1
title_p = renderer._after_current[0]
assert "Листинг модуля" in title_p._docx_paragraph.text
assert title_p._docx_paragraph.alignment == WD_ALIGN_PARAGRAPH.CENTER
def test_caption_table_keep_with_next_and_toc_styles():
import docx
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from md2gost.styles import apply_mirea_styles
doc = docx.Document(r"md2gost/Template.docx")
apply_mirea_styles(doc)
assert doc.styles["Caption Table"].paragraph_format.keep_with_next is True
assert doc.styles["Table Text"].paragraph_format.alignment == WD_ALIGN_PARAGRAPH.LEFT
bh = doc.styles["Bibliography Heading"]
assert abs(bh.paragraph_format.left_indent.cm - 1.25) < 0.01
toc1 = doc.styles["toc 1"]
assert toc1.font.all_caps is True
assert toc1.font.bold is False
assert toc1.paragraph_format.line_spacing_rule == WD_LINE_SPACING.ONE_POINT_FIVE
def test_emdash_default_false_in_pipeline():
from md2gost.pipeline import ConvertRequest
assert ConvertRequest().emdash_to_hyphen is False
def test_object_ref_unused_warning():
text = SAMPLE_OK.replace("@Рисунок:arch", "схема")
issues = check_markdown(text, "coursework")
assert any(i.id == "ref.unused" for i in issues)
def test_table_continuation_warning():
issues = check_markdown(SAMPLE_OK, "coursework", table_continuation="off")
assert any(i.id == "table.continuation" for i in issues)
issues2 = check_markdown(SAMPLE_OK, "coursework", table_continuation="caption")
assert not any(i.id == "table.continuation" for i in issues2)
issues3 = check_markdown(SAMPLE_OK, "coursework", table_continuation="word")
assert not any(i.id == "table.continuation" for i in issues3)
def test_appendix_list_warning():
text = SAMPLE_OK + "\n## Приложение Б Ещё\n\nТекст.\n"
# No list between ПРИЛОЖЕНИЯ and first appendix
issues = check_markdown(text, "coursework")
assert any(i.id == "appendix.toc" for i in issues)
def test_vkr_biblio_per_section():
text = """
# *СОДЕРЖАНИЕ
[TOC]
# *ВВЕДЕНИЕ
Текст.
# 1 Раздел
См. [1.1].
# *ЗАКЛЮЧЕНИЕ
Выводы.
# *СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
## Нормативные
[1.1]: ГОСТ 7.32-2017. — М., 2022.
## Научные
[2.1]: Иванов И. И. Книга. — М., 2023.
# *ПРИЛОЖЕНИЯ
## Приложение А Графический материал
Слайды.
"""
issues = check_markdown(text, "vkr")
assert any(i.id == "biblio.count" for i in issues)
def test_page_fill_evaluate_heuristic():
from md2gost.page_fill_check import PageMetric, evaluate_page_fill
pages = [
PageMetric(1, 0.4, "1 Анализ", "1 Анализ"),
PageMetric(2, 0.9, "1 Анализ", "2 Проект", is_last_doc_page=False),
PageMetric(3, 0.5, "2 Проект", None, is_last_doc_page=True),
]
issues = evaluate_page_fill(pages)
assert len(issues) == 1
assert issues[0].page_index == 1
assert "heuristic" in issues[0].message
# Last page of section — no issue
pages2 = [
PageMetric(1, 0.3, "1 Анализ", "2 Проект"),
PageMetric(2, 0.9, "2 Проект", None, is_last_doc_page=True),
]
assert evaluate_page_fill(pages2) == []
# Landscape skipped
pages3 = [
PageMetric(1, 0.2, "1 Анализ", "1 Анализ", is_landscape=True),
PageMetric(2, 0.9, "1 Анализ", None, is_last_doc_page=True),
]
assert evaluate_page_fill(pages3) == []
def test_landscape_valign_center():
from docx import Document
from docx.oxml.ns import qn
from md2gost import package_dir
import os
from md2gost.page_geometry import apply_section_geometry
doc = Document(os.path.join(package_dir(), "Template.docx"))
section = doc.sections[0]
apply_section_geometry(section, landscape=True)
valign = section._sectPr.find(qn("w:vAlign"))
assert valign is not None
assert valign.get(qn("w:val")) == "center"
apply_section_geometry(section, landscape=False)
assert section._sectPr.find(qn("w:vAlign")) is None