141 lines
5.0 KiB
Python
141 lines
5.0 KiB
Python
"""
|
|
Regression tests for form field label geometry (spec S15).
|
|
|
|
Text renders with a baseline anchor, so drawing the label at the field's origin
|
|
put its glyphs above that origin - outside the box the field claims through size
|
|
and in_object. Stacked fields therefore had each label overprinting the input box
|
|
of the field before it.
|
|
"""
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from PIL import Image, ImageDraw
|
|
|
|
from pyWebLayout.abstract.functional import Form, FormField, FormFieldType
|
|
from pyWebLayout.concrete.functional import FormFieldText
|
|
from pyWebLayout.concrete.page import Page
|
|
from pyWebLayout.layout.document_layouter import form_layouter
|
|
from pyWebLayout.style import Font
|
|
from pyWebLayout.style.page_style import PageStyle
|
|
|
|
|
|
ORIGIN = (10, 40)
|
|
FIELD_HEIGHT = 24
|
|
|
|
|
|
@pytest.fixture
|
|
def font():
|
|
return Font(font_size=12, colour=(0, 0, 0))
|
|
|
|
|
|
@pytest.fixture
|
|
def canvas():
|
|
image = Image.new("RGB", (300, 200), (255, 255, 255))
|
|
return image, ImageDraw.Draw(image)
|
|
|
|
|
|
def make_field(font, draw, label="Email Address"):
|
|
field = FormField(name="email", field_type=FormFieldType.TEXT, label=label)
|
|
renderable = FormFieldText(field, font, draw, field_height=FIELD_HEIGHT)
|
|
renderable.set_origin(np.array(list(ORIGIN)))
|
|
return renderable
|
|
|
|
|
|
def ink_rows(image, x_range, y_range):
|
|
pixels = image.convert("RGB").load()
|
|
return [y for y in y_range
|
|
if any(sum(pixels[x, y]) < 400 for x in x_range)]
|
|
|
|
|
|
class TestLabelStaysInsideTheFieldBox:
|
|
|
|
def test_label_ink_is_below_the_origin(self, font, canvas):
|
|
image, draw = canvas
|
|
renderable = make_field(font, draw)
|
|
renderable.render()
|
|
|
|
rows = ink_rows(image, range(ORIGIN[0], ORIGIN[0] + 140),
|
|
range(0, ORIGIN[1]))
|
|
assert not rows, \
|
|
f"label drew above its own origin, at rows {rows}"
|
|
|
|
def test_label_and_box_do_not_overlap(self, font, canvas):
|
|
image, draw = canvas
|
|
renderable = make_field(font, draw)
|
|
renderable.render()
|
|
|
|
ascent, descent = font.font.getmetrics()
|
|
label_bottom = ORIGIN[1] + ascent + descent
|
|
box_top = renderable.field_area_offset + ORIGIN[1]
|
|
|
|
assert box_top >= label_bottom, \
|
|
"the input box must start below the label's descenders"
|
|
|
|
def test_reported_height_covers_everything_drawn(self, font, canvas):
|
|
image, draw = canvas
|
|
renderable = make_field(font, draw)
|
|
renderable.render()
|
|
|
|
top, bottom = ORIGIN[1], ORIGIN[1] + int(renderable.size[1])
|
|
rows = ink_rows(image, range(ORIGIN[0], ORIGIN[0] + 200), range(0, 200))
|
|
assert min(rows) >= top, "ink above the field's declared box"
|
|
assert max(rows) < bottom, "ink below the field's declared box"
|
|
|
|
|
|
class TestStackedFieldsDoNotCollide:
|
|
|
|
def test_form_layout_leaves_labels_clear(self, font):
|
|
page = Page(size=(300, 400), style=PageStyle())
|
|
form = Form("signup")
|
|
for name in ["Username", "Email Address", "Password"]:
|
|
form.add_field(FormField(name=name.lower().replace(" ", "_"),
|
|
field_type=FormFieldType.TEXT, label=name))
|
|
|
|
ok, ids = form_layouter(form, page, font)
|
|
assert ok and len(ids) == 3
|
|
|
|
fields = [c for c in page.children if isinstance(c, FormFieldText)]
|
|
assert len(fields) == 3
|
|
|
|
for earlier, later in zip(fields, fields[1:]):
|
|
earlier_bottom = earlier.origin[1] + earlier.size[1]
|
|
assert later.origin[1] >= earlier_bottom, \
|
|
"fields overlap: a label would print over the preceding input box"
|
|
|
|
def test_rendered_form_has_no_ink_collisions(self, font):
|
|
"""Every field's ink stays within its own declared bounds."""
|
|
page = Page(size=(300, 400), style=PageStyle())
|
|
form = Form("signup")
|
|
for name in ["Username", "Email Address"]:
|
|
form.add_field(FormField(name=name.lower(), field_type=FormFieldType.TEXT,
|
|
label=name))
|
|
form_layouter(form, page, font)
|
|
image = page.render()
|
|
|
|
fields = [c for c in page.children if isinstance(c, FormFieldText)]
|
|
for field in fields:
|
|
top = int(field.origin[1])
|
|
bottom = top + int(field.size[1])
|
|
rows = ink_rows(image, range(int(field.origin[0]),
|
|
int(field.origin[0] + field.size[0])),
|
|
range(max(0, top - 6), top))
|
|
assert not rows, f"ink found just above a field at y={top}"
|
|
|
|
|
|
class TestClickTargetsFollowTheLayout:
|
|
|
|
def test_click_in_the_input_area_focuses(self, font, canvas):
|
|
_, draw = canvas
|
|
renderable = make_field(font, draw)
|
|
|
|
inside = (5, renderable.field_area_offset + FIELD_HEIGHT // 2)
|
|
assert renderable.handle_click(inside) is True
|
|
assert renderable._focused is True
|
|
|
|
def test_click_on_the_label_does_not_focus(self, font, canvas):
|
|
_, draw = canvas
|
|
renderable = make_field(font, draw)
|
|
|
|
on_label = (5, 2)
|
|
assert renderable.handle_click(on_label) is False
|