diff --git a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/__snapshots__/AskAssistantChat.test.ts.snap b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/__snapshots__/AskAssistantChat.test.ts.snap index 3928b6f13be..a70b1db3a10 100644 --- a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/__snapshots__/AskAssistantChat.test.ts.snap +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/__snapshots__/AskAssistantChat.test.ts.snap @@ -89,7 +89,7 @@ exports[`AskAssistantChat > limits maximum input length when maxCharacterLength data-test-id="chat-input" disabled="false" maxlength="100" - maxlinesbeforescroll="6" + maxlinesbeforescroll="10" minlines="1" modelvalue="" placeholder="assistantChat.inputPlaceholder" @@ -323,7 +323,7 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = ` data-test-id="chat-input" disabled="false" maxlength="5000" - maxlinesbeforescroll="6" + maxlinesbeforescroll="10" minlines="1" modelvalue="" placeholder="assistantChat.inputPlaceholder" @@ -425,7 +425,7 @@ exports[`AskAssistantChat > renders default placeholder chat correctly 1`] = ` data-test-id="chat-input" disabled="false" maxlength="5000" - maxlinesbeforescroll="6" + maxlinesbeforescroll="10" minlines="1" modelvalue="" placeholder="assistantChat.inputPlaceholder" @@ -570,7 +570,7 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = ` data-test-id="chat-input" disabled="true" maxlength="5000" - maxlinesbeforescroll="6" + maxlinesbeforescroll="10" minlines="1" modelvalue="" placeholder="assistantChat.inputPlaceholder" @@ -700,7 +700,7 @@ exports[`AskAssistantChat > renders error message correctly with retry button 1` data-test-id="chat-input" disabled="false" maxlength="5000" - maxlinesbeforescroll="6" + maxlinesbeforescroll="10" minlines="1" modelvalue="" placeholder="assistantChat.inputPlaceholder" @@ -830,7 +830,7 @@ exports[`AskAssistantChat > renders message with code snippet 1`] = ` data-test-id="chat-input" disabled="false" maxlength="5000" - maxlinesbeforescroll="6" + maxlinesbeforescroll="10" minlines="1" modelvalue="" placeholder="assistantChat.inputPlaceholder" @@ -960,7 +960,7 @@ exports[`AskAssistantChat > renders streaming chat correctly 1`] = ` data-test-id="chat-input" disabled="false" maxlength="5000" - maxlinesbeforescroll="6" + maxlinesbeforescroll="10" minlines="1" modelvalue="" placeholder="assistantChat.inputPlaceholder" diff --git a/packages/frontend/@n8n/design-system/src/components/N8nPromptInput/N8nPromptInput.test.ts b/packages/frontend/@n8n/design-system/src/components/N8nPromptInput/N8nPromptInput.test.ts index d6d0a18957e..41c34626f2a 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nPromptInput/N8nPromptInput.test.ts +++ b/packages/frontend/@n8n/design-system/src/components/N8nPromptInput/N8nPromptInput.test.ts @@ -968,4 +968,110 @@ describe('N8nPromptInput', () => { expect(disabledContainer).toBeTruthy(); }); }); + + describe('height adjustment optimization', () => { + it('should skip height adjustment when content fits within current height', async () => { + // Mock scrollHeight and clientHeight to simulate content that fits + const originalScrollHeightDescriptor = Object.getOwnPropertyDescriptor( + HTMLTextAreaElement.prototype, + 'scrollHeight', + ); + const originalClientHeightDescriptor = Object.getOwnPropertyDescriptor( + HTMLTextAreaElement.prototype, + 'clientHeight', + ); + + Object.defineProperty(HTMLTextAreaElement.prototype, 'scrollHeight', { + configurable: true, + get(this: HTMLTextAreaElement) { + return 72; // Content height + }, + }); + + Object.defineProperty(HTMLTextAreaElement.prototype, 'clientHeight', { + configurable: true, + get(this: HTMLTextAreaElement) { + return 72; // Container height (content fits perfectly) + }, + }); + + try { + const wrapper = mount(N8nPromptInput, { + props: { + modelValue: 'Line 1\nLine 2\nLine 3\nLine 4', + }, + global: { + stubs: ['N8nCallout', 'N8nScrollArea', 'N8nSendStopButton'], + }, + }); + + // Wait for initial mount to complete + await wrapper.vm.$nextTick(); + + const textarea = wrapper.find('textarea').element as HTMLTextAreaElement; + + // Start spying after mount + const setHeightSpy = vi.spyOn(textarea.style, 'height', 'set'); + + // Type some text that doesn't change the height requirement + await wrapper.setProps({ modelValue: 'Line 1\nLine 2\nLine 3\nLine 4a' }); + + // Wait for the watcher to trigger + await wrapper.vm.$nextTick(); + + // Since content fits, adjustHeight should return early without setting height to '0' + const heightCalls = setHeightSpy.mock.calls; + const hasZeroHeightCall = heightCalls.some((call) => call[0] === '0'); + + // If early return works, there should be no '0' height call after the prop update + expect(hasZeroHeightCall).toBe(false); + + wrapper.unmount(); + } finally { + // Restore original descriptors + if (originalScrollHeightDescriptor) { + Object.defineProperty( + HTMLTextAreaElement.prototype, + 'scrollHeight', + originalScrollHeightDescriptor, + ); + } + if (originalClientHeightDescriptor) { + Object.defineProperty( + HTMLTextAreaElement.prototype, + 'clientHeight', + originalClientHeightDescriptor, + ); + } + } + }); + + it('should use ScrollArea to constrain visible area', () => { + // This test verifies that the component uses N8nScrollArea with max-height + // to constrain the visible area, rather than capping the textarea height itself + const wrapper = mount(N8nPromptInput, { + props: { + modelValue: 'Line1\nLine2', + maxLinesBeforeScroll: 10, + minLines: 2, + }, + global: { + stubs: { + N8nCallout: true, + N8nScrollArea: { + props: ['maxHeight', 'type'], + template: '