/** * Targets the remaining uncovered branches in widget-store.ts (75% baseline): * - registerTool no-op optimization when structural properties match * - unregisterTool when widget doesn't exist (early return) * - triggerToolPipeline when widget doesn't exist * - setToolEnabled no-op when state already matches * - setToolEnabled when widget doesn't exist * - executeToolPipeline / executeConfigPipeline cancellation + no-op paths * - disables propagation (cross-tool disabling) * * Complements widget-store.test.ts which covers the happy paths. */ import { describe, it, expect, beforeEach, vi } from 'vitest' import { useWidgetStore, widgetStoreActions } from './widget-store' import type { ToolRegistration } from './types' beforeEach(() => { useWidgetStore.setState({ widgets: {} }) }) function makeTool(overrides: Partial = {}): ToolRegistration { return { id: 'tool-x', type: 'data', order: 10, enabled: true, fn: (d: unknown) => d, ...overrides, } } describe('registerTool no-op optimization', () => { it('updates fn in place when structural properties are unchanged', () => { const id = 'w1' const fn1 = vi.fn((d: unknown) => d) const fn2 = vi.fn((d: unknown) => d) widgetStoreActions.setWidget(id, {}) widgetStoreActions.registerTool(id, makeTool({ fn: fn1 })) const after1 = useWidgetStore.getState().widgets[id]!.registeredTools! const ref1 = after1 widgetStoreActions.registerTool(id, makeTool({ fn: fn2 })) const after2 = useWidgetStore.getState().widgets[id]!.registeredTools! // Reference is preserved (no-op path) expect(after2).toBe(ref1) // But fn was updated via direct mutation expect(after2[0]!.fn).toBe(fn2) }) it('triggers a full store update when order changes', () => { const id = 'w2' widgetStoreActions.setWidget(id, {}) widgetStoreActions.registerTool(id, makeTool({ order: 10 })) const ref1 = useWidgetStore.getState().widgets[id]!.registeredTools! widgetStoreActions.registerTool(id, makeTool({ order: 20 })) const ref2 = useWidgetStore.getState().widgets[id]!.registeredTools! expect(ref2).not.toBe(ref1) expect(ref2[0]!.order).toBe(20) }) it('creates a new widget when the widget does not exist yet', () => { widgetStoreActions.registerTool('w-new', makeTool()) const widget = useWidgetStore.getState().widgets['w-new'] expect(widget).toBeDefined() expect(widget!.registeredTools).toHaveLength(1) }) }) describe('unregisterTool early-return branches', () => { it('is a no-op when the widget does not exist', () => { const before = useWidgetStore.getState().widgets widgetStoreActions.unregisterTool('does-not-exist', 'any-tool') const after = useWidgetStore.getState().widgets expect(after).toBe(before) }) it('removes the tool when both widget and tool exist', () => { widgetStoreActions.setWidget('w3', {}) widgetStoreActions.registerTool('w3', makeTool({ id: 'a' })) widgetStoreActions.registerTool('w3', makeTool({ id: 'b' })) widgetStoreActions.unregisterTool('w3', 'a') const tools = useWidgetStore.getState().widgets.w3!.registeredTools! expect(tools).toHaveLength(1) expect(tools[0]!.id).toBe('b') }) }) describe('triggerToolPipeline early-return branches', () => { it('is a no-op when the widget does not exist', () => { const before = useWidgetStore.getState().widgets widgetStoreActions.triggerToolPipeline('does-not-exist') const after = useWidgetStore.getState().widgets expect(after).toBe(before) }) it('produces a new registeredTools reference when the widget exists', () => { widgetStoreActions.setWidget('w4', {}) widgetStoreActions.registerTool('w4', makeTool()) const ref1 = useWidgetStore.getState().widgets.w4!.registeredTools! widgetStoreActions.triggerToolPipeline('w4') const ref2 = useWidgetStore.getState().widgets.w4!.registeredTools! expect(ref2).not.toBe(ref1) expect(ref2).toEqual(ref1) }) }) describe('setToolEnabled branches', () => { it('is a no-op when the tool is already in the requested enabled state', () => { widgetStoreActions.setWidget('w5', {}) widgetStoreActions.registerTool('w5', makeTool({ enabled: true })) const ref1 = useWidgetStore.getState().widgets.w5!.registeredTools! widgetStoreActions.setToolEnabled('w5', 'tool-x', true) const ref2 = useWidgetStore.getState().widgets.w5!.registeredTools! expect(ref2).toBe(ref1) // no update }) it('updates only the targeted tool when state differs', () => { widgetStoreActions.setWidget('w6', {}) widgetStoreActions.registerTool('w6', makeTool({ id: 'a', enabled: true })) widgetStoreActions.registerTool('w6', makeTool({ id: 'b', enabled: true })) widgetStoreActions.setToolEnabled('w6', 'a', false) const tools = useWidgetStore.getState().widgets.w6!.registeredTools! expect(tools.find((t) => t.id === 'a')!.enabled).toBe(false) expect(tools.find((t) => t.id === 'b')!.enabled).toBe(true) }) it('is a no-op when the widget does not exist', () => { const before = useWidgetStore.getState().widgets widgetStoreActions.setToolEnabled('does-not-exist', 'tool-x', false) const after = useWidgetStore.getState().widgets expect(after).toBe(before) }) }) describe('executeToolPipeline branches', () => { it('returns early when the widget does not exist', async () => { await widgetStoreActions.executeToolPipeline('does-not-exist', { x: 1 }) expect(useWidgetStore.getState().widgets['does-not-exist']).toBeUndefined() }) it('chains tool fns in order and writes the final value to data', async () => { widgetStoreActions.setWidget('w-pipe', {}) widgetStoreActions.registerTool( 'w-pipe', makeTool({ id: 'add-1', order: 10, fn: (d: unknown) => (d as number) + 1, }), ) widgetStoreActions.registerTool( 'w-pipe', makeTool({ id: 'mul-2', order: 20, fn: (d: unknown) => (d as number) * 2, }), ) await widgetStoreActions.executeToolPipeline('w-pipe', 5) const widget = useWidgetStore.getState().widgets['w-pipe']! expect(widget.data).toBe(12) // (5 + 1) * 2 expect(widget.sourceData).toBe(5) }) it('respects the `disables` cross-tool field', async () => { widgetStoreActions.setWidget('w-disables', {}) widgetStoreActions.registerTool( 'w-disables', makeTool({ id: 'enabler', order: 10, enabled: true, disables: ['victim'], fn: (d: unknown) => d, }), ) widgetStoreActions.registerTool( 'w-disables', makeTool({ id: 'victim', order: 20, enabled: true, fn: (d: unknown) => (d as number) + 999, // must NOT run }), ) await widgetStoreActions.executeToolPipeline('w-disables', 1) expect(useWidgetStore.getState().widgets['w-disables']!.data).toBe(1) }) it('swallows errors from individual tools and continues with the current data', async () => { const errorSpy = vi .spyOn(console, 'error') .mockImplementation(() => undefined) widgetStoreActions.setWidget('w-err', {}) widgetStoreActions.registerTool( 'w-err', makeTool({ id: 'thrower', order: 10, fn: () => { throw new Error('boom') }, }), ) widgetStoreActions.registerTool( 'w-err', makeTool({ id: 'after', order: 20, fn: (d: unknown) => (d as number) + 1, }), ) await widgetStoreActions.executeToolPipeline('w-err', 5) // The thrower failed but the chain continued — final value is 5 + 1 expect(useWidgetStore.getState().widgets['w-err']!.data).toBe(6) errorSpy.mockRestore() }) it('skips the store write when data is referentially identical', async () => { widgetStoreActions.setWidget('w-noop', { data: 7, sourceData: 7 }) widgetStoreActions.registerTool( 'w-noop', makeTool({ id: 'identity', fn: (d) => d }), ) // Trigger with the same sourceData → identity fn → final data === existing data const ref1 = useWidgetStore.getState().widgets['w-noop'] await widgetStoreActions.executeToolPipeline('w-noop', 7) const ref2 = useWidgetStore.getState().widgets['w-noop'] expect(ref2).toBe(ref1) }) }) describe('executeConfigPipeline branches', () => { it('returns early when the widget does not exist', async () => { await widgetStoreActions.executeConfigPipeline('does-not-exist', { foo: 1 }) expect(useWidgetStore.getState().widgets['does-not-exist']).toBeUndefined() }) it('runs only config-type tools, in order', async () => { widgetStoreActions.setWidget('w-cfg', {}) widgetStoreActions.registerTool( 'w-cfg', makeTool({ id: 'data-tool', type: 'data', order: 5, fn: (cfg: unknown) => ({ ...(cfg as object), dataRan: true }), }), ) widgetStoreActions.registerTool( 'w-cfg', makeTool({ id: 'cfg-tool-1', type: 'config', order: 10, fn: (cfg: unknown) => ({ ...(cfg as object), cfg1: true }), }), ) widgetStoreActions.registerTool( 'w-cfg', makeTool({ id: 'cfg-tool-2', type: 'config', order: 20, fn: (cfg: unknown) => ({ ...(cfg as object), cfg2: true }), }), ) await widgetStoreActions.executeConfigPipeline('w-cfg', { base: true }) const widget = useWidgetStore.getState().widgets['w-cfg']! // Both config tools ran; the data tool did NOT run as part of config pipeline expect((widget as unknown as { cfg1?: boolean }).cfg1).toBe(true) expect((widget as unknown as { cfg2?: boolean }).cfg2).toBe(true) expect((widget as unknown as { dataRan?: boolean }).dataRan).toBeUndefined() }) })