import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest'; const { EVENTS_MODULE, API_MODULE, APP_MODULE, COMPONENTS_MODULE, } = vi.hoisted(() => ({ EVENTS_MODULE: new URL('../../../web/comfyui/loras_widget_events.js', import.meta.url).pathname, API_MODULE: new URL('../../../scripts/api.js', import.meta.url).pathname, APP_MODULE: new URL('../../../scripts/app.js', import.meta.url).pathname, COMPONENTS_MODULE: new URL('../../../web/comfyui/loras_widget_components.js', import.meta.url).pathname, })); vi.mock(API_MODULE, () => ({ api: {}, })); vi.mock(APP_MODULE, () => ({ app: {}, })); vi.mock(COMPONENTS_MODULE, () => ({ createMenuItem: vi.fn(), createDropIndicator: vi.fn(), })); describe('LoRA widget drag interactions', () => { beforeEach(() => { document.body.innerHTML = ''; const dragStyle = document.getElementById('lm-lora-shared-styles'); if (dragStyle) { dragStyle.remove(); } }); afterEach(() => { document.body.classList.remove('lm-lora-strength-dragging'); }); it('adjusts a single LoRA strength while syncing collapsed clip strength', async () => { const { handleStrengthDrag } = await import(EVENTS_MODULE); const widget = { value: [ { name: 'Test', strength: 0.5, clipStrength: 0.25, expanded: false }, ], callback: vi.fn(), }; handleStrengthDrag('Test', 0.5, 100, { clientX: 140 }, widget, false); expect(widget.value[0].strength).toBeCloseTo(0.54, 2); expect(widget.value[0].clipStrength).toBeCloseTo(0.54, 2); expect(widget.callback).toHaveBeenCalledWith(widget.value); }); it('applies proportional drag updates to all LoRAs', async () => { const { handleAllStrengthsDrag } = await import(EVENTS_MODULE); const widget = { value: [ { name: 'A', strength: 0.4, clipStrength: 0.4 }, { name: 'B', strength: 0.6, clipStrength: 0.6 }, ], callback: vi.fn(), }; const initialStrengths = [ { modelStrength: 0.4, clipStrength: 0.4 }, { modelStrength: 0.6, clipStrength: 0.6 }, ]; handleAllStrengthsDrag(initialStrengths, 100, { clientX: 160 }, widget); expect(widget.value[0].strength).toBeCloseTo(0.41, 2); expect(widget.value[1].strength).toBeCloseTo(0.62, 2); expect(widget.callback).toHaveBeenCalledWith(widget.value); }); it('initiates drag gestures, updates strength, and clears cursor state on pointerup', async () => { const module = await import(EVENTS_MODULE); const renderSpy = vi.fn(); const previewSpy = { hide: vi.fn() }; const onDragStart = vi.fn(); const onDragEnd = vi.fn(); const dragEl = document.createElement('div'); dragEl.className = 'lm-lora-entry'; document.body.append(dragEl); const widget = { value: [{ name: 'Test', strength: 0.5, clipStrength: 0.5 }], callback: vi.fn(), }; module.initDrag(dragEl, 'Test', widget, false, previewSpy, renderSpy, { onDragStart, onDragEnd, }); dragEl.dispatchEvent(new PointerEvent('pointerdown', { clientX: 50, bubbles: true, pointerId: 1 })); expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(false); expect(onDragStart).not.toHaveBeenCalled(); dragEl.dispatchEvent(new PointerEvent('pointermove', { clientX: 70, bubbles: true, pointerId: 1 })); expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(true); expect(onDragStart).toHaveBeenCalledTimes(1); expect(previewSpy.hide).toHaveBeenCalled(); expect(widget.value[0].strength).not.toBe(0.5); expect(widget.callback).not.toHaveBeenCalled(); expect(renderSpy).not.toHaveBeenCalled(); dragEl.dispatchEvent(new PointerEvent('pointerup', { pointerId: 1 })); expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(false); expect(onDragEnd).toHaveBeenCalledTimes(1); expect(renderSpy).toHaveBeenCalledWith(widget.value, widget); }); it('deletes the selected LoRA when backspace is pressed outside of strength inputs', async () => { const { handleKeyboardNavigation } = await import(EVENTS_MODULE); const widget = { value: [{ name: 'Test', strength: 0.5, clipStrength: 0.5 }], callback: vi.fn(), }; const preventDefault = vi.fn(); const renderSpy = vi.fn(); const selectLora = vi.fn(); const event = { key: 'Backspace', preventDefault, ctrlKey: false, metaKey: false, target: document.createElement('div'), }; const handled = handleKeyboardNavigation(event, 'Test', widget, renderSpy, selectLora); expect(handled).toBe(true); expect(preventDefault).toHaveBeenCalled(); expect(widget.value).toEqual([]); expect(widget.callback).toHaveBeenCalledWith([]); expect(renderSpy).toHaveBeenCalledWith([], widget); expect(selectLora).toHaveBeenCalledWith(null); }); it('keeps the LoRA entry when editing strength input and pressing backspace', async () => { const { handleKeyboardNavigation } = await import(EVENTS_MODULE); const strengthInput = document.createElement('input'); strengthInput.classList.add('lm-lora-strength-input'); const widget = { value: [{ name: 'Test', strength: 0.5, clipStrength: 0.5 }], callback: vi.fn(), }; const preventDefault = vi.fn(); const renderSpy = vi.fn(); const selectLora = vi.fn(); const event = { key: 'Backspace', preventDefault, ctrlKey: false, metaKey: false, target: strengthInput, }; const handled = handleKeyboardNavigation(event, 'Test', widget, renderSpy, selectLora); expect(handled).toBe(false); expect(preventDefault).not.toHaveBeenCalled(); expect(widget.value).toHaveLength(1); expect(widget.callback).not.toHaveBeenCalled(); expect(renderSpy).not.toHaveBeenCalled(); expect(selectLora).not.toHaveBeenCalled(); }); });