mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
@@ -23,6 +23,36 @@ export default defineConfig({
|
||||
viewportWidth: 1440,
|
||||
viewportHeight: 900,
|
||||
setupNodeEvents(on, config) {
|
||||
// Configure browser launch options
|
||||
on('before:browser:launch', (browser, launchOptions) => {
|
||||
if (browser.name === 'chrome' || browser.family === 'chromium') {
|
||||
// Remove fullscreen and kiosk related flags
|
||||
launchOptions.args = launchOptions.args.filter(arg => {
|
||||
return !arg.includes('--start-fullscreen') &&
|
||||
!arg.includes('--start-maximized') &&
|
||||
!arg.includes('--kiosk') &&
|
||||
!arg.includes('--app') &&
|
||||
!arg.includes('--auto-open-devtools-for-tabs');
|
||||
});
|
||||
|
||||
// Add flags to ensure windowed mode
|
||||
// Position window at bottom of screen (adjust based on your screen height)
|
||||
// For a 1080p screen (1920x1080), positioning at y=180 leaves the window at bottom
|
||||
// For a 1440p screen (2560x1440), positioning at y=540 leaves the window at bottom
|
||||
launchOptions.args.push('--window-size=1440,900');
|
||||
launchOptions.args.push('--window-position=0,180');
|
||||
launchOptions.args.push('--disable-gpu-sandbox');
|
||||
launchOptions.args.push('--no-sandbox');
|
||||
launchOptions.args.push('--disable-dev-shm-usage');
|
||||
|
||||
// Force disable fullscreen
|
||||
launchOptions.args.push('--force-device-scale-factor=1');
|
||||
|
||||
console.log('Chrome launch args:', launchOptions.args);
|
||||
}
|
||||
|
||||
return launchOptions;
|
||||
});
|
||||
// Override baseUrl if CYPRESS_BASE_URL is set
|
||||
if (process.env.CYPRESS_BASE_URL) {
|
||||
config.baseUrl = process.env.CYPRESS_BASE_URL;
|
||||
|
||||
275
cypress/e2e/chat/chat-input.cy.ts
Normal file
275
cypress/e2e/chat/chat-input.cy.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthTestUtils } from '../../support/auth-utils';
|
||||
import { TestTool } from '../../support/page-utils';
|
||||
import { PageSelectors, SidebarSelectors } from '../../support/selectors';
|
||||
|
||||
describe('Chat Input Tests', () => {
|
||||
const APPFLOWY_BASE_URL = Cypress.env('APPFLOWY_BASE_URL');
|
||||
const APPFLOWY_GOTRUE_BASE_URL = Cypress.env('APPFLOWY_GOTRUE_BASE_URL');
|
||||
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
|
||||
let testEmail: string;
|
||||
|
||||
before(() => {
|
||||
cy.task(
|
||||
'log',
|
||||
`Test Environment Configuration:\n - APPFLOWY_BASE_URL: ${APPFLOWY_BASE_URL}\n - APPFLOWY_GOTRUE_BASE_URL: ${APPFLOWY_GOTRUE_BASE_URL}`
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
testEmail = generateRandomEmail();
|
||||
});
|
||||
|
||||
it('tests chat input UI controls', () => {
|
||||
cy.on('uncaught:exception', (err: Error) => {
|
||||
if (err.message.includes('No workspace or service found') ||
|
||||
err.message.includes('View not found') ||
|
||||
err.message.includes('WebSocket') ||
|
||||
err.message.includes('connection') ||
|
||||
err.message.includes('Failed to load models') ||
|
||||
err.message.includes('Minified React error')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
cy.visit('/login', { failOnStatusCode: false });
|
||||
cy.wait(2000);
|
||||
|
||||
const authUtils = new AuthTestUtils();
|
||||
authUtils.signInWithTestUrl(testEmail).then(() => {
|
||||
cy.url().should('include', '/app');
|
||||
|
||||
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
|
||||
PageSelectors.items().should('exist', { timeout: 30000 });
|
||||
cy.wait(2000);
|
||||
|
||||
TestTool.expandSpace();
|
||||
cy.wait(1000);
|
||||
|
||||
PageSelectors.items()
|
||||
.first()
|
||||
.trigger('mouseenter', { force: true })
|
||||
.trigger('mouseover', { force: true });
|
||||
|
||||
cy.wait(1000);
|
||||
|
||||
cy.get('[data-testid="inline-add-page"]').first().click({ force: true });
|
||||
cy.get('[data-testid="add-ai-chat-button"]').should('be.visible').click();
|
||||
|
||||
cy.wait(2000);
|
||||
|
||||
// Test 1: Format toggle
|
||||
cy.log('Testing format toggle');
|
||||
cy.get('body').then($body => {
|
||||
if ($body.find('[data-testid="chat-format-group"]').length > 0) {
|
||||
cy.get('[data-testid="chat-input-format-toggle"]').click();
|
||||
cy.get('[data-testid="chat-format-group"]').should('not.exist');
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('[data-testid="chat-input-format-toggle"]').should('be.visible').click();
|
||||
cy.get('[data-testid="chat-format-group"]').should('exist');
|
||||
cy.get('[data-testid="chat-format-group"] button').should('have.length.at.least', 4);
|
||||
cy.get('[data-testid="chat-input-format-toggle"]').click();
|
||||
cy.get('[data-testid="chat-format-group"]').should('not.exist');
|
||||
|
||||
// Test 2: Model selector
|
||||
cy.log('Testing model selector');
|
||||
cy.get('[data-testid="model-selector-button"]').should('be.visible').click();
|
||||
cy.get('[data-testid^="model-option-"]').should('exist');
|
||||
cy.get('body').click(0, 0);
|
||||
|
||||
// Test 3: Browse prompts
|
||||
cy.log('Testing browse prompts');
|
||||
cy.get('[data-testid="chat-input-browse-prompts"]').click();
|
||||
cy.get('[role="dialog"]').should('exist');
|
||||
cy.get('[role="dialog"]').contains('Browse prompts').should('be.visible');
|
||||
cy.get('body').type('{esc}');
|
||||
cy.get('[role="dialog"]').should('not.exist');
|
||||
|
||||
// Test 4: Related views
|
||||
cy.log('Testing related views');
|
||||
cy.get('[data-testid="chat-input-related-views"]').click();
|
||||
cy.get('[data-testid="chat-related-views-popover"]').should('be.visible');
|
||||
cy.get('body').type('{esc}');
|
||||
cy.get('[data-testid="chat-related-views-popover"]').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('tests chat input message handling', () => {
|
||||
cy.on('uncaught:exception', (err: Error) => {
|
||||
if (err.message.includes('No workspace or service found') ||
|
||||
err.message.includes('View not found') ||
|
||||
err.message.includes('WebSocket') ||
|
||||
err.message.includes('connection') ||
|
||||
err.message.includes('Failed to load models') ||
|
||||
err.message.includes('Minified React error')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
cy.visit('/login', { failOnStatusCode: false });
|
||||
cy.wait(2000);
|
||||
|
||||
const authUtils = new AuthTestUtils();
|
||||
authUtils.signInWithTestUrl(testEmail).then(() => {
|
||||
cy.url().should('include', '/app');
|
||||
|
||||
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
|
||||
PageSelectors.items().should('exist', { timeout: 30000 });
|
||||
cy.wait(2000);
|
||||
|
||||
TestTool.expandSpace();
|
||||
cy.wait(1000);
|
||||
|
||||
PageSelectors.items()
|
||||
.first()
|
||||
.trigger('mouseenter', { force: true })
|
||||
.trigger('mouseover', { force: true });
|
||||
|
||||
cy.wait(1000);
|
||||
|
||||
cy.get('[data-testid="inline-add-page"]').first().click({ force: true });
|
||||
cy.get('[data-testid="add-ai-chat-button"]').should('be.visible').click();
|
||||
|
||||
cy.wait(3000); // Wait for chat to fully load
|
||||
|
||||
// Mock API endpoints with more realistic responses
|
||||
cy.intercept('POST', '**/api/chat/**/message/question', (req) => {
|
||||
req.reply({
|
||||
statusCode: 200,
|
||||
body: {
|
||||
code: 0,
|
||||
data: {
|
||||
message_id: Date.now().toString(),
|
||||
content: req.body.content || 'Test message',
|
||||
chat_id: 'test-chat-id',
|
||||
},
|
||||
message: 'success',
|
||||
},
|
||||
});
|
||||
}).as('submitQuestion');
|
||||
|
||||
cy.intercept('POST', '**/api/chat/**/answer/stream', (req) => {
|
||||
req.reply({
|
||||
statusCode: 200,
|
||||
body: 'data: {"content":"Test response","type":"message"}\n\n',
|
||||
headers: {
|
||||
'content-type': 'text/event-stream',
|
||||
},
|
||||
});
|
||||
}).as('streamAnswer');
|
||||
|
||||
// Get the textarea using a more flexible selector
|
||||
const getTextarea = () => cy.get('textarea').first();
|
||||
|
||||
// Test 1: Check textarea exists and is ready
|
||||
cy.log('Testing textarea availability');
|
||||
getTextarea().should('exist').and('be.visible');
|
||||
|
||||
// Test 2: Keyboard interactions with better waits
|
||||
cy.log('Testing keyboard interactions');
|
||||
getTextarea()
|
||||
.should('not.be.disabled')
|
||||
.clear()
|
||||
.type('First line')
|
||||
.should((el) => {
|
||||
expect(el.val()).to.include('First line');
|
||||
});
|
||||
|
||||
cy.wait(500);
|
||||
|
||||
getTextarea()
|
||||
.type('{shift+enter}Second line')
|
||||
.should((el) => {
|
||||
expect(el.val()).to.include('First line\nSecond line');
|
||||
});
|
||||
|
||||
// Test 3: Textarea auto-resize
|
||||
cy.log('Testing auto-resize');
|
||||
getTextarea().then($textarea => {
|
||||
const initialHeight = $textarea.height();
|
||||
cy.wrap($textarea)
|
||||
.clear()
|
||||
.type('Line 1{shift+enter}Line 2{shift+enter}Line 3{shift+enter}Line 4');
|
||||
|
||||
cy.wait(500);
|
||||
|
||||
getTextarea().then($resized => {
|
||||
const newHeight = $resized.height();
|
||||
cy.log(`Initial height: ${initialHeight}, New height: ${newHeight}`);
|
||||
expect(newHeight).to.be.at.least(initialHeight!);
|
||||
});
|
||||
});
|
||||
|
||||
// Test 4: Button states with better selectors
|
||||
cy.log('Testing button states');
|
||||
getTextarea().clear();
|
||||
cy.wait(500);
|
||||
|
||||
// Check send button is disabled when empty
|
||||
cy.get('[data-testid="chat-input-send"]').should('exist');
|
||||
cy.get('[data-testid="chat-input-send"]').then($button => {
|
||||
// Button might be disabled via attribute or opacity
|
||||
const isDisabled = $button.prop('disabled') || $button.css('opacity') === '0.5';
|
||||
expect(isDisabled).to.be.true;
|
||||
});
|
||||
|
||||
// Type message and check button becomes enabled
|
||||
getTextarea().type('Test message');
|
||||
cy.wait(500);
|
||||
|
||||
cy.get('[data-testid="chat-input-send"]').then($button => {
|
||||
const isDisabled = $button.prop('disabled') || $button.css('opacity') === '0.5';
|
||||
expect(isDisabled).to.be.false;
|
||||
});
|
||||
|
||||
// Test 5: Message sending with proper waits
|
||||
cy.log('Testing message sending');
|
||||
getTextarea().clear().type('Hello world');
|
||||
cy.wait(500);
|
||||
|
||||
cy.get('[data-testid="chat-input-send"]').click();
|
||||
cy.wait('@submitQuestion', { timeout: 10000 });
|
||||
|
||||
// Wait for textarea to be ready again
|
||||
cy.wait(2000);
|
||||
|
||||
getTextarea()
|
||||
.should('exist')
|
||||
.and('be.visible')
|
||||
.and('have.value', '');
|
||||
|
||||
// Test 6: Special characters with simpler approach
|
||||
cy.log('Testing special characters');
|
||||
cy.wait(1000);
|
||||
|
||||
const specialMessage = 'Test with special: @#$%';
|
||||
getTextarea()
|
||||
.should('not.be.disabled')
|
||||
.clear()
|
||||
.type(specialMessage);
|
||||
|
||||
cy.wait(500);
|
||||
|
||||
getTextarea().should((el) => {
|
||||
expect(el.val()).to.equal(specialMessage);
|
||||
});
|
||||
|
||||
// Test 7: Enter sends message with better handling
|
||||
cy.log('Testing Enter key');
|
||||
getTextarea().clear();
|
||||
cy.wait(500);
|
||||
|
||||
getTextarea().type('Quick test{enter}');
|
||||
cy.wait('@submitQuestion', { timeout: 10000 });
|
||||
|
||||
cy.wait(2000);
|
||||
getTextarea()
|
||||
.should('exist')
|
||||
.and('have.value', '');
|
||||
});
|
||||
});
|
||||
});
|
||||
189
cypress/e2e/chat/selection-mode.cy.ts
Normal file
189
cypress/e2e/chat/selection-mode.cy.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthTestUtils } from '../../support/auth-utils';
|
||||
import { TestTool } from '../../support/page-utils';
|
||||
import { PageSelectors, SidebarSelectors } from '../../support/selectors';
|
||||
|
||||
const STUBBED_MESSAGE_ID = 101;
|
||||
const STUBBED_MESSAGE_CONTENT = 'Stubbed AI answer ready for export';
|
||||
|
||||
function setupChatApiStubs() {
|
||||
cy.intercept('GET', '**/api/chat/**/message**', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
code: 0,
|
||||
data: {
|
||||
messages: [
|
||||
{
|
||||
message_id: STUBBED_MESSAGE_ID,
|
||||
author: {
|
||||
author_type: 3,
|
||||
author_uuid: 'assistant',
|
||||
},
|
||||
content: STUBBED_MESSAGE_CONTENT,
|
||||
created_at: new Date().toISOString(),
|
||||
meta_data: [],
|
||||
},
|
||||
],
|
||||
has_more: false,
|
||||
total: 1,
|
||||
},
|
||||
message: 'success',
|
||||
},
|
||||
}).as('getChatMessages');
|
||||
|
||||
cy.intercept('GET', '**/api/chat/**/settings**', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
code: 0,
|
||||
data: {
|
||||
rag_ids: [],
|
||||
metadata: {
|
||||
ai_model: 'Auto',
|
||||
},
|
||||
},
|
||||
message: 'success',
|
||||
},
|
||||
}).as('getChatSettings');
|
||||
|
||||
cy.intercept('PATCH', '**/api/chat/**/settings**', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
code: 0,
|
||||
message: 'success',
|
||||
},
|
||||
}).as('updateChatSettings');
|
||||
|
||||
cy.intercept('GET', '**/api/ai/**/model/list**', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
code: 0,
|
||||
data: {
|
||||
models: [
|
||||
{
|
||||
name: 'Auto',
|
||||
metadata: { is_default: true, desc: 'Automatically select an AI model' },
|
||||
},
|
||||
{
|
||||
name: 'E2E Test Model',
|
||||
provider: 'Test Provider',
|
||||
metadata: { is_default: false, desc: 'Stubbed model for testing' },
|
||||
},
|
||||
],
|
||||
},
|
||||
message: 'success',
|
||||
},
|
||||
}).as('getModelList');
|
||||
|
||||
cy.intercept('GET', '**/api/chat/**/**/related_question**', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
code: 0,
|
||||
data: {
|
||||
message_id: `${STUBBED_MESSAGE_ID}`,
|
||||
items: [],
|
||||
},
|
||||
message: 'success',
|
||||
},
|
||||
}).as('getRelatedQuestions');
|
||||
}
|
||||
|
||||
describe('Chat Selection Mode Tests', () => {
|
||||
const APPFLOWY_BASE_URL = Cypress.env('APPFLOWY_BASE_URL');
|
||||
const APPFLOWY_GOTRUE_BASE_URL = Cypress.env('APPFLOWY_GOTRUE_BASE_URL');
|
||||
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
|
||||
let testEmail: string;
|
||||
|
||||
before(() => {
|
||||
cy.task('log', `Test Environment Configuration:\n - APPFLOWY_BASE_URL: ${APPFLOWY_BASE_URL}\n - APPFLOWY_GOTRUE_BASE_URL: ${APPFLOWY_GOTRUE_BASE_URL}`);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
testEmail = generateRandomEmail();
|
||||
setupChatApiStubs();
|
||||
});
|
||||
|
||||
it('enables message selection mode and toggles message selection', () => {
|
||||
cy.on('uncaught:exception', (err: Error) => {
|
||||
if (err.message.includes('No workspace or service found')) {
|
||||
return false;
|
||||
}
|
||||
if (err.message.includes('View not found')) {
|
||||
return false;
|
||||
}
|
||||
if (err.message.includes('WebSocket') || err.message.includes('connection')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
cy.visit('/login', { failOnStatusCode: false });
|
||||
cy.wait(2000);
|
||||
|
||||
const authUtils = new AuthTestUtils();
|
||||
authUtils.signInWithTestUrl(testEmail).then(() => {
|
||||
cy.url().should('include', '/app');
|
||||
|
||||
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
|
||||
PageSelectors.items().should('exist', { timeout: 30000 });
|
||||
cy.wait(2000);
|
||||
|
||||
TestTool.expandSpace();
|
||||
cy.wait(1000);
|
||||
|
||||
PageSelectors.items()
|
||||
.first()
|
||||
.as('firstSidebarPage');
|
||||
|
||||
cy.get('@firstSidebarPage')
|
||||
.trigger('mouseenter', { force: true })
|
||||
.trigger('mouseover', { force: true });
|
||||
|
||||
cy.wait(1000);
|
||||
|
||||
cy.get('[data-testid="inline-add-page"]').first().click({ force: true });
|
||||
|
||||
cy.get('[data-testid="add-ai-chat-button"]').should('be.visible').click();
|
||||
|
||||
cy.wait('@getChatSettings');
|
||||
cy.wait('@getModelList');
|
||||
cy.wait('@getChatMessages');
|
||||
|
||||
cy.contains(STUBBED_MESSAGE_CONTENT).should('be.visible');
|
||||
|
||||
cy.get('[data-testid="page-more-actions"]').first().click({ force: true });
|
||||
|
||||
cy.get('[role="menu"]').should('exist');
|
||||
|
||||
cy.contains('[role="menuitem"]', 'Add messages to page')
|
||||
.should('exist')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.chat-selections-banner', { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.and('contain.text', 'Select messages');
|
||||
|
||||
cy.get(`[data-message-id="${STUBBED_MESSAGE_ID}"]`).as('firstMessage');
|
||||
|
||||
cy.get('@firstMessage')
|
||||
.find('button.w-4.h-4')
|
||||
.first()
|
||||
.click();
|
||||
|
||||
cy.get('@firstMessage')
|
||||
.find('svg.text-primary')
|
||||
.should('exist');
|
||||
|
||||
cy.get('.chat-selections-banner').should('contain.text', '1 selected');
|
||||
|
||||
cy.get('.chat-selections-banner')
|
||||
.find('button')
|
||||
.last()
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.chat-selections-banner').should('not.exist');
|
||||
cy.get('@firstMessage')
|
||||
.find('button.w-4.h-4')
|
||||
.should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -20,6 +20,8 @@
|
||||
"test:unit:coverage": "cross-env COVERAGE=true jest --coverage",
|
||||
"test:components:coverage": "cross-env COVERAGE=true cypress run --component --browser chrome --headless",
|
||||
"test:cy": "cypress run",
|
||||
"test:cy:chrome": "cypress run --browser chrome --headed",
|
||||
"test:cy:chrome:windowed": "ELECTRON_EXTRA_LAUNCH_ARGS='--window-size=1440,900 --window-position=100,100' cypress run --browser chrome --headed",
|
||||
"test:integration": "cypress run --spec 'cypress/e2e/**/*.cy.ts'",
|
||||
"coverage": "cross-env COVERAGE=true pnpm run test:unit && cross-env COVERAGE=true pnpm run test:components",
|
||||
"generate-tokens": "node scripts/system-token/convert-tokens.cjs",
|
||||
|
||||
@@ -192,18 +192,19 @@ export function ChatInput() {
|
||||
<div className={'flex items-center justify-between gap-4'}>
|
||||
<div className={'flex items-center gap-1'}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={'ghost'}
|
||||
size={'icon'}
|
||||
onClick={() => {
|
||||
setResponseMode(
|
||||
responseMode === ChatInputMode.FormatResponse ? ChatInputMode.Auto : ChatInputMode.FormatResponse
|
||||
);
|
||||
}}
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={'ghost'}
|
||||
size={'icon'}
|
||||
data-testid='chat-input-format-toggle'
|
||||
onClick={() => {
|
||||
setResponseMode(
|
||||
responseMode === ChatInputMode.FormatResponse ? ChatInputMode.Auto : ChatInputMode.FormatResponse
|
||||
);
|
||||
}}
|
||||
>
|
||||
<FormatIcon className='text-icon-secondary h-5 w-5' />
|
||||
</Button>
|
||||
@@ -216,18 +217,19 @@ export function ChatInput() {
|
||||
<ModelSelector className={'h-7'} disabled={questionSending || answerApplying} />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={'ghost'}
|
||||
className={'h-7 text-xs text-text-secondary'}
|
||||
onClick={() => {
|
||||
reloadDatabasePrompts();
|
||||
openModal();
|
||||
}}
|
||||
>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={'ghost'}
|
||||
className={'h-7 text-xs text-text-secondary'}
|
||||
data-testid='chat-input-browse-prompts'
|
||||
onClick={() => {
|
||||
reloadDatabasePrompts();
|
||||
openModal();
|
||||
}}
|
||||
>
|
||||
{t('chat.customPrompt.browsePrompts')}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
@@ -260,6 +262,7 @@ export function ChatInput() {
|
||||
variant={'link'}
|
||||
className={'text-fill-theme-thick p-0'}
|
||||
disabled={!message.trim() || disabled}
|
||||
data-testid='chat-input-send'
|
||||
>
|
||||
{questionSending ? (
|
||||
<LoadingDots />
|
||||
@@ -273,4 +276,4 @@ export function ChatInput() {
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ export function RelatedViews() {
|
||||
size='sm'
|
||||
className='gap-0.5 px-1.5 text-sm text-text-secondary'
|
||||
variant={'ghost'}
|
||||
data-testid='chat-input-related-views'
|
||||
>
|
||||
<DocIcon className='h-5 w-5 text-icon-secondary' />
|
||||
{length}
|
||||
@@ -99,6 +100,7 @@ export function RelatedViews() {
|
||||
initial="hidden"
|
||||
animate={open ? "visible" : "exit"}
|
||||
className={'h-fit min-h-[200px] max-h-[360px] w-[300px] flex flex-col'}
|
||||
data-testid='chat-related-views-popover'
|
||||
>
|
||||
<SearchInput
|
||||
className='m-2'
|
||||
@@ -126,4 +128,3 @@ export function RelatedViews() {
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ export function FormatGroup({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'flex gap-2 items-center'}>
|
||||
<div className={'flex gap-2 items-center'} data-testid='chat-format-group'>
|
||||
<div className={'flex gap-1'}>
|
||||
{renderGroup(actions, false)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user