import { nextTick, ref } from 'vue' import { describe, expect, it, vi } from 'vitest' import { defineDoseDesignControlModel } from '../../composables/useControlSchema' import { usePluginWorkspaceControls } from '../../components/PluginWorkspaceView.controls' import type { ControlComponentBindingsConfig, ControlComponentPropsByIdMap, ControlComponentPropsMap, ControlSchema, } from '../../composables/useControlSchema' interface CreateControlsOptions { controls?: ControlSchema modelValue?: () => Record | undefined values?: () => Record | undefined componentProps?: () => ControlComponentPropsMap | undefined componentPropsById?: () => ControlComponentPropsByIdMap | undefined componentBindings?: () => ControlComponentBindingsConfig | undefined emitValues?: (values: Record) => void } function createControls({ controls, modelValue, values, componentProps, componentPropsById, componentBindings, emitValues = vi.fn(), }: CreateControlsOptions = {}) { return usePluginWorkspaceControls({ model: () => undefined, controls: () => controls, controlOptions: () => ({}), modelValue: modelValue ?? (() => undefined), values: values ?? (() => undefined), componentBindings: componentBindings ?? (() => undefined), componentProps: componentProps ?? (() => undefined), componentPropsById: componentPropsById ?? (() => undefined), emitValues, }) } describe('PluginWorkspaceView control state helpers', () => { it('merges control defaults with external values and resyncs when values change', async () => { const externalValues = ref | undefined>({ threshold: 0.2 }) const state = createControls({ controls: { threshold: { label: 'Threshold', default: 0.05 }, mode: { label: 'Mode', default: 'fast' }, }, modelValue: () => externalValues.value, }) expect(state.internalControlValues).toEqual({ threshold: 0.2, mode: 'fast', }) externalValues.value = { threshold: 0.8 } await nextTick() expect(state.internalControlValues).toEqual({ threshold: 0.8, mode: 'fast', }) }) it('emits updates from sidebar form values without dropping generated defaults', () => { const emitValues = vi.fn() const state = createControls({ controls: { threshold: { label: 'Threshold', default: 0.05 }, mode: { label: 'Mode', default: 'fast' }, }, emitValues, }) state.handleControlValuesUpdate({ threshold: 0.4 }) expect(state.internalControlValues).toEqual({ threshold: 0.4, mode: 'fast', }) expect(emitValues).toHaveBeenCalledWith({ threshold: 0.4, mode: 'fast', }) }) it('derives component props and bindings from one internal value model', () => { const state = createControls({ controls: { threshold: { label: 'Threshold', default: 0.05 }, mode: { label: 'Mode', default: 'fast' }, }, componentProps: () => ['threshold'], componentPropsById: () => ({ chart: ['threshold', 'mode'], }), componentBindings: () => ({ chart: { component: 'ChartContainer', props: ['threshold'], }, }), }) expect(state.resolvedComponentProps.value).toEqual({ threshold: 0.05 }) expect(state.resolvedComponentPropsById.value).toEqual({ chart: { threshold: 0.05, mode: 'fast' }, }) expect(state.resolvedComponentBindingsById.value.chart).toMatchObject({ component: 'ChartContainer', props: { threshold: 0.05 }, }) }) it('uses compact control model navigation and binding defaults', () => { const model = defineDoseDesignControlModel({ selectedWells: ['A1', 'A2'], includeMolecularWeight: true, }) const state = usePluginWorkspaceControls({ model: () => model, controls: () => undefined, controlOptions: () => ({}), modelValue: () => ({ selectedWells: ['C1'], plateFormat: 384, doseMode: 'dilution', disabled: true, molecularWeight: 450, }), values: () => undefined, componentBindings: () => undefined, componentProps: () => undefined, componentPropsById: () => undefined, emitValues: vi.fn(), }) expect(state.generatedPillNav.value.map(item => item.id)).toEqual(['design']) expect(state.generatedDefaultView.value).toBe('design') expect(state.resolvedComponentBindingsById.value.plate.props).toMatchObject({ modelValue: ['C1'], format: 384, disabled: true, }) expect(state.resolvedComponentPropsById.value.dose).toMatchObject({ mode: 'dilution', targetWells: ['C1'], disabled: true, molecularWeight: 450, }) }) })