# -*- coding: utf-8 -*- """Tests for `vibe.pa_criteria.page` -- the Design Criteria section layout. No Revit and no WPF renderer are available here (see `conftest.py`'s docstring for why `vibe` imports this way), so this suite cannot check that the page LOOKS right -- nobody has run this inside Revit and this file does not claim otherwise. What it can and does check mechanically: * `page()` never raises, on an empty dict, a filled dict, `None` values, `None` reads, or a `values` object that is not even a dict. * every block returned is a well-formed member of the block vocabulary `page()`'s docstring promises (`heading`, `columns`, `field`, `kv`, `sentence`, `table`, `rule`, `space`) -- an unknown `kind` renders as nothing on the real form, so a typo here would be a silently missing block that only this test would catch. * every `field` block carries a non-empty, plain-English `label` and a non-empty `hint` -- the owner's whole complaint about the old form was that a field carried only its engineering symbol. * a field key with no backing entry in `panelanalysisform.FIELDS` still renders (an empty editor), and is named in `problems` -- never silently dropped. * `values` is never mutated by a call to `page()`. * the block list is stable: calling `page()` twice on the same input produces the same output. """ import copy import sys from pathlib import Path TESTS_DIR = Path(__file__).resolve().parent if str(TESTS_DIR) not in sys.path: sys.path.insert(0, str(TESTS_DIR)) from vibe import pa_criteria as pc # noqa: E402 KNOWN_KINDS = set(['heading', 'columns', 'field', 'kv', 'sentence', 'table', 'rule', 'space']) KNOWN_EDITORS = set(['text', 'num', 'check', 'choice']) def _walk(blocks): """Yield every block in the tree, including nested `columns` children.""" for b in blocks: yield b if b.get('kind') == 'columns': for col in b.get('columns') or []: for sub in _walk(col): yield sub def _assert_well_formed(blocks): for b in blocks: assert isinstance(b, dict), 'block is not a dict: {0!r}'.format(b) kind = b.get('kind') assert kind in KNOWN_KINDS, 'unknown block kind {0!r}'.format(kind) if kind == 'heading': assert isinstance(b['text'], str) and b['text'].strip() assert b['level'] in (1, 2, 3) elif kind == 'columns': assert isinstance(b['columns'], list) and b['columns'] for col in b['columns']: assert isinstance(col, list) _assert_well_formed(col) elif kind == 'field': assert isinstance(b['key'], str) and b['key'] assert isinstance(b['label'], str) and b['label'].strip(), ( 'field {0!r} has no plain-English label'.format(b['key'])) assert isinstance(b['hint'], str) and b['hint'].strip(), ( 'field {0!r} has no hint'.format(b['key'])) assert b['symbol'] is None or isinstance(b['symbol'], str) assert isinstance(b['required'], bool) assert b['editor'] in KNOWN_EDITORS, ( 'field {0!r} has unknown editor {1!r}' .format(b['key'], b['editor'])) if b['editor'] == 'choice': assert b['choices'], ( 'choice field {0!r} has no choices'.format(b['key'])) else: assert b['choices'] is None elif kind == 'kv': assert isinstance(b['rows'], list) for row in b['rows']: assert len(row) == 2 elif kind == 'sentence': assert isinstance(b['text'], str) and b['text'].strip() elif kind == 'table': assert isinstance(b['headers'], list) and b['headers'] assert isinstance(b['rows'], list) for row in b['rows']: assert len(row) == len(b['headers']), ( 'table row {0!r} does not match headers {1!r}' .format(row, b['headers'])) for cell in row: if isinstance(cell, dict): assert cell.get('editor') == 'check' assert cell.get('key') else: assert isinstance(cell, str) elif kind == 'rule': assert set(b.keys()) == set(['kind']) elif kind == 'space': assert isinstance(b['h'], int) def _field_keys(blocks): return [b['key'] for b in _walk(blocks) if b.get('kind') == 'field'] # --------------------------------------------------------------------- tests def test_empty_values_never_raises_and_is_well_formed(): blocks, problems = pc.page({}) assert isinstance(blocks, list) and blocks assert isinstance(problems, list) _assert_well_formed(blocks) def test_none_values_and_none_reads_never_raises(): blocks, problems = pc.page(None, None) assert isinstance(blocks, list) and blocks _assert_well_formed(blocks) def test_non_dict_values_degrades_rather_than_raising(): blocks, problems = pc.page('not a dict') assert isinstance(blocks, list) and blocks _assert_well_formed(blocks) def test_filled_values_never_raises_and_is_well_formed(): values = { 'fc_psi': '6000', 'fci_psi': '3500', 'unit_weight_pcf': '150', 'concrete_type': 'Normalweight', 'rebar_fy_ksi': '60', 'rebar_es_ksi': '29000', 'ps_fpu_ksi': '270', 'strand_ep_ksi': '28500', 'ps_loss_method': 'PCI', 'ps_loss_humidity_pct': '75', 'design_criteria.rvalue_calc_enabled': 'yes', 'design_criteria.min_reinf_checked': 'true', } blocks, problems = pc.page(values, {'mark': 'sample_mark', 'design_number': 'PNL1.0'}) assert isinstance(blocks, list) and blocks _assert_well_formed(blocks) def test_values_dict_is_never_mutated(): values = {'fc_psi': '6000'} before = copy.deepcopy(values) pc.page(values, {'mark': 'sample_mark'}) assert values == before def test_reads_dict_is_never_mutated(): reads = {'mark': 'sample_mark', 'design_number': 'PNL1.0'} before = copy.deepcopy(reads) pc.page({}, reads) assert reads == before def test_block_list_is_stable_for_the_same_input(): values = {'fc_psi': '6000', 'unit_weight_pcf': '150'} reads = {'mark': 'sample_mark'} a, pa = pc.page(values, reads) b, pb = pc.page(values, reads) assert a == b assert pa == pb def test_missing_key_degrades_to_a_field_and_names_a_problem(): blocks, problems = pc.page({}) keys = set(_field_keys(blocks)) # A key this file invented because Eriksson's page has no home for it # yet in panelanalysisform.FIELDS. assert 'design_criteria.analysis_beta_d' in keys joined = ' '.join(problems) assert 'design_criteria.analysis_beta_d' in joined def test_existing_form_key_is_not_reported_as_missing(): blocks, problems = pc.page({}) joined = ' '.join(problems) # fc_psi is a real, long-standing panelanalysisform.FIELDS key. assert "'fc_psi'" not in joined or 'fc_psi) has no backing' not in joined for p in problems: assert 'fc_psi)' not in p def test_every_field_key_appears_at_most_once(): blocks, _problems = pc.page({}) keys = _field_keys(blocks) dupes = [k for k in set(keys) if keys.count(k) > 1] assert not dupes, 'field key(s) rendered more than once: {0}'.format(dupes) def test_section_heading_is_present(): blocks, _problems = pc.page({}) headings = [b['text'] for b in blocks if b.get('kind') == 'heading'] assert 'Design Criteria' in headings assert headings[0] == 'Design Criteria' def test_prime_notation_used_not_ascii_apostrophe(): blocks, _problems = pc.page({}) for b in _walk(blocks): if b.get('kind') != 'field': continue symbol = b.get('symbol') or '' # f'c / f'ci style ASCII apostrophes must never appear; the real # prime character (U+2032) is what the renderer turns into a # subscript. assert "f'c" not in symbol and "f'ci" not in symbol if u"f′" in symbol or u"E′" in symbol: assert True # sanity: the prime character is reachable at all def test_load_combination_tables_have_include_checkboxes(): blocks, problems = pc.page({}) tables = [b for b in _walk(blocks) if b.get('kind') == 'table'] assert len(tables) == 2 for t in tables: assert t['headers'][-1] == 'Include' for row in t['rows']: cell = row[-1] assert isinstance(cell, dict) and cell['editor'] == 'check' joined = ' '.join(problems) assert 'load_combo.strength' in joined assert 'load_combo.service' in joined def test_soil_blocks_are_sentences_not_editors(): blocks, problems = pc.page({}) sentences = [b['text'] for b in _walk(blocks) if b.get('kind') == 'sentence'] assert any('no exterior soil loads' in s for s in sentences) assert any('no interior soil loads' in s for s in sentences) assert any('soil loads are not collected' in p for p in problems) def test_prestress_loss_group_is_a_sentence_when_blank(): blocks, _problems = pc.page({}) sentences = [b['text'] for b in _walk(blocks) if b.get('kind') == 'sentence'] assert any('No prestress loss assumptions' in s for s in sentences) # And its fields must NOT be rendered while blank -- shown only once # something in the group is filled in. keys = set(_field_keys(blocks)) assert 'ps_loss_method' not in keys def test_prestress_loss_group_becomes_fields_when_filled(): blocks, _problems = pc.page({'ps_loss_method': 'PCI'}) keys = set(_field_keys(blocks)) assert 'ps_loss_method' in keys assert 'ps_loss_humidity_pct' in keys def test_filling_one_group_field_does_not_file_a_phantom_problem_for_others(): # Regression guard: `_group_or_sentence` must not evaluate the # filled-in branch (and thus call `F(key)`, which files a "missing" # problem) when the sentence branch is the one actually shown. _blocks, problems_blank = pc.page({}) joined = ' '.join(problems_blank) assert 'ps_loss_method' not in joined assert 'ps_loss_humidity_pct' not in joined def test_rvalue_and_min_reinforcement_default_sentences_present(): blocks, _problems = pc.page({}) sentences = [b['text'] for b in _walk(blocks) if b.get('kind') == 'sentence'] assert any('R-value is not being calculated' in s for s in sentences) assert any('Minimum steel is not being checked' in s for s in sentences) def test_concrete_grid_has_release_and_inplace_strength_fields(): blocks, _problems = pc.page({}) keys = set(_field_keys(blocks)) assert 'fci_psi' in keys assert 'fc_psi' in keys assert 'conc_eci_psi' in keys assert 'conc_ec_psi' in keys def test_ksi_reconciliation_hint_on_psi_keys(): blocks, _problems = pc.page({}) for b in _walk(blocks): if b.get('kind') == 'field' and b['key'] in ('fc_psi', 'fci_psi'): assert 'ksi' in b['hint'].lower() def test_required_fields_match_panelanalysisform_where_it_exists(): from vibe import panelanalysisform as pf blocks, _problems = pc.page({}) required_here = dict((b['key'], b['required']) for b in _walk(blocks) if b.get('kind') == 'field') for f in pf.FIELDS: key = f['key'] if key in required_here: assert required_here[key] == f['required'], ( 'required flag mismatch for {0!r}'.format(key)) def test_panel_header_kv_shown_only_when_reads_present(): blocks_no_reads, _ = pc.page({}, {}) kvs_no_reads = [b for b in blocks_no_reads if b.get('kind') == 'kv'] blocks_with_reads, _ = pc.page({}, {'mark': 'sample_mark'}) kvs_with_reads = [b for b in blocks_with_reads if b.get('kind') == 'kv'] assert len(kvs_with_reads) >= len(kvs_no_reads) + 1 def test_page_import_survives_a_broken_panelanalysisform(monkeypatch): """Simulates the sibling agent's file being mid-edit / unimportable. `_existing_keys()` is the one function that reaches for `panelanalysisform`; monkeypatching it directly to behave the way it would if the import raised (returning `None`) exercises the same "could not judge" path `page()` must degrade through, without depending on the interpreter's module-cache mechanics for a package (`vibe`) that other tests in this same process have already imported `panelanalysisform` onto as an attribute. """ monkeypatch.setattr(pc, '_existing_keys', lambda: None) blocks, problems = pc.page({}) assert isinstance(blocks, list) and blocks assert any('could not read panelanalysisform' in p for p in problems) def test_choice_field_has_choices(): blocks, _problems = pc.page({}) for b in _walk(blocks): if b.get('kind') == 'field' and b['key'] == 'concrete_type': assert b['editor'] == 'choice' assert 'Normalweight' in b['choices']