Skip to content

Commit 35e31d0

Browse files
committed
add tests
1 parent e66d0f2 commit 35e31d0

1 file changed

Lines changed: 355 additions & 0 deletions

File tree

tests/dml/test_transparency.py

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
"""Unit-test suite for transparency functionality in `pptx.dml` module."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
from pptx.dml.color import ColorFormat, RGBColor
8+
from pptx.dml.fill import FillFormat, _SolidFill
9+
from pptx.enum.dml import MSO_COLOR_TYPE, MSO_FILL
10+
11+
from ..oxml.unitdata.dml import (
12+
a_alpha,
13+
a_solidFill,
14+
an_srgbClr,
15+
a_schemeClr,
16+
)
17+
from ..unitutil.cxml import element
18+
19+
20+
class DescribeColorFormatTransparency(object):
21+
"""Unit-test suite for ColorFormat transparency property."""
22+
23+
def it_knows_its_transparency_value(self, transparency_get_fixture):
24+
color_format, expected_transparency = transparency_get_fixture
25+
assert color_format.transparency == expected_transparency
26+
27+
def it_can_set_its_transparency_value(self, transparency_set_fixture):
28+
color_format, transparency, expected_xml = transparency_set_fixture
29+
color_format.transparency = transparency
30+
assert color_format._xFill.xml == expected_xml
31+
32+
def it_raises_on_transparency_get_for_NoneColor(self, _NoneColor_color_format):
33+
# This should not raise - NoneColor should return 0.0 transparency
34+
transparency = _NoneColor_color_format.transparency
35+
assert transparency == 0.0
36+
37+
def it_raises_on_transparency_set_for_NoneColor(self, _NoneColor_color_format):
38+
with pytest.raises(ValueError):
39+
_NoneColor_color_format.transparency = 0.5
40+
41+
def it_raises_on_assign_invalid_transparency_value(self, rgb_color_format):
42+
color_format = rgb_color_format
43+
with pytest.raises(ValueError):
44+
color_format.transparency = 1.1
45+
with pytest.raises(ValueError):
46+
color_format.transparency = -0.1
47+
48+
def it_can_set_transparency_to_zero_removes_alpha_element(self, rgb_color_format):
49+
"""Setting transparency to 0.0 should remove the alpha element."""
50+
color_format = rgb_color_format
51+
# First set some transparency
52+
color_format.transparency = 0.5
53+
assert color_format._color._xClr.alpha is not None
54+
55+
# Then set to 0.0 - should remove alpha element
56+
color_format.transparency = 0.0
57+
assert color_format._color._xClr.alpha is None
58+
assert color_format.transparency == 0.0
59+
60+
def it_can_set_transparency_to_one_creates_zero_alpha(self, rgb_color_format):
61+
"""Setting transparency to 1.0 should create alpha element with val=0."""
62+
color_format = rgb_color_format
63+
color_format.transparency = 1.0
64+
65+
alpha_element = color_format._color._xClr.alpha
66+
assert alpha_element is not None
67+
assert alpha_element.val == 0.0
68+
assert color_format.transparency == 1.0
69+
70+
# fixtures -------------------------------------------------------
71+
72+
@pytest.fixture(
73+
params=[
74+
# no alpha element - fully opaque
75+
(lambda: an_srgbClr().with_val("FF0000"), 0.0),
76+
# alpha = 100000 (100% opaque) - 0% transparent
77+
(lambda: an_srgbClr().with_val("FF0000").with_child(
78+
a_alpha().with_val(100000)), 0.0),
79+
# alpha = 50000 (50% opaque) - 50% transparent
80+
(lambda: an_srgbClr().with_val("FF0000").with_child(
81+
a_alpha().with_val(50000)), 0.5),
82+
# alpha = 0 (0% opaque) - 100% transparent
83+
(lambda: an_srgbClr().with_val("FF0000").with_child(
84+
a_alpha().with_val(0)), 1.0),
85+
]
86+
)
87+
def transparency_get_fixture(self, request):
88+
xClr_bldr_fn, expected_transparency = request.param
89+
xClr_bldr = xClr_bldr_fn()
90+
solidFill = a_solidFill().with_nsdecls().with_child(xClr_bldr).element
91+
color_format = ColorFormat.from_colorchoice_parent(solidFill)
92+
return color_format, expected_transparency
93+
94+
@pytest.fixture(
95+
params=[
96+
# Set to 0.0 (fully opaque) - should remove alpha element
97+
(lambda: an_srgbClr().with_val("FF0000"), 0.0,
98+
lambda: an_srgbClr().with_val("FF0000")),
99+
# Set to 0.5 (50% transparent) - should add alpha=50000
100+
(lambda: an_srgbClr().with_val("FF0000"), 0.5,
101+
lambda: an_srgbClr().with_val("FF0000").with_child(
102+
a_alpha().with_val(50000))),
103+
# Set to 1.0 (fully transparent) - should add alpha=0
104+
(lambda: an_srgbClr().with_val("FF0000"), 1.0,
105+
lambda: an_srgbClr().with_val("FF0000").with_child(
106+
a_alpha().with_val(0))),
107+
# Set from existing alpha to different value
108+
(lambda: an_srgbClr().with_val("FF0000").with_child(
109+
a_alpha().with_val(75000)), 0.3,
110+
lambda: an_srgbClr().with_val("FF0000").with_child(
111+
a_alpha().with_val(70000))),
112+
]
113+
)
114+
def transparency_set_fixture(self, request):
115+
xClr_bldr_fn, transparency, expected_xClr_bldr_fn = request.param
116+
117+
xClr_bldr = xClr_bldr_fn()
118+
solidFill = a_solidFill().with_nsdecls().with_child(xClr_bldr).element
119+
color_format = ColorFormat.from_colorchoice_parent(solidFill)
120+
121+
expected_xClr_bldr = expected_xClr_bldr_fn()
122+
expected_xml = a_solidFill().with_nsdecls().with_child(expected_xClr_bldr).xml()
123+
124+
return color_format, transparency, expected_xml
125+
126+
@pytest.fixture
127+
def rgb_color_format(self):
128+
solidFill = a_solidFill().with_nsdecls().with_child(
129+
an_srgbClr().with_val("FF0000")
130+
).element
131+
return ColorFormat.from_colorchoice_parent(solidFill)
132+
133+
@pytest.fixture
134+
def _NoneColor_color_format(self):
135+
solidFill = a_solidFill().with_nsdecls().element
136+
return ColorFormat.from_colorchoice_parent(solidFill)
137+
138+
# Additional validation tests
139+
def it_validates_transparency_value_range(self):
140+
"""Test that _validate_transparency_value works correctly."""
141+
from pptx.dml.color import ColorFormat
142+
143+
# Create a dummy ColorFormat instance for testing validation
144+
xClr_bldr = an_srgbClr().with_val("FF0000")
145+
solidFill = a_solidFill().with_nsdecls().with_child(xClr_bldr).element
146+
color_format = ColorFormat.from_colorchoice_parent(solidFill)
147+
148+
# Valid values should not raise
149+
color_format._validate_transparency_value(0.0)
150+
color_format._validate_transparency_value(0.5)
151+
color_format._validate_transparency_value(1.0)
152+
153+
# Invalid values should raise ValueError
154+
with pytest.raises(ValueError, match="transparency must be number in range 0.0 to 1.0"):
155+
color_format._validate_transparency_value(-0.1)
156+
with pytest.raises(ValueError, match="transparency must be number in range 0.0 to 1.0"):
157+
color_format._validate_transparency_value(1.1)
158+
with pytest.raises(ValueError, match="transparency must be number in range 0.0 to 1.0"):
159+
color_format._validate_transparency_value(2.0)
160+
161+
162+
class DescribeFillFormatTransparency(object):
163+
"""Unit-test suite for FillFormat transparency property."""
164+
165+
def it_delegates_transparency_to_solid_fill_object(self):
166+
"""Test that FillFormat delegates transparency to its _SolidFill object."""
167+
# Create a _SolidFill directly
168+
xClr_bldr = an_srgbClr().with_val("FF0000")
169+
solidFill_elm = a_solidFill().with_nsdecls().with_child(xClr_bldr).element
170+
solid_fill = _SolidFill(solidFill_elm)
171+
172+
# Create a FillFormat that wraps our _SolidFill
173+
from pptx.dml.fill import FillFormat
174+
175+
# Create a simple mock parent
176+
class MockParent:
177+
def __init__(self):
178+
self.eg_fillProperties = solidFill_elm
179+
180+
parent_mock = MockParent()
181+
fill_format = FillFormat(parent_mock, solid_fill)
182+
183+
# Set transparency through FillFormat
184+
fill_format.transparency = 0.3
185+
186+
# Verify it's delegated to the underlying solid fill
187+
assert abs(fill_format._fill.transparency - 0.3) < 0.001
188+
assert abs(fill_format.transparency - 0.3) < 0.001
189+
190+
def it_raises_on_transparency_access_for_non_solid_fills(self):
191+
"""Test that non-solid fills raise TypeError on transparency access."""
192+
from pptx.dml.fill import FillFormat, _NoFill
193+
194+
# Create a FillFormat with NoFill
195+
class MockParent:
196+
def __init__(self):
197+
self.eg_fillProperties = None
198+
199+
parent_mock = MockParent()
200+
no_fill = _NoFill(None)
201+
fill_format = FillFormat(parent_mock, no_fill)
202+
203+
with pytest.raises(TypeError, match="fill type .* has no transparency"):
204+
fill_format.transparency
205+
206+
def it_raises_on_transparency_set_for_non_solid_fills(self):
207+
"""Test that non-solid fills raise TypeError on transparency set."""
208+
from pptx.dml.fill import FillFormat, _NoFill
209+
210+
# Create a FillFormat with NoFill
211+
class MockParent:
212+
def __init__(self):
213+
self.eg_fillProperties = None
214+
215+
parent_mock = MockParent()
216+
no_fill = _NoFill(None)
217+
fill_format = FillFormat(parent_mock, no_fill)
218+
219+
with pytest.raises(TypeError, match="fill type .* has no transparency"):
220+
fill_format.transparency = 0.5
221+
222+
223+
class DescribeSolidFillTransparency(object):
224+
"""Unit-test suite for _SolidFill transparency property."""
225+
226+
def it_provides_access_to_transparency(self, solid_fill_transparency_fixture):
227+
solid_fill, expected_transparency = solid_fill_transparency_fixture
228+
assert abs(solid_fill.transparency - expected_transparency) < 0.001
229+
230+
def it_can_set_transparency(self, solid_fill_transparency_set_fixture):
231+
solid_fill, transparency = solid_fill_transparency_set_fixture
232+
solid_fill.transparency = transparency
233+
assert abs(solid_fill.transparency - transparency) < 0.001
234+
235+
def it_delegates_transparency_to_fore_color(self, solid_fill_obj):
236+
"""Test that _SolidFill delegates transparency to its fore_color."""
237+
solid_fill = solid_fill_obj
238+
239+
# Set transparency through _SolidFill
240+
solid_fill.transparency = 0.6
241+
242+
# Verify it's delegated to fore_color
243+
assert solid_fill.fore_color.transparency == 0.6
244+
assert solid_fill.transparency == 0.6
245+
246+
def it_has_correct_fill_type(self, solid_fill_obj):
247+
"""Test that _SolidFill reports correct fill type."""
248+
solid_fill = solid_fill_obj
249+
assert solid_fill.type == MSO_FILL.SOLID
250+
251+
# fixtures -------------------------------------------------------
252+
253+
@pytest.fixture(
254+
params=[
255+
# No alpha element - fully opaque
256+
(lambda: an_srgbClr().with_val("00FF00"), 0.0),
257+
# Alpha = 80000 (80% opaque) - 20% transparent
258+
(lambda: an_srgbClr().with_val("00FF00").with_child(
259+
a_alpha().with_val(80000)), 0.2),
260+
# Alpha = 30000 (30% opaque) - 70% transparent
261+
(lambda: an_srgbClr().with_val("00FF00").with_child(
262+
a_alpha().with_val(30000)), 0.7),
263+
]
264+
)
265+
def solid_fill_transparency_fixture(self, request):
266+
xClr_bldr_fn, expected_transparency = request.param
267+
xClr_bldr = xClr_bldr_fn()
268+
solidFill_elm = a_solidFill().with_nsdecls().with_child(xClr_bldr).element
269+
270+
solid_fill = _SolidFill(solidFill_elm)
271+
return solid_fill, expected_transparency
272+
273+
@pytest.fixture(
274+
params=[0.0, 0.1, 0.33, 0.67, 0.9, 1.0]
275+
)
276+
def solid_fill_transparency_set_fixture(self, request):
277+
transparency = request.param
278+
xClr_bldr = an_srgbClr().with_val("00FF00")
279+
solidFill_elm = a_solidFill().with_nsdecls().with_child(xClr_bldr).element
280+
281+
solid_fill = _SolidFill(solidFill_elm)
282+
return solid_fill, transparency
283+
284+
@pytest.fixture
285+
def solid_fill_obj(self):
286+
"""Create a _SolidFill object for testing."""
287+
xClr_bldr = an_srgbClr().with_val("0000FF")
288+
solidFill_elm = a_solidFill().with_nsdecls().with_child(xClr_bldr).element
289+
290+
return _SolidFill(solidFill_elm)
291+
292+
293+
class DescribeTransparencyIntegration(object):
294+
"""Integration tests for transparency across the entire stack."""
295+
296+
def it_works_end_to_end_with_solid_fill_and_color_format(self):
297+
"""Test transparency from ColorFormat through _SolidFill."""
298+
# Create a complete fill hierarchy using _SolidFill directly
299+
xClr_bldr = an_srgbClr().with_val("FF00FF")
300+
solidFill_elm = a_solidFill().with_nsdecls().with_child(xClr_bldr).element
301+
302+
solid_fill = _SolidFill(solidFill_elm)
303+
304+
# Test setting transparency at _SolidFill level
305+
solid_fill.transparency = 0.4
306+
307+
# Verify it propagates through all levels
308+
assert abs(solid_fill.transparency - 0.4) < 0.001
309+
assert abs(solid_fill.fore_color.transparency - 0.4) < 0.001
310+
311+
# Verify the underlying XML has the alpha element
312+
alpha_elm = solid_fill.fore_color._color._xClr.alpha
313+
assert alpha_elm is not None
314+
assert abs(alpha_elm.val - 0.6) < 0.001 # 1.0 - 0.4 = 0.6 (60% opaque)
315+
316+
def it_handles_transparency_removal_correctly(self):
317+
"""Test that setting transparency to 0.0 removes alpha element."""
318+
xClr_bldr = an_srgbClr().with_val("FFFF00").with_child(a_alpha().with_val(40000))
319+
solidFill_elm = a_solidFill().with_nsdecls().with_child(xClr_bldr).element
320+
321+
solid_fill = _SolidFill(solidFill_elm)
322+
323+
# Verify initial transparency (40000/100000 = 0.4 opaque = 0.6 transparent)
324+
assert abs(solid_fill.transparency - 0.6) < 0.001
325+
assert solid_fill.fore_color._color._xClr.alpha is not None
326+
327+
# Set transparency to 0.0
328+
solid_fill.transparency = 0.0
329+
330+
# Verify alpha element is removed
331+
assert solid_fill.transparency == 0.0
332+
assert solid_fill.fore_color._color._xClr.alpha is None
333+
334+
def it_handles_color_format_transparency_directly(self):
335+
"""Test ColorFormat transparency manipulation directly."""
336+
xClr_bldr = an_srgbClr().with_val("00FFFF")
337+
solidFill_elm = a_solidFill().with_nsdecls().with_child(xClr_bldr).element
338+
339+
color_format = ColorFormat.from_colorchoice_parent(solidFill_elm)
340+
341+
# Test setting various transparency values
342+
test_values = [0.0, 0.25, 0.5, 0.75, 1.0]
343+
for transparency in test_values:
344+
color_format.transparency = transparency
345+
assert abs(color_format.transparency - transparency) < 0.001
346+
347+
if transparency == 0.0:
348+
# No alpha element for fully opaque
349+
assert color_format._color._xClr.alpha is None
350+
else:
351+
# Alpha element should exist with correct value
352+
alpha_elm = color_format._color._xClr.alpha
353+
assert alpha_elm is not None
354+
expected_alpha = 1.0 - transparency
355+
assert abs(alpha_elm.val - expected_alpha) < 0.001

0 commit comments

Comments
 (0)