mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
* chore: disable edit * chore: disable field edit * chore: fix relation test * chore: fix test * chore: i18
887 lines
36 KiB
TypeScript
887 lines
36 KiB
TypeScript
/**
|
|
* Relation Cell Integration Tests
|
|
*
|
|
* Based on desktop Flutter integration tests from:
|
|
* frontend/appflowy_flutter/integration_test/desktop/grid/grid_relation_test.dart
|
|
*
|
|
* Test scenarios:
|
|
* 1. Open relation cell popup when clicking on a relation cell
|
|
* 2. Select a related database from the popup
|
|
* 3. Link rows from another database
|
|
* 4. Click relation link opens row detail (regression test for #7593)
|
|
*/
|
|
|
|
import {
|
|
AddPageSelectors,
|
|
DatabaseGridSelectors,
|
|
DatabaseViewSelectors,
|
|
DropdownSelectors,
|
|
PropertyMenuSelectors,
|
|
GridFieldSelectors,
|
|
PageSelectors,
|
|
ViewActionSelectors,
|
|
ModalSelectors,
|
|
FieldType,
|
|
byTestId,
|
|
waitForReactUpdate,
|
|
} from '../../support/selectors';
|
|
import { AuthTestUtils } from '../../support/auth-utils';
|
|
import { generateRandomEmail } from '../../support/test-config';
|
|
|
|
/**
|
|
* Relation-specific selectors
|
|
*/
|
|
const RelationSelectors = {
|
|
// Relation cell popup (uses Radix Popover)
|
|
relationPopover: () => cy.get('[data-radix-popper-content-wrapper]'),
|
|
|
|
// No database selected content
|
|
noDatabaseSelectedContent: () => cy.contains('Select a database'),
|
|
|
|
// Database list in picker
|
|
databaseOption: (dbName: string) => cy.contains(dbName),
|
|
|
|
// Relation cell menu content
|
|
relationMenuContent: () => cy.get('[data-radix-popper-content-wrapper]'),
|
|
|
|
// Search input in relation menu
|
|
searchInput: () => cy.get('[data-radix-popper-content-wrapper] input'),
|
|
|
|
// Row items in relation picker
|
|
rowItem: (rowName: string) => cy.get('[data-radix-popper-content-wrapper]').contains(rowName),
|
|
|
|
// Linked row in cell
|
|
linkedRow: (rowName: string) => cy.contains(rowName),
|
|
};
|
|
|
|
const waitForAppReady = () => {
|
|
cy.get(`${byTestId('inline-add-page')}, ${byTestId('new-page-button')}`, { timeout: 20000 }).should('be.visible');
|
|
};
|
|
|
|
const waitForGridReady = () => {
|
|
DatabaseGridSelectors.grid().should('exist', { timeout: 30000 });
|
|
DatabaseGridSelectors.cells().should('have.length.at.least', 1, { timeout: 30000 });
|
|
};
|
|
|
|
const renameActiveViewTab = (name: string) => {
|
|
DatabaseViewSelectors.activeViewTab()
|
|
.should('be.visible')
|
|
.find('.truncate')
|
|
.first()
|
|
.trigger('pointerdown', { button: 2, force: true });
|
|
|
|
DropdownSelectors.content({ timeout: 5000 }).should('be.visible');
|
|
DatabaseViewSelectors.tabActionRename().should('be.visible').click({ force: true });
|
|
ModalSelectors.renameInput().should('be.visible', { timeout: 5000 }).clear().type(name);
|
|
ModalSelectors.renameSaveButton().click({ force: true });
|
|
DatabaseViewSelectors.activeViewTab().should('contain.text', name);
|
|
waitForReactUpdate(500);
|
|
};
|
|
|
|
const isRelationRollupEditEnabled = Cypress.env('APPFLOWY_ENABLE_RELATION_ROLLUP_EDIT') === 'true';
|
|
const describeIfEnabled = isRelationRollupEditEnabled ? describe : describe.skip;
|
|
|
|
describeIfEnabled('Relation Cell Type', () => {
|
|
beforeEach(() => {
|
|
// Handle React errors gracefully
|
|
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')
|
|
) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
cy.viewport(1600, 900);
|
|
});
|
|
|
|
/**
|
|
* Test 1: Open relation cell popup when clicking on a relation cell
|
|
*
|
|
* This test verifies the fix for the relation cell popup not opening.
|
|
* Steps:
|
|
* 1. Create a grid database
|
|
* 2. Add a Relation field
|
|
* 3. Click on a relation cell
|
|
* 4. Verify the popup opens (should show database selection since no database is configured)
|
|
*/
|
|
it('should open relation cell popup when clicking on a relation cell', () => {
|
|
const testEmail = generateRandomEmail();
|
|
cy.log(`[TEST] Relation cell popup test - Email: ${testEmail}`);
|
|
|
|
cy.visit('/login', { failOnStatusCode: false });
|
|
|
|
const authUtils = new AuthTestUtils();
|
|
authUtils.signInWithTestUrl(testEmail).then(() => {
|
|
cy.url({ timeout: 30000 }).should('include', '/app');
|
|
waitForAppReady();
|
|
|
|
// Create a new grid
|
|
cy.log('[STEP 1] Creating new grid');
|
|
AddPageSelectors.inlineAddButton().then(($btn) => {
|
|
if ($btn.length > 0) {
|
|
AddPageSelectors.inlineAddButton().first().click({ force: true });
|
|
} else {
|
|
PageSelectors.newPageButton().first().click({ force: true });
|
|
}
|
|
});
|
|
|
|
waitForReactUpdate(1000);
|
|
AddPageSelectors.addGridButton().should('exist', { timeout: 10000 });
|
|
AddPageSelectors.addGridButton().click({ force: true });
|
|
|
|
// Wait for grid to render
|
|
cy.log('[STEP 2] Waiting for grid to render');
|
|
DatabaseGridSelectors.cells().should('exist', { timeout: 15000 });
|
|
|
|
// Add a Relation field
|
|
cy.log('[STEP 3] Adding Relation field');
|
|
PropertyMenuSelectors.newPropertyButton().first().scrollIntoView().click({ force: true });
|
|
|
|
// Wait for the PropertyMenu dropdown to open (Radix dropdown)
|
|
cy.log('[STEP 3b] Waiting for property menu dropdown to open');
|
|
cy.get('[data-radix-popper-content-wrapper]', { timeout: 10000 }).should('be.visible');
|
|
|
|
// Change column type to Relation
|
|
cy.log('[STEP 4] Changing column type to Relation');
|
|
// The property-type-trigger should now be visible inside the dropdown
|
|
PropertyMenuSelectors.propertyTypeTrigger().should('be.visible', { timeout: 5000 }).first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
|
|
// Wait for the type submenu to open and click Relation
|
|
cy.log('[STEP 4b] Selecting Relation type');
|
|
PropertyMenuSelectors.propertyTypeOption(FieldType.Relation).should('be.visible', { timeout: 5000 }).click({ force: true });
|
|
waitForReactUpdate(2000);
|
|
|
|
// Close property menu
|
|
cy.get('body').type('{esc}{esc}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Find and click on a relation cell
|
|
cy.log('[STEP 5] Finding and clicking relation cell');
|
|
|
|
// Get the relation field ID from the header
|
|
GridFieldSelectors.allFieldHeaders().then(($headers) => {
|
|
// Find the Relation field header (should be the last one we added)
|
|
const lastHeader = $headers.last();
|
|
const fieldId = lastHeader.attr('data-testid')?.replace('grid-field-header-', '');
|
|
|
|
if (fieldId) {
|
|
cy.log(`[INFO] Found Relation field ID: ${fieldId}`);
|
|
|
|
// Click on the relation cell in the first data row
|
|
// Use the clickable wrapper with data-column-id
|
|
DatabaseGridSelectors.dataRowCellsForField(fieldId).first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
|
|
// Verify the popup opened
|
|
cy.log('[STEP 6] Verifying relation popup opened');
|
|
RelationSelectors.relationPopover().should('be.visible', { timeout: 5000 });
|
|
|
|
cy.log('[SUCCESS] Relation cell popup opened successfully!');
|
|
} else {
|
|
// Fallback: try clicking by position
|
|
cy.log('[INFO] Using fallback cell selection method');
|
|
DatabaseGridSelectors.dataRows()
|
|
.first()
|
|
.find('.grid-row-cell')
|
|
.last()
|
|
.click({ force: true });
|
|
|
|
waitForReactUpdate(1000);
|
|
RelationSelectors.relationPopover().should('be.visible', { timeout: 5000 });
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Test 2: Link rows from another database
|
|
*
|
|
* Based on desktop Flutter test: 'link rows from another database'
|
|
* Steps:
|
|
* 1. Create first grid (related database) with a row named "Linked Row"
|
|
* 2. Create second grid with a Relation field pointing to first grid
|
|
* 3. Open relation cell popup
|
|
* 4. Select the related database
|
|
* 5. Select "Linked Row" from the list
|
|
* 6. Verify the linked row appears in the cell
|
|
*/
|
|
// SKIP: Test is flaky due to view sync timing issues when creating multiple grids
|
|
// The "View not found in outline" warnings indicate the second grid isn't fully registered
|
|
// before navigation attempts. Core relation functionality is covered by tests 1 and 3.
|
|
it.skip('should link rows from another database', () => {
|
|
const testEmail = generateRandomEmail();
|
|
const uniqueSuffix = Date.now();
|
|
const sourceDbName = `Relation Source ${uniqueSuffix}`;
|
|
const targetDbName = `Relation Target ${uniqueSuffix}`;
|
|
|
|
cy.log(`[TEST] Link rows from another database - Email: ${testEmail}`);
|
|
|
|
cy.visit('/login', { failOnStatusCode: false });
|
|
|
|
const authUtils = new AuthTestUtils();
|
|
authUtils.signInWithTestUrl(testEmail).then(() => {
|
|
cy.url({ timeout: 30000 }).should('include', '/app');
|
|
waitForAppReady();
|
|
|
|
// Create first grid (related database)
|
|
cy.log('[STEP 1] Creating first grid (related database)');
|
|
AddPageSelectors.inlineAddButton().first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
AddPageSelectors.addGridButton().click({ force: true });
|
|
|
|
// Wait for grid cells to be available
|
|
cy.log('[STEP 1b] Waiting for grid cells to load');
|
|
waitForGridReady();
|
|
|
|
cy.log('[STEP 1c] Renaming source view');
|
|
renameActiveViewTab(sourceDbName);
|
|
|
|
// Add a row with a specific name
|
|
cy.log('[STEP 2] Adding row named "Linked Row"');
|
|
DatabaseGridSelectors.cells().first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
cy.focused().type('Linked Row{enter}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Create second grid (with relation field)
|
|
cy.log('[STEP 3] Creating second grid');
|
|
AddPageSelectors.inlineAddButton().first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
AddPageSelectors.addGridButton().click({ force: true });
|
|
|
|
cy.log('[STEP 3a] Waiting for second grid to render');
|
|
waitForGridReady();
|
|
|
|
cy.log('[STEP 3b] Renaming target view');
|
|
renameActiveViewTab(targetDbName);
|
|
|
|
// Add data to second grid
|
|
cy.log('[STEP 3c] Adding data to second grid');
|
|
DatabaseGridSelectors.cells().first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
cy.focused().type('Main Row{enter}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Add Relation field
|
|
cy.log('[STEP 4] Adding Relation field to second grid');
|
|
PropertyMenuSelectors.newPropertyButton().first().scrollIntoView().click({ force: true });
|
|
|
|
// Wait for the PropertyMenu dropdown to open
|
|
cy.get('[data-radix-popper-content-wrapper]', { timeout: 10000 }).should('be.visible');
|
|
|
|
PropertyMenuSelectors.propertyTypeTrigger().should('be.visible', { timeout: 5000 }).first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
PropertyMenuSelectors.propertyTypeOption(FieldType.Relation).should('be.visible', { timeout: 5000 }).click({ force: true });
|
|
waitForReactUpdate(2000);
|
|
|
|
// Close property menu
|
|
cy.get('body').type('{esc}{esc}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Click on relation cell
|
|
cy.log('[STEP 5] Clicking on relation cell');
|
|
GridFieldSelectors.allFieldHeaders().then(($headers) => {
|
|
const lastHeader = $headers.last();
|
|
const fieldId = lastHeader.attr('data-testid')?.replace('grid-field-header-', '');
|
|
|
|
if (fieldId) {
|
|
DatabaseGridSelectors.dataRowCellsForField(fieldId).first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
|
|
// Verify popup is visible
|
|
RelationSelectors.relationPopover().should('be.visible', { timeout: 5000 });
|
|
|
|
cy.log('[STEP 6] Selecting related database from popup');
|
|
cy.get('[data-radix-popper-content-wrapper]')
|
|
.should('be.visible')
|
|
.then(($popover) => {
|
|
const text = $popover.text();
|
|
cy.log(`[INFO] Popup content: ${text.substring(0, 200)}`);
|
|
});
|
|
|
|
cy.get('[data-radix-popper-content-wrapper]')
|
|
.find('button')
|
|
.contains(sourceDbName)
|
|
.should('be.visible')
|
|
.click({ force: true });
|
|
|
|
cy.log('[STEP 7] Waiting for rows from related database');
|
|
cy.get('[data-radix-popper-content-wrapper]', { timeout: 15000 })
|
|
.should('contain.text', 'Linked Row');
|
|
|
|
// Now click the + button to link the row (NOT the row text which navigates)
|
|
cy.log('[STEP 7a] Clicking + button to link "Linked Row"');
|
|
cy.get('[data-radix-popper-content-wrapper]')
|
|
.contains('Linked Row')
|
|
.parents('div.group')
|
|
.find('button')
|
|
.click({ force: true });
|
|
|
|
waitForReactUpdate(1000);
|
|
|
|
// Close the popup
|
|
cy.get('body').type('{esc}');
|
|
waitForReactUpdate(500);
|
|
|
|
// Verify the linked row appears in the cell
|
|
cy.log('[STEP 8] Verifying linked row appears in cell');
|
|
cy.contains('Linked Row').should('exist');
|
|
cy.log('[SUCCESS] Row linked successfully!');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Test 3: Verify relation cell in row detail panel
|
|
*
|
|
* Tests the relation cell popup in the row detail panel context
|
|
*/
|
|
it('should open relation popup from row detail panel', () => {
|
|
const testEmail = generateRandomEmail();
|
|
cy.log(`[TEST] Relation popup in row detail - Email: ${testEmail}`);
|
|
|
|
cy.visit('/login', { failOnStatusCode: false });
|
|
|
|
const authUtils = new AuthTestUtils();
|
|
authUtils.signInWithTestUrl(testEmail).then(() => {
|
|
cy.url({ timeout: 30000 }).should('include', '/app');
|
|
waitForAppReady();
|
|
|
|
// Create a grid with a Relation field
|
|
cy.log('[STEP 1] Creating grid with Relation field');
|
|
AddPageSelectors.inlineAddButton().first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
AddPageSelectors.addGridButton().click({ force: true });
|
|
|
|
// Wait for grid cells to be available first
|
|
cy.log('[STEP 1b] Waiting for grid cells to load');
|
|
DatabaseGridSelectors.cells().should('exist', { timeout: 15000 });
|
|
|
|
// Add Relation field
|
|
PropertyMenuSelectors.newPropertyButton().first().scrollIntoView().click({ force: true });
|
|
|
|
// Wait for the PropertyMenu dropdown to open
|
|
cy.get('[data-radix-popper-content-wrapper]', { timeout: 10000 }).should('be.visible');
|
|
|
|
PropertyMenuSelectors.propertyTypeTrigger().should('be.visible', { timeout: 5000 }).first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
PropertyMenuSelectors.propertyTypeOption(FieldType.Relation).should('be.visible', { timeout: 5000 }).click({ force: true });
|
|
waitForReactUpdate(2000);
|
|
|
|
cy.get('body').type('{esc}{esc}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Open row detail panel by hovering over the first row to reveal the expand button
|
|
cy.log('[STEP 2] Opening row detail panel');
|
|
|
|
// First, get the first data row and hover over it to reveal the OpenAction button
|
|
DatabaseGridSelectors.dataRows().first().realHover();
|
|
waitForReactUpdate(1000);
|
|
|
|
// Look for the expand button (OpenAction) - it contains an SVG with full_screen icon
|
|
// The button should now be visible after hovering
|
|
cy.get('[data-testid^="grid-row-"]:not([data-testid="grid-row-undefined"])')
|
|
.first()
|
|
.find('button.bg-surface-primary')
|
|
.should('be.visible', { timeout: 5000 })
|
|
.click({ force: true });
|
|
|
|
waitForReactUpdate(2000);
|
|
|
|
// Verify row detail panel opened by checking for the page/row content
|
|
cy.log('[STEP 3] Verifying row detail panel');
|
|
// Row detail panel should show the field names
|
|
cy.get('body').then(($body) => {
|
|
// Check if we navigated to a row page or opened a modal
|
|
const hasRowDetail = $body.text().includes('Relation') || $body.find('[data-testid="row-detail"]').length > 0;
|
|
|
|
if (hasRowDetail) {
|
|
cy.log('[INFO] Row detail panel detected');
|
|
|
|
// Click on the Relation field value area
|
|
cy.log('[STEP 4] Clicking on Relation field in row detail');
|
|
|
|
// Look for "Add Relation" button text (case-sensitive) in row detail
|
|
// The row detail panel shows field names with "Add Relation" as clickable text
|
|
cy.contains(/Add Relation/i).should('be.visible', { timeout: 5000 }).click({ force: true });
|
|
|
|
waitForReactUpdate(1000);
|
|
|
|
// Verify the popup opened
|
|
cy.log('[STEP 5] Verifying relation popup opened in row detail');
|
|
RelationSelectors.relationPopover().should('be.visible', { timeout: 5000 });
|
|
|
|
cy.log('[SUCCESS] Relation popup opened from row detail panel!');
|
|
} else {
|
|
cy.log('[INFO] Row detail panel not found - test may need different approach');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Test 4: Click relation link opens row detail
|
|
*
|
|
* Regression test for: https://github.com/AppFlowy-IO/AppFlowy/issues/7593
|
|
* Based on desktop Flutter test: 'click relation link opens row detail'
|
|
*
|
|
* Steps:
|
|
* 1. Create first grid with a row named "Target Row"
|
|
* 2. Create second grid with a Relation field
|
|
* 3. Link "Target Row" from the first grid
|
|
* 4. Click on the relation link in the cell
|
|
* 5. Verify the row detail page opens
|
|
*/
|
|
// SKIP: Test is flaky due to view sync timing issues when creating multiple grids
|
|
// The "View not found in outline" warnings indicate the second grid isn't fully registered
|
|
// before navigation attempts. Core relation functionality is covered by tests 1 and 3.
|
|
it.skip('should open row detail when clicking relation link (regression #7593)', () => {
|
|
const testEmail = generateRandomEmail();
|
|
const uniqueSuffix = Date.now();
|
|
const sourceDbName = `Relation Source ${uniqueSuffix}`;
|
|
const targetDbName = `Relation Target ${uniqueSuffix}`;
|
|
|
|
cy.log(`[TEST] Click relation link opens row detail - Email: ${testEmail}`);
|
|
|
|
cy.visit('/login', { failOnStatusCode: false });
|
|
|
|
const authUtils = new AuthTestUtils();
|
|
authUtils.signInWithTestUrl(testEmail).then(() => {
|
|
cy.url({ timeout: 30000 }).should('include', '/app');
|
|
waitForAppReady();
|
|
|
|
// Create first grid (related database)
|
|
cy.log('[STEP 1] Creating first grid with "Target Row"');
|
|
AddPageSelectors.inlineAddButton().first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
AddPageSelectors.addGridButton().click({ force: true });
|
|
|
|
waitForGridReady();
|
|
renameActiveViewTab(sourceDbName);
|
|
|
|
// Name the first row "Target Row"
|
|
DatabaseGridSelectors.cells().first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
cy.focused().type('Target Row{enter}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Create second grid
|
|
cy.log('[STEP 2] Creating second grid with Relation field');
|
|
AddPageSelectors.inlineAddButton().first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
AddPageSelectors.addGridButton().click({ force: true });
|
|
|
|
cy.log('[STEP 2a] Waiting for second grid to render');
|
|
waitForGridReady();
|
|
renameActiveViewTab(targetDbName);
|
|
|
|
// Add data to second grid (needed for stable grid state)
|
|
cy.log('[STEP 2b] Adding data to second grid');
|
|
DatabaseGridSelectors.cells().first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
cy.focused().type('Main Row{enter}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Add Relation field
|
|
cy.log('[STEP 3] Adding Relation field');
|
|
PropertyMenuSelectors.newPropertyButton().first().scrollIntoView().click({ force: true });
|
|
|
|
// Wait for the PropertyMenu dropdown to open
|
|
cy.get('[data-radix-popper-content-wrapper]', { timeout: 10000 }).should('be.visible');
|
|
waitForReactUpdate(500);
|
|
|
|
PropertyMenuSelectors.propertyTypeTrigger().should('be.visible', { timeout: 5000 }).first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
PropertyMenuSelectors.propertyTypeOption(FieldType.Relation).should('be.visible', { timeout: 10000 }).click({ force: true });
|
|
waitForReactUpdate(2000);
|
|
|
|
cy.get('body').type('{esc}{esc}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Click on relation cell and link the row
|
|
cy.log('[STEP 4] Linking "Target Row" to relation cell');
|
|
GridFieldSelectors.allFieldHeaders().then(($headers) => {
|
|
const lastHeader = $headers.last();
|
|
const fieldId = lastHeader.attr('data-testid')?.replace('grid-field-header-', '');
|
|
|
|
if (fieldId) {
|
|
DatabaseGridSelectors.dataRowCellsForField(fieldId).first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
|
|
RelationSelectors.relationPopover().should('be.visible', { timeout: 5000 });
|
|
|
|
// Select the related database - click on source database name
|
|
cy.log('[STEP 4a] Selecting related database');
|
|
cy.get('[data-radix-popper-content-wrapper]')
|
|
.find('button')
|
|
.contains(sourceDbName)
|
|
.first()
|
|
.click({ force: true });
|
|
|
|
// Wait for the popup to transition to showing rows from the selected database
|
|
cy.log('[STEP 4b] Waiting for Target Row to appear in popup');
|
|
cy.get('[data-radix-popper-content-wrapper]', { timeout: 15000 })
|
|
.should('contain.text', 'Target Row');
|
|
|
|
// Click the + button to link the row (NOT the row text which navigates)
|
|
cy.log('[STEP 4c] Clicking + button to link "Target Row"');
|
|
cy.get('[data-radix-popper-content-wrapper]')
|
|
.contains('Target Row')
|
|
.parents('div.group')
|
|
.find('button')
|
|
.click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
|
|
// Close the popup
|
|
cy.get('body').type('{esc}');
|
|
waitForReactUpdate(500);
|
|
|
|
// Verify the linked row appears in the cell
|
|
cy.log('[STEP 5] Verifying linked row appears');
|
|
cy.get('[data-radix-popper-content-wrapper]').should('not.exist');
|
|
cy.contains('Target Row', { timeout: 10000 }).should('exist');
|
|
|
|
// Click on the relation link to open row detail
|
|
cy.log('[STEP 6] Clicking relation link to open row detail');
|
|
|
|
// Find the relation cell and click on the linked row text
|
|
DatabaseGridSelectors.dataRowCellsForField(fieldId)
|
|
.first()
|
|
.contains('Target Row')
|
|
.click({ force: true });
|
|
|
|
waitForReactUpdate(2000);
|
|
|
|
// Verify row detail page opened - check for row detail indicators
|
|
cy.log('[STEP 7] Verifying row detail page opened');
|
|
|
|
// The row detail should show the row content or navigate to a new page
|
|
// Check if URL changed or if row detail modal appeared
|
|
cy.url().then((url) => {
|
|
// Either we navigated to a row page or a modal opened
|
|
const isRowPage = url.includes('/row/') || url.includes('?r=');
|
|
|
|
if (isRowPage) {
|
|
cy.log('[SUCCESS] Navigated to row detail page!');
|
|
} else {
|
|
// Check for row detail modal/panel
|
|
cy.get('body').should('contain.text', 'Target Row');
|
|
cy.log('[SUCCESS] Row detail content visible!');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Test 5: Rename related row updates relation cell display
|
|
*
|
|
* Regression test for: https://github.com/AppFlowy-IO/AppFlowy/issues/6699
|
|
* Based on desktop Flutter test: 'rename related row updates relation cell display'
|
|
*
|
|
* Steps:
|
|
* 1. Create first grid with a row named "Original Name"
|
|
* 2. Create second grid with a Relation field linked to first grid
|
|
* 3. Link the row from first grid
|
|
* 4. Go back to first grid and rename the row to "Renamed Row"
|
|
* 5. Return to second grid and verify the relation cell shows "Renamed Row"
|
|
*/
|
|
// SKIP: Test is flaky due to view sync timing issues when creating multiple grids
|
|
// The "View not found in outline" warnings indicate the second grid isn't fully registered
|
|
// before navigation attempts. Core relation functionality is covered by tests 1 and 3.
|
|
it.skip('should update relation cell when related row is renamed (regression #6699)', () => {
|
|
const testEmail = generateRandomEmail();
|
|
const uniqueSuffix = Date.now();
|
|
const sourceDbName = `Relation Source ${uniqueSuffix}`;
|
|
const targetDbName = `Relation Target ${uniqueSuffix}`;
|
|
|
|
cy.log(`[TEST] Rename related row updates cell - Email: ${testEmail}`);
|
|
|
|
cy.visit('/login', { failOnStatusCode: false });
|
|
|
|
const authUtils = new AuthTestUtils();
|
|
authUtils.signInWithTestUrl(testEmail).then(() => {
|
|
cy.url({ timeout: 30000 }).should('include', '/app');
|
|
waitForAppReady();
|
|
|
|
// Create first grid (related database)
|
|
cy.log('[STEP 1] Creating first grid with "Original Name"');
|
|
AddPageSelectors.inlineAddButton().first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
AddPageSelectors.addGridButton().click({ force: true });
|
|
|
|
waitForGridReady();
|
|
renameActiveViewTab(sourceDbName);
|
|
|
|
// Name the first row
|
|
DatabaseGridSelectors.cells().first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
cy.focused().type('Original Name{enter}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Store the first grid's URL/name for navigation
|
|
cy.url().then((firstGridUrl) => {
|
|
// Create second grid
|
|
cy.log('[STEP 2] Creating second grid with Relation field');
|
|
AddPageSelectors.inlineAddButton().first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
AddPageSelectors.addGridButton().click({ force: true });
|
|
|
|
cy.log('[STEP 2a] Waiting for second grid to render');
|
|
waitForGridReady();
|
|
renameActiveViewTab(targetDbName);
|
|
|
|
// Capture the second grid URL and navigate to ensure proper state
|
|
cy.url().then((secondGridUrl) => {
|
|
cy.log(`[INFO] Second grid URL: ${secondGridUrl}`);
|
|
|
|
// Add data to second grid (needed for stable grid state)
|
|
cy.log('[STEP 2b] Adding data to second grid');
|
|
DatabaseGridSelectors.cells().first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
cy.focused().type('Main Row{enter}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Add Relation field
|
|
PropertyMenuSelectors.newPropertyButton().first().scrollIntoView().click({ force: true });
|
|
|
|
// Wait for the PropertyMenu dropdown to open
|
|
cy.get('[data-radix-popper-content-wrapper]', { timeout: 10000 }).should('be.visible');
|
|
waitForReactUpdate(500);
|
|
|
|
PropertyMenuSelectors.propertyTypeTrigger().should('be.visible', { timeout: 5000 }).first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
PropertyMenuSelectors.propertyTypeOption(FieldType.Relation).should('be.visible', { timeout: 10000 }).click({ force: true });
|
|
waitForReactUpdate(2000);
|
|
|
|
cy.get('body').type('{esc}{esc}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Link the row from first grid
|
|
cy.log('[STEP 3] Linking "Original Name" row');
|
|
GridFieldSelectors.allFieldHeaders().then(($headers) => {
|
|
const lastHeader = $headers.last();
|
|
const fieldId = lastHeader.attr('data-testid')?.replace('grid-field-header-', '');
|
|
|
|
if (fieldId) {
|
|
DatabaseGridSelectors.dataRowCellsForField(fieldId).first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
|
|
RelationSelectors.relationPopover().should('be.visible', { timeout: 5000 });
|
|
|
|
// Select the related database - click on source database name
|
|
cy.log('[STEP 3a] Selecting related database');
|
|
cy.get('[data-radix-popper-content-wrapper]')
|
|
.find('button')
|
|
.contains(sourceDbName)
|
|
.first()
|
|
.click({ force: true });
|
|
|
|
// Wait for the popup to transition to showing rows from the selected database
|
|
cy.log('[STEP 3b] Waiting for Original Name to appear in popup');
|
|
cy.get('[data-radix-popper-content-wrapper]', { timeout: 15000 })
|
|
.should('contain.text', 'Original Name');
|
|
|
|
// Click the + button to link the row (NOT the row text which navigates)
|
|
cy.log('[STEP 3c] Clicking + button to link "Original Name"');
|
|
cy.get('[data-radix-popper-content-wrapper]')
|
|
.contains('Original Name')
|
|
.parents('div.group')
|
|
.find('button')
|
|
.click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
|
|
cy.get('body').type('{esc}');
|
|
waitForReactUpdate(500);
|
|
|
|
// Verify linked
|
|
cy.get('[data-radix-popper-content-wrapper]').should('not.exist');
|
|
cy.contains('Original Name', { timeout: 10000 }).should('exist');
|
|
cy.log('[INFO] Row linked successfully');
|
|
|
|
// Navigate to first grid to rename the row
|
|
cy.log('[STEP 4] Navigating to first grid to rename row');
|
|
cy.visit(firstGridUrl);
|
|
waitForGridReady();
|
|
|
|
// Find and rename the row
|
|
cy.log('[STEP 5] Renaming row to "Renamed Row"');
|
|
DatabaseGridSelectors.cells().first().dblclick({ force: true });
|
|
waitForReactUpdate(500);
|
|
|
|
// Clear existing text and type new name
|
|
cy.focused().clear().type('Renamed Row{enter}');
|
|
waitForReactUpdate(2000);
|
|
|
|
// Navigate back to second grid
|
|
cy.log('[STEP 6] Navigating back to second grid');
|
|
cy.visit(secondGridUrl);
|
|
waitForGridReady();
|
|
|
|
// Verify the relation cell shows the renamed value
|
|
cy.log('[STEP 7] Verifying relation cell shows "Renamed Row"');
|
|
cy.contains('Renamed Row', { timeout: 15000 }).should('exist');
|
|
|
|
cy.log('[SUCCESS] Relation cell updated with renamed row!');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Test 6: Rename related database updates relation field header
|
|
*
|
|
* Based on desktop Flutter test: 'rename related database updates relation field header'
|
|
*
|
|
* NOTE: This test is skipped because the web implementation does not automatically
|
|
* update the relation field header when the related database is renamed.
|
|
* On desktop, the field header shows the related database name and updates when renamed.
|
|
* On web, the field header shows the field name ("Relation") and doesn't auto-update.
|
|
* This is a feature difference between desktop and web implementations.
|
|
*
|
|
* Steps:
|
|
* 1. Create first grid (will be related database)
|
|
* 2. Create second grid with a Relation field pointing to first grid
|
|
* 3. Verify the relation field header shows the database name
|
|
* 4. Rename the first grid
|
|
* 5. Return to second grid and verify the field header shows the new name
|
|
*/
|
|
it.skip('should update relation field header when related database is renamed', () => {
|
|
const testEmail = generateRandomEmail();
|
|
const firstDbName = 'New Database'; // Default name for new databases
|
|
const renamedDbName = 'RenamedDatabase';
|
|
|
|
cy.log(`[TEST] Rename database updates field header - Email: ${testEmail}`);
|
|
|
|
cy.visit('/login', { failOnStatusCode: false });
|
|
|
|
const authUtils = new AuthTestUtils();
|
|
authUtils.signInWithTestUrl(testEmail).then(() => {
|
|
cy.url({ timeout: 30000 }).should('include', '/app');
|
|
waitForAppReady();
|
|
|
|
// Create first grid (related database)
|
|
cy.log('[STEP 1] Creating first grid (related database)');
|
|
AddPageSelectors.inlineAddButton().first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
AddPageSelectors.addGridButton().click({ force: true });
|
|
|
|
DatabaseGridSelectors.cells().should('exist', { timeout: 15000 });
|
|
|
|
cy.url().then((firstGridUrl) => {
|
|
// Create second grid
|
|
cy.log('[STEP 2] Creating second grid with Relation field');
|
|
AddPageSelectors.inlineAddButton().first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
AddPageSelectors.addGridButton().click({ force: true });
|
|
DatabaseGridSelectors.grid().should('exist', { timeout: 30000 });
|
|
cy.reload();
|
|
|
|
DatabaseGridSelectors.cells().should('have.length.at.least', 1, { timeout: 30000 });
|
|
|
|
// Add Relation field
|
|
cy.log('[STEP 3] Adding Relation field');
|
|
PropertyMenuSelectors.newPropertyButton().first().scrollIntoView().click({ force: true });
|
|
|
|
// Wait for the PropertyMenu dropdown to open
|
|
cy.get('[data-radix-popper-content-wrapper]', { timeout: 10000 }).should('be.visible');
|
|
|
|
PropertyMenuSelectors.propertyTypeTrigger().should('be.visible', { timeout: 5000 }).first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
PropertyMenuSelectors.propertyTypeOption(FieldType.Relation).should('be.visible', { timeout: 5000 }).click({ force: true });
|
|
waitForReactUpdate(2000);
|
|
|
|
cy.get('body').type('{esc}{esc}');
|
|
waitForReactUpdate(1000);
|
|
|
|
cy.url().then((secondGridUrl) => {
|
|
// Configure the relation field
|
|
cy.log('[STEP 4] Configuring relation to point to first database');
|
|
GridFieldSelectors.allFieldHeaders().then(($headers) => {
|
|
const lastHeader = $headers.last();
|
|
const fieldId = lastHeader.attr('data-testid')?.replace('grid-field-header-', '');
|
|
|
|
if (fieldId) {
|
|
DatabaseGridSelectors.dataRowCellsForField(fieldId).first().click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
|
|
RelationSelectors.relationPopover().should('be.visible', { timeout: 5000 });
|
|
|
|
// Select the first database from the list - click on "Grid"
|
|
cy.get('[data-radix-popper-content-wrapper]')
|
|
.contains(/^Grid$/i)
|
|
.click({ force: true });
|
|
waitForReactUpdate(2000);
|
|
|
|
cy.get('body').type('{esc}');
|
|
waitForReactUpdate(1000);
|
|
|
|
// Navigate to first grid to rename it
|
|
cy.log('[STEP 5] Navigating to first grid to rename it');
|
|
cy.visit(firstGridUrl);
|
|
waitForAppReady();
|
|
|
|
// Rename using the page more actions menu
|
|
cy.log('[STEP 6] Renaming first database to "RenamedDatabase"');
|
|
|
|
// Find the page item in sidebar and hover to reveal more actions
|
|
PageSelectors.itemByName(firstDbName)
|
|
.should('exist', { timeout: 10000 })
|
|
.trigger('mouseenter', { force: true })
|
|
.trigger('mouseover', { force: true });
|
|
|
|
waitForReactUpdate(1000);
|
|
|
|
// Click more actions button
|
|
PageSelectors.moreActionsButton(firstDbName).click({ force: true });
|
|
waitForReactUpdate(1000);
|
|
|
|
// Click rename option
|
|
ViewActionSelectors.renameButton().should('be.visible').click({ force: true });
|
|
waitForReactUpdate(500);
|
|
|
|
// Enter new name in the modal
|
|
ModalSelectors.renameInput()
|
|
.should('be.visible', { timeout: 5000 })
|
|
.clear()
|
|
.type(renamedDbName);
|
|
|
|
// Save the new name
|
|
ModalSelectors.renameSaveButton().click({ force: true });
|
|
waitForReactUpdate(2000);
|
|
|
|
// Verify the page was renamed
|
|
cy.contains(renamedDbName, { timeout: 10000 }).should('exist');
|
|
cy.log('[INFO] Database renamed successfully');
|
|
|
|
// Navigate back to second grid
|
|
cy.log('[STEP 7] Navigating back to second grid');
|
|
cy.visit(secondGridUrl);
|
|
DatabaseGridSelectors.cells().should('exist', { timeout: 15000 });
|
|
|
|
// Verify the relation field header shows the new database name
|
|
cy.log('[STEP 8] Verifying field header shows "RenamedDatabase"');
|
|
|
|
// The relation field header should contain the database name
|
|
GridFieldSelectors.allFieldHeaders()
|
|
.last()
|
|
.should('contain.text', renamedDbName);
|
|
|
|
cy.log('[SUCCESS] Field header updated with renamed database!');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|