Files
cgalo5758 bc4a933a3d Pin the HTML of every registered form
TestFormsHTML renders each declaration in every binding the library
renders it in and compares the text, through golden.Compare, with a file
under internal/server/testdata/forms. A form that is not a rows form is
rendered unbound, on the edit side and on the create side, unbound with
values, and as a record (every field bound to a value its rules accept,
three cells per field, an instance and a message); a search is then
rendered boosted and with a target, a select and an out-of-band select,
and any other form is rendered refused (an empty POST parsed through the
declaration) with the refusals of a 600-character value in every field.
A rows form is rendered unbound, then with an open row, a staged delta
and the tray (or the row's own commit), each row field and one row
action. Runtime options are a fixed "Choose one", Alpha and Beta.

The subtests are grouped in three. registered/<name> holds the 19 forms
forms.All returns in the internal/server test binary; the files
number 26 in all. per-request/<case> holds the declarations built per
request: the settings form, the org type card's two copies, the rules
form, and the list search embedded (one sibling parameter) and
standalone. library-defaults holds a table's "Field" and "Value"
headings, a textarea's three rows and the htmx verb of GET, PUT, PATCH,
DELETE and POST on a form that is not a search.

TestFormsGoldenFilesAreAllRendered fails for any file under
testdata/forms that TestFormsHTML does not write, so a form that leaves
the registry leaves no stale file. TestPerRequestFormsSatisfyTheInvariants
runs CheckInvariants over the org type card's two copies and the rules
form's copy; the settings form has its own test, and the list search
builds its declaration inside listSearchForm and never returns it, so no
test can pass it to CheckInvariants and it stays unchecked.

Shown to change no output: only a test file and its goldens are added.
The names and results of ./internal/forms ./internal/server
./internal/integrations/... (2113 at the parent, 2144 here, none
failing, one skip on each side), taken with the test database reset and
the app down at the parent and at this commit, are equal apart from the
new tests and their 28 subtests.
The registered forms' files in name order followed by the defaults' file
are byte-equal to the output of a standalone program that renders the
same bindings, run at 0abbfac7; no file
under internal/forms, internal/server or internal/embeds that is not a
test or a golden has changed since. A separate render of the per-request
declarations, unbound only, equals the unbound section of the org type
card's files and of the rules form's file apart from the runtime options
it did not bind, and equals the list search's two files exactly. The
other bindings and the settings file were read against their
declarations, not compared with another render. Three runs in a row
produce the same files, with no -update.

22 breaks in the form library's render.go and the three server files of
the per-request cases, each edited once and each caught by the new
tests. The old side has no counterpart: every break is a new-side
record, since no old test names these. None dropped.

Pinned defects, so a later fix flips them by name: the list search's
hidden sibling inputs come out in map order with two or more siblings
(rendered here with one, so the file does not vary from run to run);
the library defaults no registered form reaches, which every earlier
test missed.

Not probed: the templates' own markup, because the files hold it for a
later template change to show while the breaks probe the view the
library builds; fieldAttrs, buildRows, RowField and resolveRowsFocus,
whose output is in the files; the list search's per-request declaration
against the invariants, for the reason above; the stale-file check,
which guards the test's own files. A dense checkbox's label position is
the one render break this net misses, since no registered form has a
dense checkbox; TestFormPartCheckboxLabelSitsAboveTheBox catches it.
2026-10-11 15:55:41 -05:00

204 lines
8.5 KiB
HTML

===== operator.list.controls unbound
<form id="form-operator.list.controls" data-form="operator.list.controls" data-form-kind="search" class="app-form app-form-bar" method="GET" action="/operator" role="search" hx-get="/operator" hx-target="this" hx-swap="outerHTML" hx-push-url="true">
<div id="form-operator.list.controls-error" class="app-form-error" role="alert"></div>
<label class="form-label visually-hidden" for="form-operator.list.controls-q-control">Search this list</label>
<div class="input-group">
<span class="input-group-text"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.44.44.98.98l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.98-.98zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/></svg></span>
<input type="text" class="form-control" id="form-operator.list.controls-q-control" name="q" value="" autocomplete="off">
<button type="submit" class="btn btn-outline-secondary"><span class="spinner-border spinner-border-sm htmx-indicator me-1" aria-hidden="true"></span>Search</button>
</div>
</form>
===== operator.list.controls unbound edit side
<form id="form-operator.list.controls" data-form="operator.list.controls" data-form-kind="search" class="app-form app-form-bar" method="GET" action="/operator" role="search" hx-get="/operator" hx-target="this" hx-swap="outerHTML" hx-push-url="true">
<div id="form-operator.list.controls-error" class="app-form-error" role="alert"></div>
<label class="form-label visually-hidden" for="form-operator.list.controls-q-control">Search this list</label>
<div class="input-group">
<span class="input-group-text"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.44.44.98.98l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.98-.98zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/></svg></span>
<input type="text" class="form-control" id="form-operator.list.controls-q-control" name="q" value="" autocomplete="off">
<button type="submit" class="btn btn-outline-secondary"><span class="spinner-border spinner-border-sm htmx-indicator me-1" aria-hidden="true"></span>Search</button>
</div>
</form>
===== operator.list.controls create side
<form id="form-operator.list.controls" data-form="operator.list.controls" data-form-kind="search" class="app-form app-form-bar" method="GET" action="/operator" role="search" hx-get="/operator" hx-target="this" hx-swap="outerHTML" hx-push-url="true">
<div id="form-operator.list.controls-error" class="app-form-error" role="alert"></div>
<label class="form-label visually-hidden" for="form-operator.list.controls-q-control">Search this list</label>
<div class="input-group">
<span class="input-group-text"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.44.44.98.98l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.98-.98zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/></svg></span>
<input type="text" class="form-control" id="form-operator.list.controls-q-control" name="q" value="" autocomplete="off">
<button type="submit" class="btn btn-outline-secondary"><span class="spinner-border spinner-border-sm htmx-indicator me-1" aria-hidden="true"></span>Search</button>
</div>
</form>
===== operator.list.controls unbound with values
<form id="form-operator.list.controls" data-form="operator.list.controls" data-form-kind="search" class="app-form app-form-bar" method="GET" action="/operator" role="search" hx-get="/operator" hx-target="this" hx-swap="outerHTML" hx-push-url="true">
<div id="form-operator.list.controls-error" class="app-form-error" role="alert"></div>
<label class="form-label visually-hidden" for="form-operator.list.controls-q-control">Search this list</label>
<div class="input-group">
<span class="input-group-text"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.44.44.98.98l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.98-.98zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/></svg></span>
<input type="text" class="form-control" id="form-operator.list.controls-q-control" name="q" value="" autocomplete="off">
<button type="submit" class="btn btn-outline-secondary"><span class="spinner-border spinner-border-sm htmx-indicator me-1" aria-hidden="true"></span>Search</button>
</div>
</form>
===== operator.list.controls record
<form id="form-operator.list.controls-i1" data-form="operator.list.controls" data-form-kind="search" class="app-form app-form-bar" method="GET" action="/operator" role="search" hx-get="/operator" hx-target="this" hx-swap="outerHTML" hx-push-url="true">
<div id="form-operator.list.controls-i1-error" class="app-form-error" role="alert"></div>
<div class="app-form-message mb-2"><p>msg</p></div>
<label class="form-label visually-hidden" for="form-operator.list.controls-i1-q-control">Search this list</label>
<div class="input-group">
<span class="input-group-text"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.44.44.98.98l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.98-.98zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/></svg></span>
<input type="text" class="form-control" id="form-operator.list.controls-i1-q-control" name="q" value="sample" autocomplete="off">
<button type="submit" class="btn btn-outline-secondary"><span class="spinner-border spinner-border-sm htmx-indicator me-1" aria-hidden="true"></span>Search</button>
</div>
</form>
===== operator.list.controls boosted
<form id="form-operator.list.controls" data-form="operator.list.controls" data-form-kind="search" class="app-form app-form-bar" method="GET" action="/operator" role="search" >
<div id="form-operator.list.controls-error" class="app-form-error" role="alert"></div>
<label class="form-label visually-hidden" for="form-operator.list.controls-q-control">Search this list</label>
<div class="input-group">
<span class="input-group-text"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.44.44.98.98l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.98-.98zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/></svg></span>
<input type="text" class="form-control" id="form-operator.list.controls-q-control" name="q" value="" autocomplete="off">
<button type="submit" class="btn btn-outline-secondary"><span class="spinner-border spinner-border-sm htmx-indicator me-1" aria-hidden="true"></span>Search</button>
</div>
</form>
===== operator.list.controls selected
<form id="form-operator.list.controls" data-form="operator.list.controls" data-form-kind="search" class="app-form app-form-bar" method="GET" action="/operator" role="search" hx-get="/operator" hx-target="#x" hx-swap="outerHTML" hx-push-url="true" hx-select="#x" hx-select-oob="#y">
<div id="form-operator.list.controls-error" class="app-form-error" role="alert"></div>
<label class="form-label visually-hidden" for="form-operator.list.controls-q-control">Search this list</label>
<div class="input-group">
<span class="input-group-text"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.44.44.98.98l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.98-.98zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/></svg></span>
<input type="text" class="form-control" id="form-operator.list.controls-q-control" name="q" value="" autocomplete="off">
<button type="submit" class="btn btn-outline-secondary"><span class="spinner-border spinner-border-sm htmx-indicator me-1" aria-hidden="true"></span>Search</button>
</div>
</form>