diff --git a/packages/api/gateway/tests/gateway.client.spec.ts b/packages/api/gateway/tests/gateway.client.spec.ts index 59add544ea..c6d4a03eb3 100644 --- a/packages/api/gateway/tests/gateway.client.spec.ts +++ b/packages/api/gateway/tests/gateway.client.spec.ts @@ -458,6 +458,76 @@ describe('Client Typert API', () => { await disposeContext() }) + it('unwinds an already-installed namespace when a later namespace fails to install', async () => { + const ctx = await bench(vi.fn()) + const { scope: _scope, ...probe } = directDescriptor() + const vault: InvocationDescriptor = { + ...probe, + id: '@fixture/vault#vault/seal', + service: 'vault', + namespace: 'vault', + method: 'seal', + } + const defineProperty = Object.defineProperty + const spy = vi.spyOn(Object, 'defineProperty').mockImplementation((target, key, attributes) => { + if (key === 'seal') throw new Error('fixture later-namespace failure') + return defineProperty(target, key, attributes) + }) + try { + await expect(ctx.remote.$mount({ package: '@fixture/two-namespaces', descriptors: [probe, vault] })) + .rejects.toThrow('fixture later-namespace failure') + } finally { + spy.mockRestore() + } + + expect((ctx.remote as unknown as Record).probe).toBeUndefined() + expect((ctx.remote as unknown as Record).vault).toBeUndefined() + await vi.waitFor(() => { expect(ctx.typert.remotes.list()).toEqual([]) }) + }) + + it('rolls back an earlier scoped projection when a later descriptor fails to install', async () => { + const ctx = await bench(vi.fn()) + const { scope: _scope, ...direct } = directDescriptor() + const failing: InvocationDescriptor = { + ...direct, + id: '@fixture/probe#probe/archive', + method: 'archive', + } + const defineProperty = Object.defineProperty + const spy = vi.spyOn(Object, 'defineProperty').mockImplementation((target, key, attributes) => { + if (key === 'archive') throw new Error('fixture trailing failure') + return defineProperty(target, key, attributes) + }) + try { + await expect(ctx.remote.$mount({ + package: '@fixture/scoped-then-failing', + descriptors: [contextDescriptor(), failing], + })).rejects.toThrow('fixture trailing failure') + } finally { + spy.mockRestore() + } + + expect((ctx.remote as unknown as Record).probe).toBeUndefined() + await vi.waitFor(() => { expect(ctx.typert.remotes.list()).toEqual([]) }) + }) + + it('keeps a namespace another contribution still populates when a group leaves', async () => { + const call = vi.fn() + .mockResolvedValue({ ok: true, value: { renamed: true } }) + const ctx = await bench(call) + const { scope: _scope, ...direct } = directDescriptor() + const disposeDirect = await ctx.remote.$mount({ package: '@fixture/direct-owner', descriptors: [direct] }) + const disposeScoped = await ctx.remote.$mount({ package: '@fixture/scoped-owner', descriptors: [contextDescriptor()] }) + + await disposeDirect() + // The namespace survives its first group: the second contribution still owns methods on it. + const surviving = ctx.get('remote.probe') as unknown as Record | undefined + expect(surviving).toBeDefined() + expect(surviving?.create).toBeUndefined() + await disposeScoped() + expect(ctx.get('remote.probe')).toBeUndefined() + }) + it('rejects weak parameter and Context codecs plus malformed scope projections', async () => { const ctx = await bench(vi.fn()) const direct = directDescriptor() diff --git a/packages/client/ui-theme/tests/host.client.spec.ts b/packages/client/ui-theme/tests/host.client.spec.ts index 18f6ed42b8..692158cba0 100644 --- a/packages/client/ui-theme/tests/host.client.spec.ts +++ b/packages/client/ui-theme/tests/host.client.spec.ts @@ -62,4 +62,13 @@ describe('ui-theme host', () => { await ctx.plugin({ apply }).await() expect(scriptText(collect(ctx)[0])).toContain('const preference = "system"') }) + + it('falls back to the schema default while the theme namespace holds no section', async () => { + // A settings provider whose namespace read comes back empty (registration + // still pending or a provider without schema defaults). + const ctx = new Context() + ctx.provide('settings', { register: () => () => {}, get: () => undefined } as never) + await ctx.plugin({ apply }).await() + expect(scriptText(collect(ctx)[0])).toContain('const preference = "system"') + }) })