Establish the shared frontend editor package and make the Go resolvers the
single source of truth for variable labels.
Go — catalogue SSOT:
- VariableResolver gains Keys() []VariableKey; ResolverSet gains
Catalogue(). The 7 submission resolvers implement Keys() with the
bilingual labels ported from the TS VARIABLE_LABELS table (incl. the
legacy rule.* aliases). Keys() is entity-independent, so
SubmissionVariableCatalogue() builds a metadata-only ResolverSet.
- GET /api/docforge/variables serves the catalogue (auth-gated, static).
- Tests: docforge ResolverSet (BuildBag merge + Catalogue order) and the
submission catalogue integrity (no dupes, labels present, spot-checks).
Frontend — frontend/src/lib/docforge-editor/ (new shared package):
- dom.ts: escapeHtml + cssEscape (pure), with bun tests. Dedupes the two
identical escapeHtml/escapeHTML copies + the cssEscape copy that lived
in the submission editor.
- catalogue.ts: fetchVariableCatalogue() + labelMap() — the client for
the Go catalogue.
- submission-draft.ts now imports escapeHtml/cssEscape from the lib and
fetches the catalogue on boot into state.varLabels (labelFor reads it,
falling back to the raw key if the fetch fails — graceful degrade). The
hardcoded VARIABLE_LABELS table is removed; VARIABLE_GROUPS stays
(presentation: which keys to show + how to section them, legitimately
frontend).
Scope note: the DOM-coupled editor plumbing (wireDraftVars/focus
preservation/autosave debounce) is extracted in slice 6 alongside its first
reuse — the authoring page — rather than speculatively now (extract with the
consumer; same principle as slices 2-3). Slice 5 lands the pure utilities +
the catalogue, which the slice-6 authoring palette consumes.
Verification: go build/vet/test green (Go files gofmt-clean; handlers.go
pre-existing drift, added region clean); bun run build.ts clean;
bun test 274/274 (incl. 5 new docforge-editor tests).
m/paliad#157
61 lines
1.6 KiB
Go
61 lines
1.6 KiB
Go
package docforge
|
|
|
|
import "testing"
|
|
|
|
// fakeResolver is a test double: it owns a namespace, populates a fixed
|
|
// set of key/value pairs, and advertises a fixed catalogue.
|
|
type fakeResolver struct {
|
|
ns string
|
|
values map[string]string
|
|
catalog []VariableKey
|
|
}
|
|
|
|
func (f fakeResolver) Namespace() string { return f.ns }
|
|
func (f fakeResolver) Keys() []VariableKey { return f.catalog }
|
|
func (f fakeResolver) Populate(bag PlaceholderMap) {
|
|
for k, v := range f.values {
|
|
bag[k] = v
|
|
}
|
|
}
|
|
|
|
func TestResolverSet_BuildBagMergesDisjointNamespaces(t *testing.T) {
|
|
set := NewResolverSet(
|
|
fakeResolver{ns: "a", values: map[string]string{"a.x": "1", "a.y": "2"}},
|
|
fakeResolver{ns: "b", values: map[string]string{"b.z": "3"}},
|
|
)
|
|
bag := set.BuildBag()
|
|
if len(bag) != 3 {
|
|
t.Fatalf("bag size = %d; want 3", len(bag))
|
|
}
|
|
for k, want := range map[string]string{"a.x": "1", "a.y": "2", "b.z": "3"} {
|
|
if bag[k] != want {
|
|
t.Errorf("bag[%q] = %q; want %q", k, bag[k], want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResolverSet_AddAndCatalogueOrder(t *testing.T) {
|
|
set := NewResolverSet(
|
|
fakeResolver{ns: "a", catalog: []VariableKey{{Key: "a.x", Group: "a"}}},
|
|
)
|
|
set.Add(fakeResolver{ns: "b", catalog: []VariableKey{
|
|
{Key: "b.y", Group: "b"},
|
|
{Key: "b.z", Group: "b"},
|
|
}})
|
|
|
|
cat := set.Catalogue()
|
|
gotOrder := make([]string, len(cat))
|
|
for i, e := range cat {
|
|
gotOrder[i] = e.Key
|
|
}
|
|
want := []string{"a.x", "b.y", "b.z"} // resolver order, then Keys() order
|
|
if len(gotOrder) != len(want) {
|
|
t.Fatalf("catalogue len = %d; want %d", len(gotOrder), len(want))
|
|
}
|
|
for i := range want {
|
|
if gotOrder[i] != want[i] {
|
|
t.Errorf("catalogue[%d] = %q; want %q", i, gotOrder[i], want[i])
|
|
}
|
|
}
|
|
}
|