# -*- coding: utf-8 -*- """Host tests for `vibe.pa_extents` -- the Panel Analysis form's "Concrete Extents" section layout: panel geometry, the openings table, the drawings row, and Section Properties. Runs on plain CPython via `tests/conftest.py`'s `vibe` stub -- this module imports no Revit and no `panelsketch` (it only NAMES a `panelsketch` entry point in a `drawing` block's `source`; the renderer, not this module, calls it). What this suite checks, per the task brief: only known block `kind`s come out; every `field` block carries a plain-English label and a hint; `openings_rows` being absent degrades to a `problems` entry rather than a crash; Section Properties never prints a row it did not derive; and `values` is never mutated. """ from vibe import panelanalysisform as paf # noqa: F401 (registers the stub) from vibe import pa_extents as E ALLOWED_KINDS = {'heading', 'columns', 'field', 'kv', 'sentence', 'table', 'drawing', 'rule', 'space'} def _walk(blocks): """Yield every block in `blocks`, recursing into `columns`.""" for b in blocks: yield b if b.get('kind') == 'columns': for col in b['columns']: for sub in _walk(col): yield sub def _all_field_blocks(blocks): return [b for b in _walk(blocks) if b.get('kind') == 'field'] def _all_drawing_blocks(blocks): return [b for b in _walk(blocks) if b.get('kind') == 'drawing'] # --------------------------------------------------------------- vocabulary def test_empty_values_only_known_kinds_no_crash(): blocks, problems = E.page({}) assert isinstance(blocks, list) assert isinstance(problems, list) for b in _walk(blocks): assert b['kind'] in ALLOWED_KINDS, 'unknown kind: {0!r}'.format(b) def test_none_values_does_not_raise(): # page() must never raise -- an absent values dict is a valid call. blocks, problems = E.page(None) assert isinstance(blocks, list) assert isinstance(problems, list) def test_filled_values_only_known_kinds_no_crash(): values = { 'length_ft': '107', 'width_ft': '9', 'thickness_in': '8', 'section_type': 'Solid', 'top_in_form': 'Interior', 'top_elevation_ft': '107', 'bottom_elevation_ft': '0', 'left_miter': 'no', 'right_miter': 'yes', 'openings_rows': [ {'type': 'opening', 'pos_along': 8.0 / 12.0, 'pos_along_from': 'start', 'length': 6.875, 'pos_cross': 2 + 11.5 / 12.0, 'pos_cross_from': 'left', 'width': 3 + 4.5 / 12.0, 'pos_vert': None, 'height': None, 'label': ''}, ], } blocks, problems = E.page(values) for b in _walk(blocks): assert b['kind'] in ALLOWED_KINDS # ------------------------------------------------------------ field quality def test_every_field_has_a_plain_label_and_a_hint(): blocks, _problems = E.page({}) fields = _all_field_blocks(blocks) assert fields, 'expected at least one field block' for f in fields: assert f['label'] and f['label'].strip(), \ 'field {0!r} has no plain-English label'.format(f['key']) assert f['hint'] and f['hint'].strip(), \ 'field {0!r} has no hint'.format(f['key']) assert f['editor'] in ('text', 'num', 'check', 'choice') assert isinstance(f['required'], bool) def test_known_paf_fields_use_panelanalysisform_label_verbatim(): # Checked LIVE against panelanalysisform.FIELDS rather than a hardcoded # key list, because a sibling agent is restructuring that module WHILE # this suite runs -- `left_miter` / `right_miter` landed there mid-task # (see the module docstring's "read defensively" discipline). Any key # `_paf_field` finds must be reflected verbatim; any key it does not # find is covered by the MISSING test below instead. blocks, _problems = E.page({}) fields = {f['key']: f for f in _all_field_blocks(blocks)} candidates = ('length_ft', 'width_ft', 'thickness_in', 'section_type', 'top_in_form', 'left_miter', 'right_miter', 'top_elevation_ft', 'bottom_elevation_ft') for key in candidates: if key not in fields: continue if E._paf_field(key) is None: continue # MISSING from panelanalysisform -- not this test's job assert fields[key]['label'] == paf.label_of(key), \ '{0} EXISTS in panelanalysisform but label was not taken from it' \ .format(key) assert fields[key]['hint'] == (paf.help_of(key) or fields[key]['hint']) def test_keys_missing_from_panelanalysisform_are_named_in_problems(): # Some Eriksson concepts on this page are not yet fields # panelanalysisform.FIELDS declares -- MISSING, per the task brief, # never an error. The page still draws a slot for them (to match # Eriksson's own layout) but must say, in `problems`, that a value # typed there is not wired up yet. Checked live against `_paf_field` # rather than a hardcoded list, since the sibling agent restructuring # panelanalysisform may add any of these keys before this suite is # read again. blocks, problems = E.page({}) fields = {f['key']: f for f in _all_field_blocks(blocks)} candidate_keys = ('top_elevation_ft', 'bottom_elevation_ft', 'left_miter', 'right_miter', 'top_in_form_check', 'top_in_form_width_in', 'top_in_form_height_in') joined = ' '.join(problems) still_missing = [k for k in candidate_keys if E._paf_field(k) is None] assert still_missing, \ 'expected at least one of {0} to still be MISSING from ' \ 'panelanalysisform for this test to mean anything'.format(candidate_keys) for key in still_missing: assert key in fields, '{0} should still get a field slot'.format(key) assert repr(key) in joined, \ '{0} MISSING from panelanalysisform but not named in problems' \ .format(key) def test_length_ft_is_required_and_matches_paf(): blocks, _problems = E.page({}) fields = {f['key']: f for f in _all_field_blocks(blocks)} assert fields['length_ft']['required'] is True assert fields['width_ft']['required'] is True assert fields['thickness_in']['required'] is True # ---------------------------------------------------------------- miters def test_miter_blank_prints_no_sentence(): blocks, _problems = E.page({}) sentences = [b['text'] for b in _walk(blocks) if b['kind'] == 'sentence'] assert not any('miter not included' in s for s in sentences) def test_miter_explicit_no_prints_a_sentence_not_a_blank(): blocks, _problems = E.page({'left_miter': 'no'}) sentences = [b['text'] for b in _walk(blocks) if b['kind'] == 'sentence'] assert any('Left miter not included' in s for s in sentences) def test_miter_explicit_yes_prints_no_sentence(): blocks, _problems = E.page({'right_miter': 'yes'}) sentences = [b['text'] for b in _walk(blocks) if b['kind'] == 'sentence'] assert not any('Right miter not included' in s for s in sentences) # -------------------------------------------------------------- openings def test_openings_rows_absent_degrades_with_a_problem_not_a_crash(): blocks, problems = E.page({'openings': ''}) assert any('openings_rows' in p for p in problems) # still lays out a sentence, never a broken table for b in _walk(blocks): assert b['kind'] in ALLOWED_KINDS def test_openings_rows_absent_but_raw_text_present_is_shown_verbatim(): blocks, problems = E.page({'openings': '2, 1, 3, 4, Door'}) assert any('openings_rows' in p for p in problems) sentences = [b['text'] for b in _walk(blocks) if b['kind'] == 'sentence'] assert any('2, 1, 3, 4, Door' in s for s in sentences) def test_openings_rows_present_builds_grouped_table(): rows = [ {'type': 'opening', 'pos_along': 8.0 / 12.0, 'pos_along_from': 'start', 'length': 6.0 + 10.5 / 12.0, 'pos_cross': 2.0 + 11.5 / 12.0, 'pos_cross_from': 'left', 'width': 3.0 + 4.5 / 12.0, 'pos_vert': None, 'height': None, 'label': ''}, {'type': 'reveal', 'pos_along': None, 'pos_along_from': None, 'length': None, 'pos_cross': None, 'pos_cross_from': None, 'width': None, 'pos_vert': None, 'height': None, 'label': ''}, ] blocks, problems = E.page({'openings_rows': rows}) assert not any('openings_rows' in p for p in problems) tables = [b for b in _walk(blocks) if b['kind'] == 'table'] assert len(tables) == 1 table = tables[0] assert table['headers'] == ['Type', 'Position', 'Length', 'Horizontal Position', 'Width', 'Vertical Position', 'Height'] assert table['header_groups'] == [('Position along Length', 2), ('Position in Cross Section', 4)] assert len(table['rows']) == 2 first = table['rows'][0] assert first[0] == 'Opening' assert first[1] == '8" from Start' assert first[3] == "2'-11 1/2\" from Left" assert first[5] == '-' # pos_vert blank -> dash assert first[6] == '-' # height blank -> dash second = table['rows'][1] assert second[0] == 'Reveal' assert second[1] == '-' # blank position -> plain dash, no "from ..." def test_openings_rows_empty_list_is_a_real_answer_not_missing(): blocks, problems = E.page({'openings_rows': []}) assert not any('openings_rows' in p for p in problems) tables = [b for b in _walk(blocks) if b['kind'] == 'table'] assert len(tables) == 1 assert tables[0]['rows'] == [] sentences = [b['text'] for b in _walk(blocks) if b['kind'] == 'sentence'] assert any('No openings' in s for s in sentences) def test_unknown_opening_type_is_echoed_not_invented(): rows = [{'type': 'blockout', 'pos_along': None, 'pos_along_from': None, 'length': None, 'pos_cross': None, 'pos_cross_from': None, 'width': None, 'pos_vert': None, 'height': None, 'label': ''}] blocks, _problems = E.page({'openings_rows': rows}) table = [b for b in _walk(blocks) if b['kind'] == 'table'][0] assert table['rows'][0][0] == 'Blockout' # title-cased, not remapped # ------------------------------------------------------------ section props def test_section_properties_never_invents_a_derived_row(): blocks, problems = E.page({'width_ft': '9', 'thickness_in': '8'}) kvs = [b for b in _walk(blocks) if b['kind'] == 'kv'] assert kvs, 'expected a Section Properties kv block once width+thickness given' labels = [row[0] for row in kvs[0]['rows']] assert labels == ['Area, in²', 'Width, in', 'Height, in'] forbidden = ('Cx', 'Cy', 'Izz', 'Iyy', 'Izy', 'Sb', 'St') for row_label, _value in kvs[0]['rows']: for bad in forbidden: assert not row_label.startswith(bad) joined = ' '.join(problems) for bad in forbidden: assert bad in joined, \ '{0} should be named in problems as refused, not silently dropped'.format(bad) def test_section_properties_values_are_correct(): blocks, _problems = E.page({'width_ft': '9', 'thickness_in': '8'}) kv = [b for b in _walk(blocks) if b['kind'] == 'kv'][0] rows = dict(kv['rows']) assert rows['Width, in'] == '108' assert rows['Height, in'] == '8' assert rows['Area, in²'] == '864' def test_section_properties_missing_inputs_never_prints_a_kv_block(): blocks, problems = E.page({}) kvs = [b for b in _walk(blocks) if b['kind'] == 'kv'] assert kvs == [] sentences = [b['text'] for b in _walk(blocks) if b['kind'] == 'sentence'] assert any('waiting on' in s for s in sentences) assert any('could not be derived' in p for p in problems) # -------------------------------------------------------------- drawings def test_drawings_use_only_named_panelsketch_entry_points(): blocks, _problems = E.page({}) drawings = _all_drawing_blocks(blocks) assert drawings, 'expected at least the Cross Section drawing' allowed_sources = {'sketch', 'elevation', 'plan', 'section', 'weight_diagram'} for d in drawings: assert d['source'] in allowed_sources assert isinstance(d['w'], int) and d['w'] > 0 assert isinstance(d['h'], int) and d['h'] > 0 assert d['caption'] and d['caption'].strip() def test_side_and_front_elevation_limitation_is_named_in_problems(): blocks, problems = E.page({}) captions = [d['caption'] for d in _all_drawing_blocks(blocks)] assert 'Side Elevation' in captions assert 'Front Elevation' in captions assert 'Cross Section' in captions assert any('same combined image' in p for p in problems) # ------------------------------------------------------------ no mutation def test_values_dict_is_never_mutated(): values = {'length_ft': '107', 'width_ft': '9', 'thickness_in': '8', 'openings': 'garbage'} before = dict(values) E.page(values) assert values == before def test_page_never_raises_on_garbage_values(): garbage = { 'width_ft': 'not-a-number', 'thickness_in': [1, 2, 3], 'openings_rows': 'not-a-list', 'left_miter': object(), 'openings': None, } blocks, problems = E.page(garbage) assert isinstance(blocks, list) assert isinstance(problems, list) # --------------------------------------------------------------- utilities def test_feet_str_formats_like_eriksson(): assert E._feet_str(8.0 / 12.0) == '8"' assert E._feet_str(0.0) == '0"' assert E._feet_str(20.0 + 8.0 / 12.0) == "20'-8\"" assert E._feet_str(2.0 + 11.5 / 12.0) == "2'-11 1/2\"" def test_dim_or_dash_passes_through_preformatted_strings(): assert E._dim_or_dash("2'-11.5\"") == "2'-11.5\"" assert E._dim_or_dash('') == '-' assert E._dim_or_dash(None) == '-' def test_tri_bool_blank_is_none_not_false(): assert E._tri_bool('') is None assert E._tri_bool(None) is None assert E._tri_bool('no') is False assert E._tri_bool('yes') is True