Skip to content

Commit 360da94

Browse files
committed
refac, add tests
1 parent f07072d commit 360da94

4 files changed

Lines changed: 147 additions & 16 deletions

File tree

gdocs/managers/batch_operation_manager.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ def _build_operation_request(
229229
if not request:
230230
raise ValueError("No paragraph style options provided")
231231

232+
_PT_PARAMS = {
233+
"indent_first_line",
234+
"indent_start",
235+
"indent_end",
236+
"space_above",
237+
"space_below",
238+
}
239+
_SUFFIX = {
240+
"heading_level": lambda v: f"H{v}",
241+
"line_spacing": lambda v: f"{v}x",
242+
}
243+
232244
style_changes = []
233245
for param, name in [
234246
("heading_level", "heading"),
@@ -241,22 +253,14 @@ def _build_operation_request(
241253
("space_below", "space below"),
242254
]:
243255
if op.get(param) is not None:
244-
value = (
245-
f"H{op[param]}"
246-
if param == "heading_level"
247-
else f"{op[param]}x"
248-
if param == "line_spacing"
249-
else f"{op[param]}pt"
250-
if param
251-
in (
252-
"indent_first_line",
253-
"indent_start",
254-
"indent_end",
255-
"space_above",
256-
"space_below",
257-
)
258-
else op[param]
259-
)
256+
raw = op[param]
257+
fmt = _SUFFIX.get(param)
258+
if fmt:
259+
value = fmt(raw)
260+
elif param in _PT_PARAMS:
261+
value = f"{raw}pt"
262+
else:
263+
value = raw
260264
style_changes.append(f"{name}: {value}")
261265

262266
description = f"paragraph style {op['start_index']}-{op['end_index']} ({', '.join(style_changes)})"

gdocs/managers/validation_manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ def validate_paragraph_style_params(
333333
False,
334334
f"{name} must be a number, got {type(param).__name__}",
335335
)
336+
# indent_first_line may be negative (hanging indent)
336337
if name != "indent_first_line" and param < 0:
337338
return False, f"{name} must be non-negative, got {param}"
338339

tests/gdocs/__init__.py

Whitespace-only changes.
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""
2+
Tests for update_paragraph_style batch operation support.
3+
4+
Covers the helpers, validation, and batch manager integration.
5+
"""
6+
7+
import pytest
8+
from unittest.mock import AsyncMock, Mock
9+
10+
from gdocs.docs_helpers import (
11+
build_paragraph_style,
12+
create_update_paragraph_style_request,
13+
)
14+
from gdocs.managers.validation_manager import ValidationManager
15+
16+
17+
class TestBuildParagraphStyle:
18+
def test_no_params_returns_empty(self):
19+
style, fields = build_paragraph_style()
20+
assert style == {}
21+
assert fields == []
22+
23+
def test_heading_zero_maps_to_normal_text(self):
24+
style, fields = build_paragraph_style(heading_level=0)
25+
assert style["namedStyleType"] == "NORMAL_TEXT"
26+
27+
def test_heading_maps_to_named_style(self):
28+
style, _ = build_paragraph_style(heading_level=3)
29+
assert style["namedStyleType"] == "HEADING_3"
30+
31+
def test_heading_out_of_range_raises(self):
32+
with pytest.raises(ValueError):
33+
build_paragraph_style(heading_level=7)
34+
35+
def test_line_spacing_scaled_to_percentage(self):
36+
style, _ = build_paragraph_style(line_spacing=1.5)
37+
assert style["lineSpacing"] == 150.0
38+
39+
def test_dimension_field_uses_pt_unit(self):
40+
style, _ = build_paragraph_style(indent_start=36.0)
41+
assert style["indentStart"] == {"magnitude": 36.0, "unit": "PT"}
42+
43+
def test_multiple_params_combined(self):
44+
style, fields = build_paragraph_style(
45+
heading_level=2, alignment="CENTER", space_below=12.0
46+
)
47+
assert len(fields) == 3
48+
assert style["alignment"] == "CENTER"
49+
50+
51+
class TestCreateUpdateParagraphStyleRequest:
52+
def test_returns_none_when_no_styles(self):
53+
assert create_update_paragraph_style_request(1, 10) is None
54+
55+
def test_produces_correct_api_structure(self):
56+
result = create_update_paragraph_style_request(1, 10, heading_level=1)
57+
inner = result["updateParagraphStyle"]
58+
assert inner["range"] == {"startIndex": 1, "endIndex": 10}
59+
assert inner["paragraphStyle"]["namedStyleType"] == "HEADING_1"
60+
assert inner["fields"] == "namedStyleType"
61+
62+
63+
class TestValidateParagraphStyleParams:
64+
@pytest.fixture()
65+
def vm(self):
66+
return ValidationManager()
67+
68+
def test_all_none_rejected(self, vm):
69+
is_valid, _ = vm.validate_paragraph_style_params()
70+
assert not is_valid
71+
72+
def test_wrong_types_rejected(self, vm):
73+
assert not vm.validate_paragraph_style_params(heading_level=1.5)[0]
74+
assert not vm.validate_paragraph_style_params(alignment=123)[0]
75+
assert not vm.validate_paragraph_style_params(line_spacing="double")[0]
76+
77+
def test_negative_indent_start_rejected(self, vm):
78+
is_valid, msg = vm.validate_paragraph_style_params(indent_start=-5.0)
79+
assert not is_valid
80+
assert "non-negative" in msg
81+
82+
def test_negative_indent_first_line_allowed(self, vm):
83+
"""Hanging indent requires negative first-line indent."""
84+
assert vm.validate_paragraph_style_params(indent_first_line=-18.0)[0]
85+
86+
def test_batch_validation_wired_up(self, vm):
87+
valid_ops = [
88+
{"type": "update_paragraph_style", "start_index": 1,
89+
"end_index": 20, "heading_level": 2},
90+
]
91+
assert vm.validate_batch_operations(valid_ops)[0]
92+
93+
no_style_ops = [
94+
{"type": "update_paragraph_style", "start_index": 1, "end_index": 20},
95+
]
96+
assert not vm.validate_batch_operations(no_style_ops)[0]
97+
98+
99+
class TestBatchManagerIntegration:
100+
@pytest.fixture()
101+
def manager(self):
102+
from gdocs.managers.batch_operation_manager import BatchOperationManager
103+
104+
return BatchOperationManager(Mock())
105+
106+
def test_build_request_and_description(self, manager):
107+
op = {
108+
"type": "update_paragraph_style",
109+
"start_index": 1, "end_index": 50,
110+
"heading_level": 2, "alignment": "CENTER", "line_spacing": 1.5,
111+
}
112+
request, desc = manager._build_operation_request(op, "update_paragraph_style")
113+
assert "updateParagraphStyle" in request
114+
assert "heading: H2" in desc
115+
assert "1.5x" in desc
116+
117+
@pytest.mark.asyncio
118+
async def test_end_to_end_execute(self, manager):
119+
manager._execute_batch_requests = AsyncMock(return_value={"replies": [{}]})
120+
success, message, meta = await manager.execute_batch_operations(
121+
"doc-123",
122+
[{"type": "update_paragraph_style", "start_index": 1,
123+
"end_index": 20, "heading_level": 1}],
124+
)
125+
assert success
126+
assert meta["operations_count"] == 1

0 commit comments

Comments
 (0)