Skip to content

Commit 29934c7

Browse files
committed
chore: support database container
1 parent 1042e51 commit 29934c7

File tree

54 files changed

+4754
-1079
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+4754
-1079
lines changed

cypress/e2e/chat/chat-input.cy.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AuthTestUtils } from '../../support/auth-utils';
22
import { TestTool } from '../../support/page-utils';
3-
import { AddPageSelectors, ModelSelectorSelectors, PageSelectors, SidebarSelectors, ChatSelectors } from '../../support/selectors';
3+
import { AddPageSelectors, ModelSelectorSelectors, PageSelectors, SidebarSelectors, ChatSelectors, byTestId } from '../../support/selectors';
44
import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config';
55

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

37-
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
38-
PageSelectors.items().should('exist', { timeout: 30000 });
37+
SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible');
38+
PageSelectors.items({ timeout: 30000 }).should('exist');
3939
cy.wait(2000);
4040

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

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

5657
// Test 1: Format toggle
5758
cy.log('Testing format toggle');
58-
ChatSelectors.formatGroup().then($group => {
59-
if ($group.length > 0) {
59+
cy.get('body').then(($body) => {
60+
if ($body.find(byTestId('chat-format-group')).length > 0) {
6061
ChatSelectors.formatToggle().click();
6162
ChatSelectors.formatGroup().should('not.exist');
6263
}
6364
});
6465

65-
ChatSelectors.formatToggle().should('be.visible').click();
66+
ChatSelectors.formatToggle({ timeout: 30000 }).should('be.visible');
67+
ChatSelectors.formatToggle().click();
6668
ChatSelectors.formatGroup().should('exist');
6769
ChatSelectors.formatGroup().find('button').should('have.length.at.least', 4);
6870
ChatSelectors.formatToggle().click();
@@ -111,8 +113,8 @@ describe('Chat Input Tests', () => {
111113
authUtils.signInWithTestUrl(testEmail).then(() => {
112114
cy.url().should('include', '/app');
113115

114-
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
115-
PageSelectors.items().should('exist', { timeout: 30000 });
116+
SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible');
117+
PageSelectors.items({ timeout: 30000 }).should('exist');
116118
cy.wait(2000);
117119

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

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

133136
// Mock API endpoints with more realistic responses
134137
cy.intercept('POST', '**/api/chat/**/message/question', (req) => {

cypress/e2e/chat/create-ai-chat.cy.ts

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,18 @@ describe('AI Chat Creation and Navigation Tests', () => {
5353
cy.get('body', { timeout: 30000 }).should('not.contain', 'Welcome!');
5454

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

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

6161
// Additional wait for stability
6262
cy.wait(2000);
6363

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

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

127-
// Check if the AI Chat container exists (but don't fail if it doesn't load immediately)
128-
ChatSelectors.aiChatContainer().then($container => {
129-
if ($container.length > 0) {
130-
testLog.info( '✓ AI Chat container exists');
131-
} else {
132-
testLog.info( 'AI Chat container not immediately visible, checking for navigation success...');
133-
}
134-
});
135-
136-
// Wait a bit for the chat to fully load
137-
cy.wait(2000);
138-
139-
// Check for AI Chat specific elements (the chat interface)
140-
// The AI chat library loads its own components
141-
cy.get('body').then($body => {
142-
ChatSelectors.aiChatContainer().then($container => {
143-
const hasChatElements = $body.find('.ai-chat').length > 0 || $container.length > 0;
144-
if (hasChatElements) {
145-
testLog.info( '✓ AI Chat interface loaded');
146-
} else {
147-
testLog.info( 'Warning: AI Chat elements not immediately visible, but container exists');
148-
}
149-
});
150-
});
127+
// Verify AI Chat container renders (chat UI may load async after container is mounted)
128+
ChatSelectors.aiChatContainer({ timeout: 30000 }).should('be.visible');
129+
testLog.info( '✓ AI Chat container exists');
151130

152131
// Verify no error messages are displayed
153132
cy.get('body').then($body => {
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { v4 as uuidv4 } from 'uuid';
2+
3+
import { AuthTestUtils } from '../../support/auth-utils';
4+
import { closeModalsIfOpen, testLog } from '../../support/test-helpers';
5+
import {
6+
AddPageSelectors,
7+
DatabaseGridSelectors,
8+
DatabaseViewSelectors,
9+
DropdownSelectors,
10+
PageSelectors,
11+
SpaceSelectors,
12+
waitForReactUpdate,
13+
} from '../../support/selectors';
14+
15+
describe('Database Container - Add Linked Views via Tab Bar', () => {
16+
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
17+
const dbName = 'New Database';
18+
const spaceName = 'General';
19+
20+
const ensureSpaceExpanded = (name: string) => {
21+
SpaceSelectors.itemByName(name).should('exist');
22+
SpaceSelectors.itemByName(name).then(($space) => {
23+
const expandedIndicator = $space.find('[data-testid="space-expanded"]');
24+
const isExpanded = expandedIndicator.attr('data-expanded') === 'true';
25+
26+
if (!isExpanded) {
27+
SpaceSelectors.itemByName(name).find('[data-testid="space-name"]').click({ force: true });
28+
waitForReactUpdate(500);
29+
}
30+
});
31+
};
32+
33+
const ensurePageExpanded = (name: string) => {
34+
PageSelectors.itemByName(name).should('exist');
35+
PageSelectors.itemByName(name).then(($page) => {
36+
const isExpanded = $page.find('[data-testid="outline-toggle-collapse"]').length > 0;
37+
38+
if (!isExpanded) {
39+
PageSelectors.itemByName(name).find('[data-testid="outline-toggle-expand"]').first().click({ force: true });
40+
waitForReactUpdate(500);
41+
}
42+
});
43+
};
44+
45+
const addViewViaPlus = (viewTypeLabel: 'Board' | 'Calendar') => {
46+
DatabaseViewSelectors.addViewButton().should('be.visible').scrollIntoView().click({ force: true });
47+
48+
DropdownSelectors.content({ timeout: 10000 })
49+
.should('be.visible')
50+
.within(() => {
51+
cy.contains('[role="menuitem"]', viewTypeLabel).should('be.visible').click({ force: true });
52+
});
53+
54+
waitForReactUpdate(3000);
55+
cy.contains('[data-testid^="view-tab-"]', viewTypeLabel, { timeout: 20000 })
56+
.should('exist')
57+
.and('have.attr', 'data-state', 'active');
58+
};
59+
60+
beforeEach(() => {
61+
cy.on('uncaught:exception', (err) => {
62+
if (
63+
err.message.includes('Minified React error') ||
64+
err.message.includes('View not found') ||
65+
err.message.includes('No workspace or service found') ||
66+
err.message.includes('ResizeObserver loop')
67+
) {
68+
return false;
69+
}
70+
return true;
71+
});
72+
73+
cy.viewport(1280, 720);
74+
});
75+
76+
it('adds Board and Calendar views and reflects them in tabs and sidebar', () => {
77+
const testEmail = generateRandomEmail();
78+
79+
testLog.testStart('Database container add linked views');
80+
testLog.info(`Test email: ${testEmail}`);
81+
82+
cy.visit('/login', { failOnStatusCode: false });
83+
cy.wait(2000);
84+
85+
const authUtils = new AuthTestUtils();
86+
authUtils.signInWithTestUrl(testEmail).then(() => {
87+
cy.url({ timeout: 30000 }).should('include', '/app');
88+
cy.wait(3000);
89+
90+
// 1) Create standalone Grid database (container + first child view)
91+
testLog.step(1, 'Create standalone Grid database');
92+
AddPageSelectors.inlineAddButton().first().click({ force: true });
93+
waitForReactUpdate(1000);
94+
AddPageSelectors.addGridButton().should('be.visible').click({ force: true });
95+
// The grid container may exist before it has a stable height; wait for cells to render.
96+
cy.wait(7000);
97+
DatabaseGridSelectors.grid().should('exist');
98+
DatabaseGridSelectors.cells().should('have.length.greaterThan', 0);
99+
100+
// Scenario 4 parity: tab bar "+" adds linked views to the same container
101+
testLog.step(2, 'Verify initial tabs (single tab)');
102+
DatabaseViewSelectors.viewTab()
103+
.should('have.length', 1)
104+
.first()
105+
.should('have.attr', 'data-state', 'active')
106+
.and('contain.text', dbName);
107+
108+
testLog.step(3, 'Add Board view via tab bar "+"');
109+
addViewViaPlus('Board');
110+
DatabaseViewSelectors.viewTab().should('have.length', 2);
111+
112+
testLog.step(4, 'Add Calendar view via tab bar "+"');
113+
addViewViaPlus('Calendar');
114+
DatabaseViewSelectors.viewTab().should('have.length', 3);
115+
116+
testLog.step(5, 'Verify sidebar container children updated');
117+
closeModalsIfOpen();
118+
ensureSpaceExpanded(spaceName);
119+
ensurePageExpanded(dbName);
120+
121+
PageSelectors.itemByName(dbName).within(() => {
122+
PageSelectors.nameContaining('Board').should('be.visible');
123+
PageSelectors.nameContaining('Calendar').should('be.visible');
124+
PageSelectors.items().should('have.length', 3);
125+
});
126+
127+
testLog.testEnd('Database container add linked views');
128+
});
129+
});
130+
});
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { v4 as uuidv4 } from 'uuid';
2+
3+
import { AuthTestUtils } from '../../support/auth-utils';
4+
import { closeModalsIfOpen, testLog } from '../../support/test-helpers';
5+
import {
6+
AddPageSelectors,
7+
DatabaseGridSelectors,
8+
DatabaseViewSelectors,
9+
ModalSelectors,
10+
PageSelectors,
11+
SpaceSelectors,
12+
waitForReactUpdate,
13+
} from '../../support/selectors';
14+
15+
describe('Database Container Open Behavior', () => {
16+
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
17+
const dbName = 'New Database';
18+
const spaceName = 'General';
19+
20+
const currentViewIdFromUrl = () =>
21+
cy.location('pathname').then((pathname) => {
22+
const maybeId = pathname.split('/').filter(Boolean).pop() || '';
23+
return maybeId;
24+
});
25+
26+
beforeEach(() => {
27+
cy.on('uncaught:exception', (err) => {
28+
if (
29+
err.message.includes('Minified React error') ||
30+
err.message.includes('View not found') ||
31+
err.message.includes('No workspace or service found') ||
32+
err.message.includes('ResizeObserver loop')
33+
) {
34+
return false;
35+
}
36+
return true;
37+
});
38+
39+
cy.viewport(1280, 720);
40+
});
41+
42+
it('opens the first child view when clicking a database container', () => {
43+
const testEmail = generateRandomEmail();
44+
testLog.testStart('Database container opens first child');
45+
testLog.info(`Test email: ${testEmail}`);
46+
47+
cy.visit('/login', { failOnStatusCode: false });
48+
cy.wait(2000);
49+
50+
const authUtils = new AuthTestUtils();
51+
authUtils.signInWithTestUrl(testEmail).then(() => {
52+
cy.url({ timeout: 30000 }).should('include', '/app');
53+
cy.wait(3000);
54+
55+
// Create a standalone database (container + first child view)
56+
testLog.step(1, 'Create standalone Grid database');
57+
AddPageSelectors.inlineAddButton().first().click({ force: true });
58+
waitForReactUpdate(1000);
59+
AddPageSelectors.addGridButton().should('be.visible').click({ force: true });
60+
61+
// Wait for the database UI to appear
62+
// The grid container may exist before it has a stable height; wait for cells to render.
63+
cy.wait(7000);
64+
DatabaseGridSelectors.grid().should('exist');
65+
DatabaseGridSelectors.cells().should('have.length.greaterThan', 0);
66+
67+
// Scenario 1 parity: a newly created container has exactly 1 child view
68+
DatabaseViewSelectors.viewTab()
69+
.should('have.length', 1)
70+
.first()
71+
.should('have.attr', 'data-state', 'active')
72+
.and('contain.text', dbName);
73+
74+
// Ensure sidebar is visible and space expanded
75+
SpaceSelectors.itemByName(spaceName).should('exist');
76+
SpaceSelectors.itemByName(spaceName).then(($space) => {
77+
const expandedIndicator = $space.find('[data-testid="space-expanded"]');
78+
const isExpanded = expandedIndicator.attr('data-expanded') === 'true';
79+
80+
if (!isExpanded) {
81+
SpaceSelectors.itemByName(spaceName).find('[data-testid="space-name"]').click({ force: true });
82+
waitForReactUpdate(500);
83+
}
84+
});
85+
86+
// Capture the currently active viewId (the first child view opened after container creation)
87+
testLog.step(2, 'Capture first child view id');
88+
currentViewIdFromUrl().then((firstChildViewId) => {
89+
expect(firstChildViewId).to.not.equal('');
90+
cy.wrap(firstChildViewId).as('firstChildViewId');
91+
});
92+
93+
// Navigate away to a document page so we can click the container again
94+
testLog.step(3, 'Navigate away to a new document');
95+
closeModalsIfOpen();
96+
AddPageSelectors.inlineAddButton().first().click({ force: true });
97+
waitForReactUpdate(1000);
98+
cy.get('[role="menuitem"]').first().click({ force: true });
99+
waitForReactUpdate(1000);
100+
101+
cy.get('body').then(($body) => {
102+
if ($body.find('[data-testid="new-page-modal"]').length > 0) {
103+
ModalSelectors.newPageModal()
104+
.should('be.visible')
105+
.within(() => {
106+
ModalSelectors.spaceItemInModal().first().click({ force: true });
107+
waitForReactUpdate(500);
108+
cy.contains('button', 'Add').click({ force: true });
109+
});
110+
}
111+
});
112+
waitForReactUpdate(2000);
113+
114+
// Click on the database container in the sidebar and ensure we land on its first child view id
115+
testLog.step(4, 'Click container and verify redirect');
116+
PageSelectors.nameContaining(dbName).first().click({ force: true });
117+
118+
cy.get<string>('@firstChildViewId').then((firstChildViewId) => {
119+
cy.location('pathname', { timeout: 20000 }).should('include', `/${firstChildViewId}`);
120+
DatabaseViewSelectors.viewTab(firstChildViewId).should('have.attr', 'data-state', 'active');
121+
});
122+
123+
DatabaseGridSelectors.grid().should('exist');
124+
DatabaseGridSelectors.cells().should('have.length.greaterThan', 0);
125+
126+
testLog.testEnd('Database container opens first child');
127+
});
128+
});
129+
});

0 commit comments

Comments
 (0)