Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions cypress/e2e/chat/chat-input.cy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AuthTestUtils } from '../../support/auth-utils';
import { TestTool } from '../../support/page-utils';
import { AddPageSelectors, ModelSelectorSelectors, PageSelectors, SidebarSelectors, ChatSelectors } from '../../support/selectors';
import { AddPageSelectors, ModelSelectorSelectors, PageSelectors, SidebarSelectors, ChatSelectors, byTestId } from '../../support/selectors';
import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config';

describe('Chat Input Tests', () => {
Expand Down Expand Up @@ -34,8 +34,8 @@ describe('Chat Input Tests', () => {
authUtils.signInWithTestUrl(testEmail).then(() => {
cy.url().should('include', '/app');

SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
PageSelectors.items().should('exist', { timeout: 30000 });
SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible');
PageSelectors.items({ timeout: 30000 }).should('exist');
cy.wait(2000);

TestTool.expandSpace();
Expand All @@ -52,17 +52,19 @@ describe('Chat Input Tests', () => {
AddPageSelectors.addAIChatButton().should('be.visible').click();

cy.wait(2000);
ChatSelectors.aiChatContainer({ timeout: 30000 }).should('be.visible');

// Test 1: Format toggle
cy.log('Testing format toggle');
ChatSelectors.formatGroup().then($group => {
if ($group.length > 0) {
cy.get('body').then(($body) => {
if ($body.find(byTestId('chat-format-group')).length > 0) {
ChatSelectors.formatToggle().click();
ChatSelectors.formatGroup().should('not.exist');
}
});

ChatSelectors.formatToggle().should('be.visible').click();
ChatSelectors.formatToggle({ timeout: 30000 }).should('be.visible');
ChatSelectors.formatToggle().click();
ChatSelectors.formatGroup().should('exist');
ChatSelectors.formatGroup().find('button').should('have.length.at.least', 4);
ChatSelectors.formatToggle().click();
Expand Down Expand Up @@ -111,8 +113,8 @@ describe('Chat Input Tests', () => {
authUtils.signInWithTestUrl(testEmail).then(() => {
cy.url().should('include', '/app');

SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
PageSelectors.items().should('exist', { timeout: 30000 });
SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible');
PageSelectors.items({ timeout: 30000 }).should('exist');
cy.wait(2000);

TestTool.expandSpace();
Expand All @@ -129,6 +131,7 @@ describe('Chat Input Tests', () => {
AddPageSelectors.addAIChatButton().should('be.visible').click();

cy.wait(3000); // Wait for chat to fully load
ChatSelectors.aiChatContainer({ timeout: 30000 }).should('be.visible');

// Mock API endpoints with more realistic responses
cy.intercept('POST', '**/api/chat/**/message/question', (req) => {
Expand Down
37 changes: 8 additions & 29 deletions cypress/e2e/chat/create-ai-chat.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,18 @@ describe('AI Chat Creation and Navigation Tests', () => {
cy.get('body', { timeout: 30000 }).should('not.contain', 'Welcome!');

// Wait for the sidebar to be visible (indicates app is loaded)
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible');

// Wait for at least one page to exist in the sidebar
PageSelectors.names().should('exist', { timeout: 30000 });
PageSelectors.names({ timeout: 30000 }).should('exist');

// Additional wait for stability
cy.wait(2000);

// Now wait for the new page button to be available
testLog.info( 'Looking for new page button...');
PageSelectors.newPageButton()
.should('exist', { timeout: 20000 })
PageSelectors.newPageButton({ timeout: 20000 })
.should('exist')
.then(() => {
testLog.info( 'New page button found!');
});
Expand Down Expand Up @@ -121,33 +121,12 @@ describe('AI Chat Creation and Navigation Tests', () => {
testLog.info( '=== Step 4: Verifying AI Chat page loaded ===');

// Check that the URL contains a view ID (indicating navigation to chat)
cy.url().should('match', /\/app\/[a-f0-9-]+\/[a-f0-9-]+/, { timeout: 10000 });
cy.url({ timeout: 20000 }).should('match', /\/app\/[^/]+\/[^/?#]+/);
testLog.info( '✓ Navigated to AI Chat page');

// Check if the AI Chat container exists (but don't fail if it doesn't load immediately)
ChatSelectors.aiChatContainer().then($container => {
if ($container.length > 0) {
testLog.info( '✓ AI Chat container exists');
} else {
testLog.info( 'AI Chat container not immediately visible, checking for navigation success...');
}
});

// Wait a bit for the chat to fully load
cy.wait(2000);

// Check for AI Chat specific elements (the chat interface)
// The AI chat library loads its own components
cy.get('body').then($body => {
ChatSelectors.aiChatContainer().then($container => {
const hasChatElements = $body.find('.ai-chat').length > 0 || $container.length > 0;
if (hasChatElements) {
testLog.info( '✓ AI Chat interface loaded');
} else {
testLog.info( 'Warning: AI Chat elements not immediately visible, but container exists');
}
});
});
// Verify AI Chat container renders (chat UI may load async after container is mounted)
ChatSelectors.aiChatContainer({ timeout: 30000 }).should('be.visible');
testLog.info( '✓ AI Chat container exists');

// Verify no error messages are displayed
cy.get('body').then($body => {
Expand Down
130 changes: 130 additions & 0 deletions cypress/e2e/database/database-container-add-linked-views.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { v4 as uuidv4 } from 'uuid';

import { AuthTestUtils } from '../../support/auth-utils';
import { closeModalsIfOpen, testLog } from '../../support/test-helpers';
import {
AddPageSelectors,
DatabaseGridSelectors,
DatabaseViewSelectors,
DropdownSelectors,
PageSelectors,
SpaceSelectors,
waitForReactUpdate,
} from '../../support/selectors';

describe('Database Container - Add Linked Views via Tab Bar', () => {
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
const dbName = 'New Database';
const spaceName = 'General';

const ensureSpaceExpanded = (name: string) => {
SpaceSelectors.itemByName(name).should('exist');
SpaceSelectors.itemByName(name).then(($space) => {
const expandedIndicator = $space.find('[data-testid="space-expanded"]');
const isExpanded = expandedIndicator.attr('data-expanded') === 'true';

if (!isExpanded) {
SpaceSelectors.itemByName(name).find('[data-testid="space-name"]').click({ force: true });
waitForReactUpdate(500);
}
});
};

const ensurePageExpanded = (name: string) => {
PageSelectors.itemByName(name).should('exist');
PageSelectors.itemByName(name).then(($page) => {
const isExpanded = $page.find('[data-testid="outline-toggle-collapse"]').length > 0;

if (!isExpanded) {
PageSelectors.itemByName(name).find('[data-testid="outline-toggle-expand"]').first().click({ force: true });
waitForReactUpdate(500);
}
});
};

const addViewViaPlus = (viewTypeLabel: 'Board' | 'Calendar') => {
DatabaseViewSelectors.addViewButton().should('be.visible').scrollIntoView().click({ force: true });

DropdownSelectors.content({ timeout: 10000 })
.should('be.visible')
.within(() => {
cy.contains('[role="menuitem"]', viewTypeLabel).should('be.visible').click({ force: true });
});

waitForReactUpdate(3000);
cy.contains('[data-testid^="view-tab-"]', viewTypeLabel, { timeout: 20000 })
.should('exist')
.and('have.attr', 'data-state', 'active');
};

beforeEach(() => {
cy.on('uncaught:exception', (err) => {
if (
err.message.includes('Minified React error') ||
err.message.includes('View not found') ||
err.message.includes('No workspace or service found') ||
err.message.includes('ResizeObserver loop')
) {
return false;
}
return true;
});

cy.viewport(1280, 720);
});

it('adds Board and Calendar views and reflects them in tabs and sidebar', () => {
const testEmail = generateRandomEmail();

testLog.testStart('Database container add linked views');
testLog.info(`Test email: ${testEmail}`);

cy.visit('/login', { failOnStatusCode: false });
cy.wait(2000);

const authUtils = new AuthTestUtils();
authUtils.signInWithTestUrl(testEmail).then(() => {
cy.url({ timeout: 30000 }).should('include', '/app');
cy.wait(3000);

// 1) Create standalone Grid database (container + first child view)
testLog.step(1, 'Create standalone Grid database');
AddPageSelectors.inlineAddButton().first().click({ force: true });
waitForReactUpdate(1000);
AddPageSelectors.addGridButton().should('be.visible').click({ force: true });
// The grid container may exist before it has a stable height; wait for cells to render.
cy.wait(7000);
DatabaseGridSelectors.grid().should('exist');
DatabaseGridSelectors.cells().should('have.length.greaterThan', 0);

// Scenario 4 parity: tab bar "+" adds linked views to the same container
testLog.step(2, 'Verify initial tabs (single tab)');
DatabaseViewSelectors.viewTab()
.should('have.length', 1)
.first()
.should('have.attr', 'data-state', 'active')
.and('contain.text', dbName);

testLog.step(3, 'Add Board view via tab bar "+"');
addViewViaPlus('Board');
DatabaseViewSelectors.viewTab().should('have.length', 2);

testLog.step(4, 'Add Calendar view via tab bar "+"');
addViewViaPlus('Calendar');
DatabaseViewSelectors.viewTab().should('have.length', 3);

testLog.step(5, 'Verify sidebar container children updated');
closeModalsIfOpen();
ensureSpaceExpanded(spaceName);
ensurePageExpanded(dbName);

PageSelectors.itemByName(dbName).within(() => {
PageSelectors.nameContaining('Board').should('be.visible');
PageSelectors.nameContaining('Calendar').should('be.visible');
PageSelectors.items().should('have.length', 3);
});

testLog.testEnd('Database container add linked views');
});
});
});
129 changes: 129 additions & 0 deletions cypress/e2e/database/database-container-open.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { v4 as uuidv4 } from 'uuid';

import { AuthTestUtils } from '../../support/auth-utils';
import { closeModalsIfOpen, testLog } from '../../support/test-helpers';
import {
AddPageSelectors,
DatabaseGridSelectors,
DatabaseViewSelectors,
ModalSelectors,
PageSelectors,
SpaceSelectors,
waitForReactUpdate,
} from '../../support/selectors';

describe('Database Container Open Behavior', () => {
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
const dbName = 'New Database';
const spaceName = 'General';

const currentViewIdFromUrl = () =>
cy.location('pathname').then((pathname) => {
const maybeId = pathname.split('/').filter(Boolean).pop() || '';
return maybeId;
});

beforeEach(() => {
cy.on('uncaught:exception', (err) => {
if (
err.message.includes('Minified React error') ||
err.message.includes('View not found') ||
err.message.includes('No workspace or service found') ||
err.message.includes('ResizeObserver loop')
) {
return false;
}
return true;
});

cy.viewport(1280, 720);
});

it('opens the first child view when clicking a database container', () => {
const testEmail = generateRandomEmail();
testLog.testStart('Database container opens first child');
testLog.info(`Test email: ${testEmail}`);

cy.visit('/login', { failOnStatusCode: false });
cy.wait(2000);

const authUtils = new AuthTestUtils();
authUtils.signInWithTestUrl(testEmail).then(() => {
cy.url({ timeout: 30000 }).should('include', '/app');
cy.wait(3000);

// Create a standalone database (container + first child view)
testLog.step(1, 'Create standalone Grid database');
AddPageSelectors.inlineAddButton().first().click({ force: true });
waitForReactUpdate(1000);
AddPageSelectors.addGridButton().should('be.visible').click({ force: true });

// Wait for the database UI to appear
// The grid container may exist before it has a stable height; wait for cells to render.
cy.wait(7000);
DatabaseGridSelectors.grid().should('exist');
DatabaseGridSelectors.cells().should('have.length.greaterThan', 0);

// Scenario 1 parity: a newly created container has exactly 1 child view
DatabaseViewSelectors.viewTab()
.should('have.length', 1)
.first()
.should('have.attr', 'data-state', 'active')
.and('contain.text', dbName);

// Ensure sidebar is visible and space expanded
SpaceSelectors.itemByName(spaceName).should('exist');
SpaceSelectors.itemByName(spaceName).then(($space) => {
const expandedIndicator = $space.find('[data-testid="space-expanded"]');
const isExpanded = expandedIndicator.attr('data-expanded') === 'true';

if (!isExpanded) {
SpaceSelectors.itemByName(spaceName).find('[data-testid="space-name"]').click({ force: true });
waitForReactUpdate(500);
}
});

// Capture the currently active viewId (the first child view opened after container creation)
testLog.step(2, 'Capture first child view id');
currentViewIdFromUrl().then((firstChildViewId) => {
expect(firstChildViewId).to.not.equal('');
cy.wrap(firstChildViewId).as('firstChildViewId');
});

// Navigate away to a document page so we can click the container again
testLog.step(3, 'Navigate away to a new document');
closeModalsIfOpen();
AddPageSelectors.inlineAddButton().first().click({ force: true });
waitForReactUpdate(1000);
cy.get('[role="menuitem"]').first().click({ force: true });
waitForReactUpdate(1000);

cy.get('body').then(($body) => {
if ($body.find('[data-testid="new-page-modal"]').length > 0) {
ModalSelectors.newPageModal()
.should('be.visible')
.within(() => {
ModalSelectors.spaceItemInModal().first().click({ force: true });
waitForReactUpdate(500);
cy.contains('button', 'Add').click({ force: true });
});
}
});
waitForReactUpdate(2000);

// Click on the database container in the sidebar and ensure we land on its first child view id
testLog.step(4, 'Click container and verify redirect');
PageSelectors.nameContaining(dbName).first().click({ force: true });

cy.get<string>('@firstChildViewId').then((firstChildViewId) => {
cy.location('pathname', { timeout: 20000 }).should('include', `/${firstChildViewId}`);
DatabaseViewSelectors.viewTab(firstChildViewId).should('have.attr', 'data-state', 'active');
});

DatabaseGridSelectors.grid().should('exist');
DatabaseGridSelectors.cells().should('have.length.greaterThan', 0);

testLog.testEnd('Database container opens first child');
});
});
});
Loading
Loading