fix(10.1): WR-01 keep a late schema response from restyling the current view

loadControllerAssets no longer activates stylesheets. activateStyles,
called only by the router, records the controller on screen, and new
links are created disabled unless they belong to it, so a list or form
schema that resolves after its view was left adds its links disabled
instead of switching plugin CSS to the wrong controller. Rebuilt the
embedded admin dist.
This commit is contained in:
Jakub Zych
2026-09-29 09:50:05 +02:00
parent 849a9ffe3f
commit 5bbb0ada05
5 changed files with 73 additions and 9 deletions

View File

@@ -165,8 +165,9 @@ describe('controller stylesheets', () => {
})
describe('loadControllerAssets', () => {
it('creates and activates the styles, then resolves once the scripts have loaded', async () => {
it('creates the styles of the active controller enabled, then resolves once the scripts have loaded', async () => {
loadStyles('acme.demo.other', [asset('css/other.css')])
activateStyles('acme.demo.c1')
const js = asset('js/c1.js')
let settled = false
const done = loadControllerAssets('acme.demo.c1', { scripts: [js], styles: [asset('css/c1.css')] }).then((failed) => {
@@ -184,6 +185,25 @@ describe('loadControllerAssets', () => {
expect(settled).toBe(true)
})
it('never enables a controller the router has left (a late schema response)', async () => {
activateStyles('acme.demo.late-b')
await loadControllerAssets('acme.demo.late-b', { scripts: [], styles: [asset('css/late-b.css')] })
expect(links('acme.demo.late-b').map((link) => link.disabled)).toEqual([false])
// A's schema resolves after the router moved on to B.
await loadControllerAssets('acme.demo.late-a', { scripts: [], styles: [asset('css/late-a.css')] })
expect(links('acme.demo.late-a').map((link) => link.disabled)).toEqual([true])
expect(links('acme.demo.late-b').map((link) => link.disabled)).toEqual([false])
// Links added while no controller is active stay disabled until one is.
activateStyles('')
loadStyles('acme.demo.late-c', [asset('css/late-c.css')])
expect(links('acme.demo.late-c').map((link) => link.disabled)).toEqual([true])
activateStyles('acme.demo.late-c')
expect(links('acme.demo.late-c').map((link) => link.disabled)).toEqual([false])
expect([...links('acme.demo.late-a'), ...links('acme.demo.late-b')].every((link) => link.disabled)).toBe(true)
})
it('resolves with the scripts that failed or were refused and never rejects', async () => {
const ok = asset('js/c2-ok.js')
const bad = asset('js/c2-bad.js')

View File

@@ -636,4 +636,37 @@ describe('controller stylesheets (D-14, UI-SPEC S6)', () => {
expect(link(gadgetsCss)!.disabled).toBe(true)
expect(seen.links.filter((item) => item.getAttribute('href') === widgetsCss)).toHaveLength(1)
})
it('keeps a late schema response of a view already left from restyling the current one (WR-01)', async () => {
const seen = stubAssets()
const gadgets = `${API}/acme/demo/gadgets`
const lateCss = '/admin-test/assets/acme/demo/css/late-widgets.css?v=1'
const currentCss = '/admin-test/assets/acme/demo/css/current-gadgets.css?v=1'
const widgetsSchema = clone(extensionListSchemaFixture)
widgetsSchema.data.assets.styles = [lateCss]
const gadgetsSchema = clone(extensionListSchemaFixture)
gadgetsSchema.data.assets.styles = [currentCss]
delete gadgetsSchema.data.headerPartial
let release: (reply: Reply) => void = () => undefined
const pending = new Promise<Reply>((resolve) => {
release = resolve
})
const { router } = await mountApp(
'/acme/demo/widgets',
listRoutes({
[`GET ${BASE}/schema/list`]: () => pending,
[`GET ${gadgets}/schema/list`]: { body: gadgetsSchema },
[`GET ${gadgets}`]: { body: listRows },
}),
)
await router.push('/acme/demo/gadgets')
await flushPromises()
const link = (url: string) => seen.links.find((item) => item.getAttribute('href') === url)
expect(link(currentCss)!.disabled).toBe(false)
release({ body: widgetsSchema })
await flushPromises()
expect(link(currentCss)!.disabled).toBe(false)
expect(link(lateCss)!.disabled).toBe(true)
})
})