feat(testing): add template validation suite and fix HTML structure issues

This commit is contained in:
matt 2025-11-04 10:08:49 -08:00
parent 40023e93b8
commit e17dcf6283
6 changed files with 34 additions and 29 deletions

View file

@ -9,6 +9,11 @@ This format follows Keep a Changelog principles and aims for Semantic Versioning
## [Unreleased] ## [Unreleased]
### Added ### Added
- **Template Validation Tests**: Comprehensive test suite for HTML/Jinja2 templates
- Validates Jinja2 syntax across all templates
- Checks HTML structure (balanced tags, unique IDs, proper attributes)
- Basic accessibility validation (alt text, form labels, button types)
- Regression prevention thresholds to maintain code quality
- **Code Quality Tools**: Enhanced development tooling for maintainability - **Code Quality Tools**: Enhanced development tooling for maintainability
- Automated utilities for code cleanup - Automated utilities for code cleanup
- Improved type checking configuration - Improved type checking configuration
@ -81,6 +86,10 @@ This format follows Keep a Changelog principles and aims for Semantic Versioning
- Optimized linting rules for development workflow - Optimized linting rules for development workflow
### Fixed ### Fixed
- **Template Quality**: Resolved HTML structure issues found by validation tests
- Fixed duplicate ID attributes in build wizard and theme picker templates
- Removed erroneous block tags from component documentation
- Corrected template structure for HTMX fragments
- **Code Quality**: Resolved type checking warnings and improved code maintainability - **Code Quality**: Resolved type checking warnings and improved code maintainability
- Fixed type annotation inconsistencies - Fixed type annotation inconsistencies
- Cleaned up redundant code quality suppressions - Cleaned up redundant code quality suppressions

View file

@ -3,9 +3,14 @@
## [Unreleased] ## [Unreleased]
### Summary ### Summary
Web UI improvements with Tailwind CSS migration, TypeScript conversion, component library, enhanced code quality tools, and optional card image caching for faster performance and better maintainability. Web UI improvements with Tailwind CSS migration, TypeScript conversion, component library, template validation tests, enhanced code quality tools, and optional card image caching for faster performance and better maintainability.
### Added ### Added
- **Template Validation Tests**: Comprehensive test suite ensuring HTML/template quality
- Validates Jinja2 syntax and structure
- Checks for common HTML issues (duplicate IDs, balanced tags)
- Basic accessibility validation
- Prevents regression in template quality
- **Code Quality Tools**: Enhanced development tooling for maintainability - **Code Quality Tools**: Enhanced development tooling for maintainability
- Automated utilities for code cleanup - Automated utilities for code cleanup
- Improved type checking configuration - Improved type checking configuration
@ -78,6 +83,10 @@ Web UI improvements with Tailwind CSS migration, TypeScript conversion, componen
_None_ _None_
### Fixed ### Fixed
- **Template Quality**: Resolved HTML structure issues
- Fixed duplicate ID attributes in templates
- Removed erroneous template block tags
- Corrected structure for HTMX fragments
- **Code Quality**: Resolved type checking warnings and improved code maintainability - **Code Quality**: Resolved type checking warnings and improved code maintainability
- Fixed type annotation inconsistencies - Fixed type annotation inconsistencies
- Cleaned up redundant code quality suppressions - Cleaned up redundant code quality suppressions

View file

@ -66,8 +66,8 @@
{% endif %} {% endif %}
{% if (query is defined and query and (not candidates or (candidates|length == 0))) and not inspect %} {% if (query is defined and query and (not candidates or (candidates|length == 0))) and not inspect %}
<div id="candidate-grid" class="muted" style="margin-top:.5rem;" aria-live="polite"> <div id="no-results-message" class="muted" style="margin-top:.5rem;" aria-live="polite">
No results for “{{ query }}”. Try a shorter name or a different spelling. No results for "{{ query }}". Try a shorter name or a different spelling.
</div> </div>
{% endif %} {% endif %}

View file

@ -33,7 +33,6 @@
<!-- BUTTONS --> <!-- BUTTONS -->
<section id="buttons" class="section-spacing"> <section id="buttons" class="section-spacing">
{% call panel(title='Buttons', padding='lg') %} {% call panel(title='Buttons', padding='lg') %}
{% block body %}
<h4 style="margin-top: 0;">Button Variants</h4> <h4 style="margin-top: 0;">Button Variants</h4>
<div class="btn-group content-spacing-lg"> <div class="btn-group content-spacing-lg">
@ -76,16 +75,14 @@
<div class="btn-group"> <div class="btn-group">
{{ button('Go Home', href='/', variant='ghost') }} {{ button('Go Home', href='/', variant='ghost') }}
{{ button('Build Deck', href='/build', variant='primary') }} {{ button('Build Deck', href='/build', variant='primary') }}
</div> </code></pre>
{% endblock %}
{% endcall %} {% endcall %}
</section> </section>
<!-- MODALS --> <!-- MODALS -->
<section id="modals" style="margin-top: 2rem;"> <section id="modals" style="margin-top: 2rem;">
{% call panel(title='Modals', padding='lg') %} {% call panel(title='Modals', padding='lg') %}
{% block body %}
<p style="margin-top: 0; color: var(--muted);">Click buttons to see modal examples</p> <p style="margin-top: 0; color: var(--muted);">Click buttons to see modal examples</p>
@ -104,14 +101,12 @@
{{ button('XLarge (960px)', onclick='showSizedModal("xl")') }} {{ button('XLarge (960px)', onclick='showSizedModal("xl")') }}
</div> </div>
{% endblock %}
{% endcall %} {% endcall %}
</section> </section>
<!-- FORMS --> <!-- FORMS -->
<section id="forms" style="margin-top: 2rem;"> <section id="forms" style="margin-top: 2rem;">
{% call panel(title='Form Components', padding='lg') %} {% call panel(title='Form Components', padding='lg') %}
{% block body %}
<form onsubmit="event.preventDefault(); alert('Form submitted!');" style="max-width: 600px;"> <form onsubmit="event.preventDefault(); alert('Form submitted!');" style="max-width: 600px;">
@ -147,14 +142,12 @@
</div> </div>
</form> </form>
{% endblock %}
{% endcall %} {% endcall %}
</section> </section>
<!-- CARD DISPLAY --> <!-- CARD DISPLAY -->
<section id="cards" style="margin-top: 2rem;"> <section id="cards" style="margin-top: 2rem;">
{% call panel(title='Card Display Components', padding='lg') %} {% call panel(title='Card Display Components', padding='lg') %}
{% block body %}
<h4 style="margin-top: 0;">Card Thumbnail Sizes</h4> <h4 style="margin-top: 0;">Card Thumbnail Sizes</h4>
<div style="display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-start; margin-bottom: 2rem;"> <div style="display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-start; margin-bottom: 2rem;">
@ -179,14 +172,12 @@
{'name': 'Birds of Paradise', 'role': 'ramp', 'tags': ['Ramp', 'Creature']} {'name': 'Birds of Paradise', 'role': 'ramp', 'tags': ['Ramp', 'Creature']}
], size='medium', columns=3) }} ], size='medium', columns=3) }}
{% endblock %}
{% endcall %} {% endcall %}
</section> </section>
<!-- PANELS --> <!-- PANELS -->
<section id="panels" style="margin-top: 2rem;"> <section id="panels" style="margin-top: 2rem;">
{% call panel(title='Panel Components', padding='lg') %} {% call panel(title='Panel Components', padding='lg') %}
{% block body %}
<h4 style="margin-top: 0;">Panel Variants</h4> <h4 style="margin-top: 0;">Panel Variants</h4>
{{ simple_panel(title='Default Panel', content='<p>This is a default panel with standard background.</p>', variant='default') }} {{ simple_panel(title='Default Panel', content='<p>This is a default panel with standard background.</p>', variant='default') }}
@ -232,14 +223,12 @@
<h4 style="margin-top: 2rem;">Collapsible Panel</h4> <h4 style="margin-top: 2rem;">Collapsible Panel</h4>
{% call collapsible_panel(title='Advanced Options', expanded=False) %} {% call collapsible_panel(title='Advanced Options', expanded=False) %}
{% block body %}
<p>These are advanced settings that are hidden by default.</p> <p>These are advanced settings that are hidden by default.</p>
<ul> <ul>
<li>Option 1: Enable feature X</li> <li>Option 1: Enable feature X</li>
<li>Option 2: Adjust threshold Y</li> <li>Option 2: Adjust threshold Y</li>
<li>Option 3: Configure behavior Z</li> <li>Option 3: Configure behavior Z</li>
</ul> </ul>
{% endblock %}
{% endcall %} {% endcall %}
<h4 style="margin-top: 2rem;">Empty State</h4> <h4 style="margin-top: 2rem;">Empty State</h4>
@ -254,7 +243,6 @@
<h4 style="margin-top: 2rem;">Loading State</h4> <h4 style="margin-top: 2rem;">Loading State</h4>
{{ loading_panel(message='Building deck...', spinner=True) }} {{ loading_panel(message='Building deck...', spinner=True) }}
{% endblock %}
{% endcall %} {% endcall %}
</section> </section>

View file

@ -17,9 +17,9 @@
Examples: Examples:
{% call form_field('Email', 'email', required=True) %} {% call form_field('Email', 'email', required=True) %}
{% block body %}
<input type="email" name="email" /> <input type="email" name="email" />
{% endblock %}
{% endcall %} {% endcall %}
#} #}
{% macro form_field(label='', name='', required=False, help_text='', error='', classes='') %} {% macro form_field(label='', name='', required=False, help_text='', error='', classes='') %}
@ -72,7 +72,7 @@
#} #}
{% macro text_input(name, label='', type='text', value='', placeholder='', required=False, disabled=False, readonly=False, help_text='', error='', classes='', attrs='') %} {% macro text_input(name, label='', type='text', value='', placeholder='', required=False, disabled=False, readonly=False, help_text='', error='', classes='', attrs='') %}
{% call form_field(label, name, required, help_text, error, classes) %} {% call form_field(label, name, required, help_text, error, classes) %}
{% block body %}
<input type="{{ type }}" <input type="{{ type }}"
id="{{ name }}" id="{{ name }}"
name="{{ name }}" name="{{ name }}"
@ -83,7 +83,7 @@
{% if disabled %}disabled{% endif %} {% if disabled %}disabled{% endif %}
{% if readonly %}readonly{% endif %} {% if readonly %}readonly{% endif %}
{{ attrs|safe }} /> {{ attrs|safe }} />
{% endblock %}
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}
@ -110,7 +110,7 @@
#} #}
{% macro textarea(name, label='', value='', placeholder='', rows=4, required=False, disabled=False, readonly=False, help_text='', error='', classes='', attrs='') %} {% macro textarea(name, label='', value='', placeholder='', rows=4, required=False, disabled=False, readonly=False, help_text='', error='', classes='', attrs='') %}
{% call form_field(label, name, required, help_text, error, classes) %} {% call form_field(label, name, required, help_text, error, classes) %}
{% block body %}
<textarea id="{{ name }}" <textarea id="{{ name }}"
name="{{ name }}" name="{{ name }}"
class="form-textarea" class="form-textarea"
@ -120,7 +120,7 @@
{% if disabled %}disabled{% endif %} {% if disabled %}disabled{% endif %}
{% if readonly %}readonly{% endif %} {% if readonly %}readonly{% endif %}
{{ attrs|safe }}>{{ value }}</textarea> {{ attrs|safe }}>{{ value }}</textarea>
{% endblock %}
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}
@ -148,7 +148,7 @@
#} #}
{% macro select(name, label='', options=[], value='', required=False, disabled=False, help_text='', error='', classes='', attrs='') %} {% macro select(name, label='', options=[], value='', required=False, disabled=False, help_text='', error='', classes='', attrs='') %}
{% call form_field(label, name, required, help_text, error, classes) %} {% call form_field(label, name, required, help_text, error, classes) %}
{% block body %}
<select id="{{ name }}" <select id="{{ name }}"
name="{{ name }}" name="{{ name }}"
class="form-select" class="form-select"
@ -163,7 +163,7 @@
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
{% endblock %}
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}
@ -295,7 +295,7 @@
#} #}
{% macro number_input(name, label='', value='', min='', max='', step=1, placeholder='', required=False, disabled=False, help_text='', error='', classes='', attrs='') %} {% macro number_input(name, label='', value='', min='', max='', step=1, placeholder='', required=False, disabled=False, help_text='', error='', classes='', attrs='') %}
{% call form_field(label, name, required, help_text, error, classes) %} {% call form_field(label, name, required, help_text, error, classes) %}
{% block body %}
<input type="number" <input type="number"
id="{{ name }}" id="{{ name }}"
name="{{ name }}" name="{{ name }}"
@ -308,7 +308,7 @@
{% if required %}required{% endif %} {% if required %}required{% endif %}
{% if disabled %}disabled{% endif %} {% if disabled %}disabled{% endif %}
{{ attrs|safe }} /> {{ attrs|safe }} />
{% endblock %}
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}
@ -333,7 +333,7 @@
#} #}
{% macro file_input(name, label='', accept='', multiple=False, required=False, disabled=False, help_text='', error='', classes='', attrs='') %} {% macro file_input(name, label='', accept='', multiple=False, required=False, disabled=False, help_text='', error='', classes='', attrs='') %}
{% call form_field(label, name, required, help_text, error, classes) %} {% call form_field(label, name, required, help_text, error, classes) %}
{% block body %}
<input type="file" <input type="file"
id="{{ name }}" id="{{ name }}"
name="{{ name }}" name="{{ name }}"
@ -343,7 +343,7 @@
{% if required %}required{% endif %} {% if required %}required{% endif %}
{% if disabled %}disabled{% endif %} {% if disabled %}disabled{% endif %}
{{ attrs|safe }} /> {{ attrs|safe }} />
{% endblock %}
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}

View file

@ -77,7 +77,6 @@
{% endif %} {% endif %}
</div> </div>
<div id="theme-preview-host"></div> <div id="theme-preview-host"></div>
<div id="filter-chips" class="filter-chips" aria-label="Active filters"></div>
</div> </div>
<style> <style>
.theme-picker-controls { display:flex; flex-wrap:wrap; gap:.5rem; margin-bottom:.75rem; } .theme-picker-controls { display:flex; flex-wrap:wrap; gap:.5rem; margin-bottom:.75rem; }