test(10.1-04): cover the Phase 10.1 extension point Go code

- acme fixture plugin under modules/cabana/testdata/extension (gadgets
  controller, header and form partials, lookup widget, JS and CSS)
- TestPhase101FormExtensionSchema, TestPhase101PartialSchema and
  TestPhase101Toolbar: every widget, partial and toolbar boot rule
- TestPhase101PartialSanitizer: tag, attribute and URL allowlist, escaping,
  per-request trans, size/node/depth caps and the view-model guard
- TestPhase101Assets: exact-key asset hits, revalidation, SPA fall-through,
  boot path checks and ?v= schema URLs
- TestPhase101Actions (PostgreSQL): scoping, fill filter, strict body,
  action permission, error mapping, CSRF header, toolbar and partial routes
- TestPhase101BoardwalkExports: ContentType and SetSecurityHeaders
This commit is contained in:
Jakub Zych
2026-09-29 02:48:12 +02:00
parent c3c547c394
commit 7eed4acd87
15 changed files with 1832 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
acme-demo-lookup button {
font: inherit;
color: var(--c-text);
}

View File

@@ -0,0 +1,18 @@
// A plain custom element: one light-DOM button that asks the admin SPA to
// run the field's action. It makes no request and reads no cookie or
// storage; the SPA owns HTTP.
class AcmeDemoLookup extends HTMLElement {
connectedCallback() {
if (this.firstChild) return
const button = document.createElement('button')
button.type = 'button'
button.textContent = this.getAttribute('label') || ''
button.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('summer-action', { bubbles: true, composed: true }))
})
this.append(button)
}
}
if (!customElements.get('acme-demo-lookup')) {
customElements.define('acme-demo-lookup', AcmeDemoLookup)
}

View File

@@ -0,0 +1,5 @@
<dl class="summer-stats">
{{- range .Data.Items -}}
<div class="summer-stat"><dt class="summer-stat__label">{{ trans .Label }}</dt><dd class="summer-stat__value">{{ .Count }}</dd></div>
{{- end -}}
</dl>

View File

@@ -0,0 +1 @@
<section class="summer-partial" aria-label="{{ trans "acme.demo::lang.gadgets.summary" }}"><p>{{ .Data.Name }}</p></section>

View File

@@ -0,0 +1,10 @@
name: acme.demo::lang.gadgets.form
form: ~/plugins/acme/demo/models/gadget/fields.yaml
modelClass: Gadget
defaultRedirect: acme/demo/gadgets
create:
redirect: acme/demo/gadgets/update/:id
redirectClose: acme/demo/gadgets
update:
redirect: acme/demo/gadgets
redirectClose: acme/demo/gadgets

View File

@@ -0,0 +1,11 @@
list: ~/plugins/acme/demo/models/gadget/columns.yaml
modelClass: Gadget
title: acme.demo::lang.gadgets.title
recordUrl: acme/demo/gadgets/update/:id
recordsPerPage: 20
showCheckboxes: true
headerPartial: stats
toolbar:
buttons: [create, delete, recount]
search:
prompt: backend::lang.list.search_prompt

View File

@@ -0,0 +1,12 @@
gadgets:
title: Gadgets
form: Gadget
name: Name
active: Active
group: Group
lookup: Lookup
summary: Summary
total: All gadgets
recount: Recount
recounted: Gadgets were recounted.
looked_up: Name and Active were filled in.

View File

@@ -0,0 +1,12 @@
gadgets:
title: Gadżety
form: Gadżet
name: Nazwa
active: Aktywny
group: Grupa
lookup: Wyszukaj
summary: Podsumowanie
total: Wszystkie gadżety
recount: Przelicz
recounted: Gadżety zostały przeliczone.
looked_up: Uzupełniono Nazwę i Aktywny.

View File

@@ -0,0 +1,7 @@
columns:
name:
label: acme.demo::lang.gadgets.name
searchable: true
active:
label: acme.demo::lang.gadgets.active
type: switch

View File

@@ -0,0 +1,23 @@
fields:
name:
label: acme.demo::lang.gadgets.name
type: text
span: left
active:
label: acme.demo::lang.gadgets.active
type: switch
span: right
group:
label: acme.demo::lang.gadgets.group
type: relation
nameFrom: title
lookup:
label: acme.demo::lang.gadgets.lookup
type: widget
widget: acme-demo-lookup
action: lookup
fill: [name, active]
summary:
label: acme.demo::lang.gadgets.summary
type: partial
path: summary