Преглед изворни кода

test(settings): cover the section directory's disposal, stale-read, and invalidation paths

The per-file coverage gate flagged four uncovered locations the new
section directory introduced: refresh() after disposal, a read superseded
by a newer one, and the two invalidation handlers that make the served
namespaces re-read (settings/document-updated and connection/reset).
Yichen Jiang пре 2 месеци
родитељ
комит
d8035680b9

+ 27 - 0
packages/client/ui-plugin-config/tests/apply.client.spec.ts

@@ -116,6 +116,33 @@ describe('ui-plugin-config apply', () => {
     }
   })
 
+  it('re-reads the served namespaces when the Host commits a settings document', async () => {
+    // Which namespaces the Host serves is a registration fact the wire never
+    // announces on its own, so the section rides the invalidation that can
+    // accompany a changed composition.
+    const { ctx, slots, describeSettings } = await bench(['bash'])
+    declareRoot(slots)
+    await ctx.plugin({ inject: [...inject], apply }).await()
+    await vi.waitFor(() => { expect(describeSettings).toHaveBeenCalled() })
+    describeSettings.mockClear()
+
+    ctx.remote.$dispatch('settings/document-updated', ['bash', 1])
+
+    await vi.waitFor(() => { expect(describeSettings).toHaveBeenCalled() })
+  })
+
+  it('re-reads the served namespaces after a reconnect', async () => {
+    const { ctx, slots, describeSettings } = await bench(['bash'])
+    declareRoot(slots)
+    await ctx.plugin({ inject: [...inject], apply }).await()
+    await vi.waitFor(() => { expect(describeSettings).toHaveBeenCalled() })
+    describeSettings.mockClear()
+
+    ctx.emit('connection/reset')
+
+    await vi.waitFor(() => { expect(describeSettings).toHaveBeenCalled() })
+  })
+
   it('re-reads the credential when the Host reports the watched reference changed', async () => {
     const { ctx, slots, describeCredentials } = await bench()
     declareRoot(slots)

+ 35 - 0
packages/client/ui-plugin-config/tests/stores.client.spec.ts

@@ -622,6 +622,41 @@ describe('PluginConfigSectionController', () => {
     expect(settings.describe).not.toHaveBeenCalled()
   })
 
+  it('ignores a slot-ledger change that arrives after disposal', async () => {
+    const settings = settingsApi(['bash'])
+    let entries = ledger()
+    const controller = new PluginConfigSectionController(settings.api, () => entries)
+    await controller.load()
+
+    controller.dispose()
+    entries = ledger('bash')
+    controller.refresh()
+
+    expect(controller.inject().hooks.pluginConfigSection.getSnapshot().namespaces).toEqual([])
+  })
+
+  it('drops a read a newer one superseded', async () => {
+    // The section re-reads on every settings-document invalidation, so a slow
+    // first answer must not overwrite the newer one that already landed.
+    const settings = settingsApi(['bash'])
+    const controller = new PluginConfigSectionController(settings.api, () => ledger('bash', 'agent-loop'))
+    const slow = Promise.withResolvers<unknown>()
+    settings.describe.mockReturnValueOnce(slow.promise as never)
+    const stale = controller.load()
+
+    await controller.load()
+    expect(controller.inject().hooks.pluginConfigSection.getSnapshot().namespaces).toEqual(['bash'])
+    slow.resolve({
+      rpcId: 's-0',
+      result: { ok: true, value: { writable: true, hasDocument: true, namespaces: [
+        { ns: 'agent-loop', schema: {}, value: {}, applies: 'live', secrets: [], revision: 0 },
+      ] } },
+    })
+    await stale
+
+    expect(controller.inject().hooks.pluginConfigSection.getSnapshot().namespaces).toEqual(['bash'])
+  })
+
   it('reports the Host answered even when it serves nothing this section shows', async () => {
     const settings = settingsApi(['ui-theme'])
     const controller = new PluginConfigSectionController(settings.api, () => ledger('bash'))