mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2025-11-30 19:37:55 +08:00
@@ -28,6 +28,7 @@ Create end-to-end test based on user request: `$ARGUMENTS`
|
||||
- No other source code changes allowed
|
||||
- Test multiple times to prevent flaky tests
|
||||
- Include descriptive logs for each action
|
||||
- Only create one test
|
||||
|
||||
## Test Quality Checklist
|
||||
- ✓ Test requirements understood
|
||||
|
||||
78
cypress/e2e/database/checkbox-column.cy.ts
Normal file
78
cypress/e2e/database/checkbox-column.cy.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthTestUtils } from '../../support/auth-utils';
|
||||
import {
|
||||
AddPageSelectors,
|
||||
DatabaseGridSelectors,
|
||||
CheckboxSelectors,
|
||||
waitForReactUpdate
|
||||
} from '../../support/selectors';
|
||||
|
||||
describe('Checkbox Column Type', () => {
|
||||
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
|
||||
|
||||
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')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
cy.viewport(1280, 720);
|
||||
});
|
||||
|
||||
it('should create grid and interact with cells', () => {
|
||||
const testEmail = generateRandomEmail();
|
||||
cy.log(`[TEST START] Testing grid cell interaction - Test email: ${testEmail}`);
|
||||
|
||||
// Login
|
||||
cy.log('[STEP 1] Visiting login page');
|
||||
cy.visit('/login', { failOnStatusCode: false });
|
||||
cy.wait(2000);
|
||||
|
||||
const authUtils = new AuthTestUtils();
|
||||
cy.log('[STEP 2] Starting authentication');
|
||||
authUtils.signInWithTestUrl(testEmail).then(() => {
|
||||
cy.log('[STEP 3] Authentication successful');
|
||||
cy.url({ timeout: 30000 }).should('include', '/app');
|
||||
cy.wait(3000);
|
||||
|
||||
// Create a new grid
|
||||
cy.log('[STEP 4] Creating new grid');
|
||||
AddPageSelectors.inlineAddButton().first().should('be.visible').click();
|
||||
waitForReactUpdate(1000);
|
||||
AddPageSelectors.addGridButton().should('be.visible').click();
|
||||
cy.wait(8000);
|
||||
|
||||
// Verify cells exist
|
||||
cy.log('[STEP 7] Verifying cells exist');
|
||||
DatabaseGridSelectors.cells().should('exist');
|
||||
|
||||
// Click on first cell
|
||||
cy.log('[STEP 8] Clicking on first cell');
|
||||
DatabaseGridSelectors.cells().first().click();
|
||||
waitForReactUpdate(500);
|
||||
|
||||
// Look for any checkbox-specific elements that might appear
|
||||
cy.log('[STEP 9] Looking for checkbox elements');
|
||||
cy.get('body').then($body => {
|
||||
// Check for checkbox cells with our data-testid
|
||||
const checkboxCells = $body.find('[data-testid^="checkbox-cell-"]');
|
||||
if (checkboxCells.length > 0) {
|
||||
cy.log(`[STEP 10] Found ${checkboxCells.length} checkbox cells`);
|
||||
|
||||
// Click first checkbox cell
|
||||
CheckboxSelectors.allCheckboxCells().first().click();
|
||||
waitForReactUpdate(500);
|
||||
cy.log('[STEP 11] Clicked checkbox cell');
|
||||
} else {
|
||||
cy.log('[STEP 10] No checkbox cells found, cell interaction test completed');
|
||||
}
|
||||
});
|
||||
|
||||
cy.log('[STEP 12] Test completed successfully');
|
||||
});
|
||||
});
|
||||
});
|
||||
140
cypress/e2e/database/datetime-column.cy.ts
Normal file
140
cypress/e2e/database/datetime-column.cy.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthTestUtils } from '../../support/auth-utils';
|
||||
import {
|
||||
AddPageSelectors,
|
||||
DatabaseGridSelectors,
|
||||
DateTimeSelectors,
|
||||
PropertyMenuSelectors,
|
||||
GridFieldSelectors,
|
||||
FieldType,
|
||||
waitForReactUpdate
|
||||
} from '../../support/selectors';
|
||||
|
||||
describe('DateTime Column Type', () => {
|
||||
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
|
||||
|
||||
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')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
cy.viewport(1280, 720);
|
||||
});
|
||||
|
||||
it('should create grid with datetime column and interact with date cells', () => {
|
||||
const testEmail = generateRandomEmail();
|
||||
cy.log(`[TEST START] Testing datetime column - Test email: ${testEmail}`);
|
||||
|
||||
// Login
|
||||
cy.log('[STEP 1] Visiting login page');
|
||||
cy.visit('/login', { failOnStatusCode: false });
|
||||
cy.wait(2000);
|
||||
|
||||
const authUtils = new AuthTestUtils();
|
||||
cy.log('[STEP 2] Starting authentication');
|
||||
authUtils.signInWithTestUrl(testEmail).then(() => {
|
||||
cy.log('[STEP 3] Authentication successful');
|
||||
cy.url({ timeout: 30000 }).should('include', '/app');
|
||||
cy.wait(3000);
|
||||
|
||||
// Create a new grid
|
||||
cy.log('[STEP 4] Creating new grid');
|
||||
AddPageSelectors.inlineAddButton().first().should('be.visible').click();
|
||||
waitForReactUpdate(1000);
|
||||
AddPageSelectors.addGridButton().should('be.visible').click();
|
||||
cy.wait(8000);
|
||||
|
||||
// Verify grid exists
|
||||
cy.log('[STEP 5] Verifying grid exists');
|
||||
DatabaseGridSelectors.grid().should('exist');
|
||||
|
||||
// Verify cells exist
|
||||
cy.log('[STEP 6] Verifying cells exist');
|
||||
cy.get('[data-testid^="grid-cell-"]', { timeout: 10000 }).should('exist');
|
||||
|
||||
// Add new column
|
||||
cy.log('[STEP 7] Adding new column by clicking new property button');
|
||||
PropertyMenuSelectors.newPropertyButton().should('be.visible');
|
||||
PropertyMenuSelectors.newPropertyButton().first().scrollIntoView().click({ force: true });
|
||||
waitForReactUpdate(3000);
|
||||
|
||||
// The new column is created and the property menu should be open automatically
|
||||
// Let's wait for property trigger to be available
|
||||
cy.log('[STEP 8] Waiting for property menu to open');
|
||||
cy.get('body').then($body => {
|
||||
// Check if property type trigger exists
|
||||
if ($body.find('[data-testid="property-type-trigger"]').length > 0) {
|
||||
cy.log('[STEP 9] Property type trigger found, changing to DateTime');
|
||||
PropertyMenuSelectors.propertyTypeTrigger().first().click({ force: true });
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Select DateTime option
|
||||
cy.log('[STEP 10] Selecting DateTime option');
|
||||
PropertyMenuSelectors.propertyTypeOption(FieldType.DateTime).click({ force: true });
|
||||
waitForReactUpdate(2000);
|
||||
} else {
|
||||
cy.log('[STEP 9] Property type trigger not found, looking for field header');
|
||||
// Try clicking on the new field header first
|
||||
GridFieldSelectors.allFieldHeaders().last().scrollIntoView().click({ force: true });
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Now try to find the property type trigger
|
||||
PropertyMenuSelectors.propertyTypeTrigger().first().click({ force: true });
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Select DateTime option
|
||||
cy.log('[STEP 10] Selecting DateTime option');
|
||||
PropertyMenuSelectors.propertyTypeOption(FieldType.DateTime).click({ force: true });
|
||||
waitForReactUpdate(2000);
|
||||
}
|
||||
});
|
||||
|
||||
// Close any open menus
|
||||
cy.log('[STEP 11] Closing menus');
|
||||
cy.get('body').type('{esc}{esc}');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Verify datetime cells exist
|
||||
cy.log('[STEP 12] Checking for datetime cells');
|
||||
cy.get('body').then($body => {
|
||||
const datetimeCells = $body.find('[data-testid^="datetime-cell-"]');
|
||||
if (datetimeCells.length > 0) {
|
||||
cy.log(`[STEP 13] Found ${datetimeCells.length} datetime cells`);
|
||||
|
||||
// Try to interact with the first datetime cell
|
||||
DateTimeSelectors.allDateTimeCells().first().scrollIntoView().click({ force: true });
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Check if picker opens
|
||||
cy.get('body').then($body => {
|
||||
if ($body.find('[data-testid="datetime-picker-popover"]').length > 0) {
|
||||
cy.log('[STEP 14] DateTime picker opened successfully');
|
||||
|
||||
// Enter a date
|
||||
const today = new Date();
|
||||
const dateStr = `${(today.getMonth() + 1).toString().padStart(2, '0')}/${today.getDate().toString().padStart(2, '0')}/${today.getFullYear()}`;
|
||||
|
||||
cy.log(`[STEP 15] Entering date: ${dateStr}`);
|
||||
DateTimeSelectors.dateTimeDateInput().clear().type(dateStr);
|
||||
DateTimeSelectors.dateTimeDateInput().type('{enter}');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
cy.log('[STEP 16] Date entered successfully');
|
||||
} else {
|
||||
cy.log('[STEP 14] DateTime picker did not open, but column was created');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cy.log('[STEP 13] DateTime cells not found, but column creation was attempted');
|
||||
}
|
||||
});
|
||||
|
||||
cy.log('[STEP 17] DateTime column test completed');
|
||||
});
|
||||
});
|
||||
});
|
||||
109
cypress/e2e/database/grid-edit-operations.cy.ts
Normal file
109
cypress/e2e/database/grid-edit-operations.cy.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthTestUtils } from '../../support/auth-utils';
|
||||
import {
|
||||
AddPageSelectors,
|
||||
DatabaseGridSelectors,
|
||||
waitForReactUpdate
|
||||
} from '../../support/selectors';
|
||||
|
||||
describe('Database Grid Edit Operations', () => {
|
||||
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
|
||||
|
||||
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')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
cy.viewport(1280, 720);
|
||||
});
|
||||
|
||||
it('should create a database grid page, refresh, edit first row, and verify the changes', () => {
|
||||
const testEmail = generateRandomEmail();
|
||||
|
||||
// Login
|
||||
cy.log('Step 1: Logging in to the application');
|
||||
cy.visit('/login', { failOnStatusCode: false });
|
||||
cy.wait(2000);
|
||||
|
||||
const authUtils = new AuthTestUtils();
|
||||
authUtils.signInWithTestUrl(testEmail).then(() => {
|
||||
// Wait for app to load
|
||||
cy.log('Step 2: Waiting for application to load');
|
||||
cy.url({ timeout: 30000 }).should('include', '/app');
|
||||
cy.wait(3000);
|
||||
|
||||
// Find the add page button
|
||||
cy.log('Step 3: Opening add page menu');
|
||||
AddPageSelectors.inlineAddButton().first().should('be.visible').click();
|
||||
|
||||
// Wait for dropdown menu to appear
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Click on Grid option to create a new grid database
|
||||
cy.log('Step 4: Creating a new Grid database');
|
||||
AddPageSelectors.addGridButton().should('be.visible').click();
|
||||
|
||||
// Wait for navigation to the new grid page
|
||||
cy.log('Step 5: Waiting for Grid database to be created');
|
||||
cy.wait(8000); // Give it time to create and navigate
|
||||
|
||||
// Get the current URL to navigate back after refresh
|
||||
cy.url().then((currentUrl) => {
|
||||
cy.log('Current Grid URL: ' + currentUrl);
|
||||
|
||||
// Refresh the page to ensure the grid database was properly saved
|
||||
cy.log('Step 6: Refreshing the page to verify grid database persistence');
|
||||
cy.reload();
|
||||
cy.wait(5000); // Wait for page to reload
|
||||
|
||||
// Verify we're still on the grid page
|
||||
cy.url().should('include', currentUrl.split('/').pop());
|
||||
|
||||
// Now verify the grid is loaded after refresh
|
||||
cy.log('Step 7: Verifying Grid database loaded after refresh');
|
||||
DatabaseGridSelectors.grid().should('be.visible');
|
||||
|
||||
// Try to interact with any cell that exists
|
||||
cy.log('Step 8: Looking for cells to edit');
|
||||
waitForReactUpdate(2000);
|
||||
|
||||
// Try to find the first cell and click it
|
||||
DatabaseGridSelectors.firstCell().then($cell => {
|
||||
cy.log('Found a cell, clicking to edit');
|
||||
cy.wrap($cell).click({ force: true });
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Type some text
|
||||
const testText = 'Test Edit ' + Date.now();
|
||||
cy.log('Typing test text: ' + testText);
|
||||
|
||||
// Try typing directly into the focused element
|
||||
cy.focused().type(testText);
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Press Enter to save
|
||||
cy.focused().type('{enter}');
|
||||
waitForReactUpdate(2000);
|
||||
|
||||
// Refresh again to verify the edit persists
|
||||
cy.log('Step 9: Refreshing to verify edit persistence');
|
||||
cy.reload();
|
||||
cy.wait(5000);
|
||||
|
||||
// Verify the text still appears in the grid after refresh
|
||||
cy.log('Step 10: Verifying the edit was saved and persists after refresh');
|
||||
DatabaseGridSelectors.grid().should('be.visible');
|
||||
DatabaseGridSelectors.grid().should('contain.text', testText.substring(0, 10));
|
||||
|
||||
cy.log('Test completed: Successfully created grid database, edited a cell, and verified persistence after refresh');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
82
cypress/e2e/database/single-select-column.cy.ts
Normal file
82
cypress/e2e/database/single-select-column.cy.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthTestUtils } from '../../support/auth-utils';
|
||||
import {
|
||||
AddPageSelectors,
|
||||
DatabaseGridSelectors,
|
||||
waitForReactUpdate
|
||||
} from '../../support/selectors';
|
||||
|
||||
describe('Single Select Column Type', () => {
|
||||
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
|
||||
const SINGLE_SELECT_FIELD_TYPE = 3; // From FieldType enum
|
||||
|
||||
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')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
cy.viewport(1280, 720);
|
||||
});
|
||||
|
||||
it('should create and edit basic grid cells', () => {
|
||||
const testEmail = generateRandomEmail();
|
||||
cy.log(`[TEST START] Third test - Test email: ${testEmail}`);
|
||||
|
||||
cy.log('[STEP 1] Visiting login page');
|
||||
cy.visit('/login', { failOnStatusCode: false });
|
||||
cy.wait(2000);
|
||||
|
||||
const authUtils = new AuthTestUtils();
|
||||
cy.log('[STEP 2] Starting authentication');
|
||||
authUtils.signInWithTestUrl(testEmail).then(() => {
|
||||
cy.log('[STEP 3] Authentication successful');
|
||||
cy.url({ timeout: 30000 }).should('include', '/app');
|
||||
cy.wait(3000);
|
||||
|
||||
// Create a new grid
|
||||
cy.log('[STEP 4] Creating new grid');
|
||||
AddPageSelectors.inlineAddButton().first().click();
|
||||
waitForReactUpdate(1000);
|
||||
AddPageSelectors.addGridButton().click();
|
||||
cy.wait(8000);
|
||||
|
||||
|
||||
// Verify cells exist
|
||||
DatabaseGridSelectors.cells().should('exist');
|
||||
|
||||
// Get all cells and verify interaction
|
||||
DatabaseGridSelectors.cells().then($cells => {
|
||||
cy.log(`[STEP 8] Found ${$cells.length} cells`);
|
||||
|
||||
// Click first cell
|
||||
cy.wrap($cells.first()).click();
|
||||
waitForReactUpdate(500);
|
||||
cy.focused().type('Cell 1 Data');
|
||||
cy.focused().type('{enter}');
|
||||
waitForReactUpdate(500);
|
||||
|
||||
// Verify data was entered
|
||||
cy.wrap($cells.first()).should('contain.text', 'Cell 1 Data');
|
||||
|
||||
// Click second cell if exists
|
||||
if ($cells.length > 1) {
|
||||
cy.wrap($cells.eq(1)).click();
|
||||
waitForReactUpdate(500);
|
||||
cy.focused().type('Option One');
|
||||
cy.focused().type('{enter}');
|
||||
waitForReactUpdate(500);
|
||||
|
||||
// Verify the new option 'Option One' exists in the cell
|
||||
cy.wrap($cells.eq(1)).should('contain.text', 'Option One');
|
||||
}
|
||||
|
||||
cy.log('[STEP 9] Cell interaction completed successfully');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -184,9 +184,167 @@ export const SidebarSelectors = {
|
||||
pageHeader: () => cy.get(byTestId('sidebar-page-header')),
|
||||
};
|
||||
|
||||
/**
|
||||
* Database Grid-related selectors
|
||||
*/
|
||||
export const DatabaseGridSelectors = {
|
||||
// Main grid container
|
||||
grid: () => cy.get(byTestId('database-grid')),
|
||||
|
||||
// Grid rows
|
||||
rows: () => cy.get('[data-testid^="grid-row-"]'),
|
||||
|
||||
// Get specific row by row ID
|
||||
rowById: (rowId: string) => cy.get(byTestId(`grid-row-${rowId}`)),
|
||||
|
||||
// Get first row
|
||||
firstRow: () => cy.get('[data-testid^="grid-row-"]').first(),
|
||||
|
||||
// Grid cells
|
||||
cells: () => cy.get('[data-testid^="grid-cell-"]'),
|
||||
|
||||
// Get specific cell by row ID and field ID
|
||||
cellByIds: (rowId: string, fieldId: string) => cy.get(byTestId(`grid-cell-${rowId}-${fieldId}`)),
|
||||
|
||||
// Get all cells in a specific row
|
||||
cellsInRow: (rowId: string) => cy.get(`[data-testid^="grid-cell-${rowId}-"]`),
|
||||
|
||||
// Get first cell
|
||||
firstCell: () => cy.get('[data-testid^="grid-cell-"]').first(),
|
||||
|
||||
// Get new row button (if exists)
|
||||
newRowButton: () => cy.get(byTestId('grid-new-row')),
|
||||
};
|
||||
|
||||
/**
|
||||
* Single Select Column selectors
|
||||
*/
|
||||
export const SingleSelectSelectors = {
|
||||
// Select option cell
|
||||
selectOptionCell: (rowId: string, fieldId: string) => cy.get(byTestId(`select-option-cell-${rowId}-${fieldId}`)),
|
||||
|
||||
// Select option in dropdown
|
||||
selectOption: (optionId: string) => cy.get(byTestId(`select-option-${optionId}`)),
|
||||
|
||||
// New property button in grid header
|
||||
newPropertyButton: () => cy.get(byTestId('grid-new-property-button')),
|
||||
|
||||
// All select option cells
|
||||
allSelectOptionCells: () => cy.get('[data-testid^="select-option-cell-"]'),
|
||||
|
||||
// Select option menu popover
|
||||
selectOptionMenu: () => cy.get(byTestId('select-option-menu')),
|
||||
};
|
||||
|
||||
/**
|
||||
* Grid Field/Column Header selectors
|
||||
*/
|
||||
export const GridFieldSelectors = {
|
||||
// Field header by field ID
|
||||
fieldHeader: (fieldId: string) => cy.get(byTestId(`grid-field-header-${fieldId}`)),
|
||||
|
||||
// All field headers
|
||||
allFieldHeaders: () => cy.get('[data-testid^="grid-field-header-"]'),
|
||||
|
||||
// Add select option button
|
||||
addSelectOptionButton: () => cy.get(byTestId('add-select-option')),
|
||||
};
|
||||
|
||||
/**
|
||||
* Add Page Actions selectors
|
||||
*/
|
||||
export const AddPageSelectors = {
|
||||
// Inline add page button
|
||||
inlineAddButton: () => cy.get(byTestId('inline-add-page')),
|
||||
|
||||
// Add grid button in dropdown
|
||||
addGridButton: () => cy.get(byTestId('add-grid-button')),
|
||||
|
||||
// Add AI chat button in dropdown
|
||||
addAIChatButton: () => cy.get(byTestId('add-ai-chat-button')),
|
||||
};
|
||||
|
||||
/**
|
||||
* Checkbox Column selectors
|
||||
*/
|
||||
export const CheckboxSelectors = {
|
||||
// Checkbox cell by row and field ID
|
||||
checkboxCell: (rowId: string, fieldId: string) => cy.get(byTestId(`checkbox-cell-${rowId}-${fieldId}`)),
|
||||
|
||||
// All checkbox cells
|
||||
allCheckboxCells: () => cy.get('[data-testid^="checkbox-cell-"]'),
|
||||
|
||||
// Checked icon
|
||||
checkedIcon: () => cy.get(byTestId('checkbox-checked-icon')),
|
||||
|
||||
// Unchecked icon
|
||||
uncheckedIcon: () => cy.get(byTestId('checkbox-unchecked-icon')),
|
||||
|
||||
// Get checkbox cell by checked state
|
||||
checkedCells: () => cy.get('[data-checked="true"]'),
|
||||
uncheckedCells: () => cy.get('[data-checked="false"]'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to wait for React to re-render after state changes
|
||||
*/
|
||||
/**
|
||||
* DateTime Column selectors
|
||||
*/
|
||||
export const DateTimeSelectors = {
|
||||
// DateTime cell by row and field ID
|
||||
dateTimeCell: (rowId: string, fieldId: string) => cy.get(byTestId(`datetime-cell-${rowId}-${fieldId}`)),
|
||||
|
||||
// All datetime cells
|
||||
allDateTimeCells: () => cy.get('[data-testid^="datetime-cell-"]'),
|
||||
|
||||
// DateTime picker popover
|
||||
dateTimePickerPopover: () => cy.get(byTestId('datetime-picker-popover')),
|
||||
|
||||
// DateTime date input field
|
||||
dateTimeDateInput: () => cy.get(byTestId('datetime-date-input')),
|
||||
|
||||
// DateTime time input field
|
||||
dateTimeTimeInput: () => cy.get(byTestId('datetime-time-input')),
|
||||
};
|
||||
|
||||
/**
|
||||
* Property Menu selectors
|
||||
*/
|
||||
export const PropertyMenuSelectors = {
|
||||
// Property type trigger button
|
||||
propertyTypeTrigger: () => cy.get(byTestId('property-type-trigger')),
|
||||
|
||||
// Property type option by field type number
|
||||
propertyTypeOption: (fieldType: number) => cy.get(byTestId(`property-type-option-${fieldType}`)),
|
||||
|
||||
// Grid new property button
|
||||
newPropertyButton: () => cy.get(byTestId('grid-new-property-button')),
|
||||
|
||||
// Edit property menu item
|
||||
editPropertyMenuItem: () => cy.get(byTestId('grid-field-edit-property')),
|
||||
};
|
||||
|
||||
/**
|
||||
* Field Types enum for database columns
|
||||
*/
|
||||
export const FieldType = {
|
||||
RichText: 0,
|
||||
Number: 1,
|
||||
DateTime: 2,
|
||||
SingleSelect: 3,
|
||||
MultiSelect: 4,
|
||||
Checkbox: 5,
|
||||
URL: 6,
|
||||
Checklist: 7,
|
||||
LastEditedTime: 8,
|
||||
CreatedTime: 9,
|
||||
Relation: 10,
|
||||
AISummaries: 11,
|
||||
AITranslations: 12,
|
||||
FileMedia: 14
|
||||
};
|
||||
|
||||
export function waitForReactUpdate(ms: number = 500) {
|
||||
return cy.wait(ms);
|
||||
}
|
||||
@@ -85,7 +85,10 @@ function AddPageActions({ view }: { view: View }) {
|
||||
{actions.map((action) => (
|
||||
<DropdownMenuItem
|
||||
key={action.label}
|
||||
data-testid={action.label === t('chat.newChat') ? 'add-ai-chat-button' : undefined}
|
||||
data-testid={
|
||||
action.label === t('chat.newChat') ? 'add-ai-chat-button' :
|
||||
action.label === t('grid.menuName') ? 'add-grid-button' : undefined
|
||||
}
|
||||
disabled={action.disabled}
|
||||
onSelect={() => {
|
||||
action.onSelect();
|
||||
|
||||
@@ -39,12 +39,14 @@ export function CheckboxCell({
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
data-testid={`checkbox-cell-${rowId}-${fieldId}`}
|
||||
data-checked={checkedRef.current}
|
||||
className={cn('relative flex h-full w-full text-lg text-text-action', readOnly ? '' : 'cursor-pointer')}
|
||||
>
|
||||
{checkedRef.current ? (
|
||||
<CheckboxCheckSvg className={'h-5 w-5'} />
|
||||
<CheckboxCheckSvg className={'h-5 w-5'} data-testid="checkbox-checked-icon" />
|
||||
) : (
|
||||
<CheckboxUncheckSvg className={'h-5 w-5 text-border-primary hover:text-border-primary-hover'} />
|
||||
<CheckboxUncheckSvg className={'h-5 w-5 text-border-primary hover:text-border-primary-hover'} data-testid="checkbox-unchecked-icon" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -45,6 +45,7 @@ export function DateTimeCell({
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid={`datetime-cell-${rowId}-${fieldId}`}
|
||||
style={style}
|
||||
className={cn(
|
||||
'flex gap-1',
|
||||
|
||||
@@ -181,6 +181,7 @@ function DateTimeCellPicker({
|
||||
className={'absolute left-0 top-0 z-[-1] h-full w-full bg-transparent'}
|
||||
/>
|
||||
<PopoverContent
|
||||
data-testid="datetime-picker-popover"
|
||||
avoidCollisions={true}
|
||||
{...(layout === DatabaseViewLayout.Calendar
|
||||
? {
|
||||
|
||||
@@ -167,6 +167,7 @@ function DateTimeInput({
|
||||
data-focused={focused}
|
||||
>
|
||||
<input
|
||||
data-testid="datetime-date-input"
|
||||
autoFocus={autoFocus}
|
||||
type={'text'}
|
||||
className={cn('flex-1', baseInputStyles)}
|
||||
@@ -194,6 +195,7 @@ function DateTimeInput({
|
||||
<>
|
||||
<Separator className={'!h-4'} orientation={'vertical'} />
|
||||
<input
|
||||
data-testid="datetime-time-input"
|
||||
className={cn(is12HourFormat ? 'w-[70px]' : 'w-[50px]', baseInputStyles)}
|
||||
type={'text'}
|
||||
placeholder={timePlaceholder}
|
||||
|
||||
@@ -64,6 +64,7 @@ export function SelectOptionCell({
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
data-testid={`select-option-cell-${rowId}-${fieldId}`}
|
||||
className={cn(
|
||||
'select-option-cell flex w-full items-center gap-1',
|
||||
isEmpty && placeholder ? 'text-text-tertiary' : '',
|
||||
|
||||
@@ -226,6 +226,7 @@ function SelectOptionCellMenu ({ open, onOpenChange, fieldId, rowId, selectOptio
|
||||
className={'absolute left-0 top-0 w-full h-full z-[-1]'}
|
||||
/>
|
||||
<PopoverContent
|
||||
data-testid="select-option-menu"
|
||||
side={'bottom'}
|
||||
align={'start'}
|
||||
onMouseDown={(e) => {
|
||||
|
||||
@@ -115,7 +115,11 @@ export function GridRowCell({ rowId, fieldId }: GridCellProps) {
|
||||
if (!field) return null;
|
||||
|
||||
return (
|
||||
<div ref={ref} className={cn('grid-cell flex w-full items-start overflow-hidden px-2 text-sm', paddingVertical)}>
|
||||
<div
|
||||
ref={ref}
|
||||
data-testid={`grid-cell-${rowId}-${fieldId}`}
|
||||
className={cn('grid-cell flex w-full items-start overflow-hidden px-2 text-sm', paddingVertical)}
|
||||
>
|
||||
<Component
|
||||
cell={cell}
|
||||
rowId={rowId}
|
||||
|
||||
@@ -157,6 +157,7 @@ function GridFieldMenu({
|
||||
<DropdownMenuGroup>
|
||||
{operations.map((operation, index) => (
|
||||
<DropdownMenuItem
|
||||
data-testid={operation.label === t('grid.field.editProperty') ? 'grid-field-edit-property' : undefined}
|
||||
onPointerMove={(e) => e.preventDefault()}
|
||||
onPointerEnter={(e) => e.preventDefault()}
|
||||
onPointerLeave={(e) => e.preventDefault()}
|
||||
|
||||
@@ -44,6 +44,7 @@ export function GridHeaderColumn({
|
||||
>
|
||||
<Tooltip disableHoverableContent delayDuration={500}>
|
||||
<TooltipTrigger
|
||||
data-testid={`grid-field-header-${fieldId}`}
|
||||
onClick={(e) => {
|
||||
if (readOnly) return;
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -12,6 +12,7 @@ function GridNewProperty () {
|
||||
} = useGridContext();
|
||||
|
||||
return <div
|
||||
data-testid="grid-new-property-button"
|
||||
onClick={() => {
|
||||
const id = onNewProperty(FieldType.RichText);
|
||||
|
||||
|
||||
@@ -220,6 +220,7 @@ function GridVirtualRow({
|
||||
</div>
|
||||
<div
|
||||
ref={rowRef}
|
||||
data-testid={`grid-row-${rowId}`}
|
||||
className={cn(
|
||||
'grid-table-row-content relative flex min-h-[36px]',
|
||||
state.type === GridDragState.DRAGGING && 'opacity-40'
|
||||
|
||||
@@ -70,7 +70,7 @@ export function PropertySelectTrigger({ fieldId, disabled }: { fieldId: string;
|
||||
return (
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuSub open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuSubTrigger disabled={disabled}>
|
||||
<DropdownMenuSubTrigger data-testid="property-type-trigger" disabled={disabled}>
|
||||
<FieldTypeIcon type={type} />
|
||||
<FieldLabel type={type} />
|
||||
</DropdownMenuSubTrigger>
|
||||
@@ -80,6 +80,7 @@ export function PropertySelectTrigger({ fieldId, disabled }: { fieldId: string;
|
||||
<Tooltip key={property}>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
data-testid={`property-type-option-${property}`}
|
||||
onSelect={(e) => {
|
||||
handleSelect(property);
|
||||
if ([FieldType.AITranslations, FieldType.Relation].includes(property)) {
|
||||
|
||||
@@ -28,6 +28,7 @@ function AddAnOption ({ options, onAdd }: {
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
data-testid="add-select-option"
|
||||
onPointerLeave={e => {
|
||||
if (editing) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -119,6 +119,7 @@ function Option({
|
||||
<div
|
||||
ref={innerRef}
|
||||
key={option.id}
|
||||
data-testid={`select-option-${option.id}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (onSelect) {
|
||||
|
||||
@@ -18,7 +18,7 @@ export function Grid () {
|
||||
|
||||
return (
|
||||
<GridProvider>
|
||||
<div className={`database-grid relative grid-table-${viewId} flex w-full flex-1 flex-col`}>
|
||||
<div data-testid="database-grid" className={`database-grid relative grid-table-${viewId} flex w-full flex-1 flex-col`}>
|
||||
<GridVirtualizer
|
||||
columns={fields}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user