Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ jobs:
run: pyright

- name: Run tests and keep summary log
env:
# 단계적 도입: 기준을 50 -> 60 -> 70으로 상향합니다.
COVERAGE_FAIL_UNDER: 50
run: |
pytest -v -ra 2>&1 | tee pytest-summary.log
pytest -v -ra --cov=hwpx --cov-report=term-missing --cov-fail-under=${COVERAGE_FAIL_UNDER} 2>&1 | tee pytest-summary.log

- name: Upload pytest summary log
if: always()
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dev = [
]
test = [
"pytest>=7.4",
"pytest-cov>=5.0",
]
typecheck = [
"mypy>=1.10",
Expand Down
15 changes: 11 additions & 4 deletions src/hwpx/oxml/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -2183,7 +2183,9 @@ def _create_run_for_object(
default_char = self.char_pr_id_ref or "0"
if default_char is not None:
attrs["charPrIDRef"] = str(default_char)
return ET.SubElement(self.element, f"{_HP}run", attrs)
run = self.element.makeelement(f"{_HP}run", attrs)
self.element.append(run)
return run

def add_run(
self,
Expand Down Expand Up @@ -2264,6 +2266,9 @@ def add_table(
height=height,
border_fill_id_ref=resolved_border_fill,
)
if type(table_element) is not type(run):
table_element = LET.fromstring(ET.tostring(table_element, encoding="utf-8"))

run.append(table_element)
self.section.mark_dirty()
return HwpxOxmlTable(table_element, self)
Expand Down Expand Up @@ -2585,7 +2590,7 @@ def add_paragraph(
if style_id_ref is not None:
attrs["styleIDRef"] = str(style_id_ref)

paragraph = ET.Element(f"{_HP}p", attrs)
paragraph = self._element.makeelement(f"{_HP}p", attrs)

if include_run:
run_attrs = dict(run_attributes or {})
Expand All @@ -2594,9 +2599,11 @@ def add_paragraph(
elif "charPrIDRef" not in run_attrs:
run_attrs["charPrIDRef"] = "0"

run = ET.SubElement(paragraph, f"{_HP}run", run_attrs)
text_element = ET.SubElement(run, f"{_HP}t")
run = paragraph.makeelement(f"{_HP}run", run_attrs)
paragraph.append(run)
text_element = run.makeelement(f"{_HP}t", {})
text_element.text = text
run.append(text_element)

self._element.append(paragraph)
self._dirty = True
Expand Down
74 changes: 74 additions & 0 deletions tests/test_coverage_targets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from __future__ import annotations

import importlib
import logging
import sys
import warnings
import xml.etree.ElementTree as ET
from typing import cast

from hwpx.document import HwpxDocument, _append_element
from hwpx.oxml.document import HwpxOxmlDocument, HwpxOxmlSection
from hwpx.opc.package import HwpxPackage


class _NoopResource:
pass


class _BrokenResource:
def flush(self) -> None:
raise RuntimeError("flush error")

def close(self) -> None:
raise RuntimeError("close error")


def _minimal_document() -> HwpxDocument:
section = HwpxOxmlSection("section0.xml", ET.Element("section"))
root = HwpxOxmlDocument(ET.Element("manifest"), [section], [])
return HwpxDocument(cast(HwpxPackage, object()), root)


def test_append_element_uses_same_element_type() -> None:
parent = ET.Element("parent")
child = _append_element(parent, "child", {"id": "42"})

assert child.tag == "child"
assert child.attrib["id"] == "42"
assert parent[0] is child


def test_flush_and_close_resource_are_noop_without_method() -> None:
HwpxDocument._flush_resource(_NoopResource())
HwpxDocument._close_resource(_NoopResource())


def test_flush_and_close_resource_swallow_exceptions(caplog) -> None:
caplog.set_level(logging.DEBUG)
resource = _BrokenResource()

HwpxDocument._flush_resource(resource)
HwpxDocument._close_resource(resource)

assert "자원 flush 중 예외를 무시합니다" in caplog.text
assert "자원 close 중 예외를 무시합니다" in caplog.text


def test_package_module_warns_on_import_and_exports_symbols() -> None:
module_name = "hwpx.package"
sys.modules.pop(module_name, None)

with warnings.catch_warnings(record=True) as records:
warnings.simplefilter("always")
module = importlib.import_module(module_name)

assert records
assert any("더 이상 권장되지 않습니다" in str(record.message) for record in records)
assert module.__all__ == [
"HwpxPackage",
"HwpxPackageError",
"HwpxStructureError",
"RootFile",
"VersionInfo",
]