Files
AppFlowy-Web/cypress/e2e/database/sort.cy.ts
Nathan.fooo 6d33312774 Database blob (#212)
* Integrate database blob diff and lazy row loading

* chore: load blob

* chore: fix cell render

* chore: create csv api endpoint

* chore: add test

* chore: fix lint

* chore: add test

* chore: add test

* chore: fix test

* chore: stable scroll test

* chore: fix filter test

* fix: row detail modal more actions menu and add test data-testid

- Fix critical bug in DatabaseRowModal where clicking more actions button
  closed the modal instead of opening the dropdown menu (removed erroneous
  onClick handler from DropdownMenuTrigger)
- Add data-testid attributes for testing:
  - row-detail-more-actions: more actions button
  - row-detail-duplicate: duplicate menu item
  - row-detail-delete: delete menu item
  - row-title-input: row title input field
- Update row-detail-helpers.ts selectors to use new data-testid attributes
- Fix row-detail.cy.ts tests to use correct selectors and test logic

* chore: fix sort

* chore: fix sort

* chore: separate test

* chore: stable test

* chore: fix test

* chore: separate test
2026-01-09 00:16:02 +08:00

418 lines
14 KiB
TypeScript

/**
* Database Sort Tests (Desktop Parity)
*
* Tests sorting functionality for database views.
* Mirrors tests from: database_sort_test.dart
*/
import 'cypress-real-events';
import {
loginAndCreateGrid,
setupFilterTest,
typeTextIntoCell,
getPrimaryFieldId,
assertRowCount,
} from '../../support/filter-test-helpers';
import {
addFieldWithType,
addRows,
toggleCheckbox,
getCellTextContent,
changeFieldTypeById,
FieldType,
} from '../../support/field-type-helpers';
import {
setupSortTest,
addSortByFieldName,
openSortMenu,
toggleSortDirection,
changeSortDirection,
deleteSort,
deleteAllSorts,
assertRowOrder,
closeSortMenu,
SortDirection,
SortSelectors,
} from '../../support/sort-test-helpers';
import {
DatabaseFilterSelectors,
DatabaseGridSelectors,
GridFieldSelectors,
PropertyMenuSelectors,
RowControlsSelectors,
waitForReactUpdate,
} from '../../support/selectors';
import { generateRandomEmail } from '../../support/test-config';
describe('Database Sort Tests (Desktop Parity)', () => {
beforeEach(() => {
setupSortTest();
});
describe('Basic Sort Operations', () => {
it('text sort - ascending', () => {
const email = generateRandomEmail();
loginAndCreateGrid(email).then(() => {
getPrimaryFieldId().then((primaryFieldId) => {
// Add rows with data: C, A, B (out of order)
addRows(2); // Now have 3 rows total
waitForReactUpdate(500);
typeTextIntoCell(primaryFieldId, 0, 'C');
typeTextIntoCell(primaryFieldId, 1, 'A');
typeTextIntoCell(primaryFieldId, 2, 'B');
waitForReactUpdate(500);
// Add sort by Name field (ascending by default)
addSortByFieldName('Name');
waitForReactUpdate(1000);
// Verify order is now A, B, C
assertRowOrder(primaryFieldId, ['A', 'B', 'C']);
});
});
});
it('text sort - descending', () => {
const email = generateRandomEmail();
loginAndCreateGrid(email).then(() => {
getPrimaryFieldId().then((primaryFieldId) => {
// Add rows with data
addRows(2);
waitForReactUpdate(500);
typeTextIntoCell(primaryFieldId, 0, 'A');
typeTextIntoCell(primaryFieldId, 1, 'C');
typeTextIntoCell(primaryFieldId, 2, 'B');
waitForReactUpdate(500);
// Add sort by Name field
addSortByFieldName('Name');
waitForReactUpdate(1000);
// Toggle to descending
openSortMenu();
toggleSortDirection(0);
closeSortMenu();
waitForReactUpdate(500);
// Verify order is now C, B, A
assertRowOrder(primaryFieldId, ['C', 'B', 'A']);
});
});
});
it('number sort - ascending', () => {
const email = generateRandomEmail();
loginAndCreateGrid(email).then(() => {
getPrimaryFieldId().then((primaryFieldId) => {
// Add a Number field (default name is "Numbers") and get its ID directly
addFieldWithType(FieldType.Number).then((numberFieldId) => {
cy.log(`Number field ID: ${numberFieldId}`);
waitForReactUpdate(500);
// Add rows and enter numbers out of order
addRows(2);
waitForReactUpdate(500);
typeTextIntoCell(primaryFieldId, 0, 'Row1');
typeTextIntoCell(primaryFieldId, 1, 'Row2');
typeTextIntoCell(primaryFieldId, 2, 'Row3');
waitForReactUpdate(300);
// Type numbers into the Number field cells
typeTextIntoCell(numberFieldId, 0, '30');
waitForReactUpdate(300);
typeTextIntoCell(numberFieldId, 1, '10');
waitForReactUpdate(300);
typeTextIntoCell(numberFieldId, 2, '20');
waitForReactUpdate(500);
// Verify numbers were entered by checking cell content
cy.log('Verifying numbers were entered...');
DatabaseGridSelectors.dataRowCellsForField(numberFieldId)
.eq(0)
.should('contain.text', '30');
// Add sort by the Number field (default name is "Numbers")
addSortByFieldName('Numbers');
waitForReactUpdate(1000);
// Verify order is now Row2 (10), Row3 (20), Row1 (30)
assertRowOrder(primaryFieldId, ['Row2', 'Row3', 'Row1']);
});
});
});
});
it('number sort - descending', () => {
const email = generateRandomEmail();
loginAndCreateGrid(email).then(() => {
getPrimaryFieldId().then((primaryFieldId) => {
// Add a Number field and get its ID directly
addFieldWithType(FieldType.Number).then((numberFieldId) => {
cy.log(`Number field ID: ${numberFieldId}`);
waitForReactUpdate(500);
// Add rows
addRows(2);
waitForReactUpdate(500);
typeTextIntoCell(primaryFieldId, 0, 'Row1');
typeTextIntoCell(primaryFieldId, 1, 'Row2');
typeTextIntoCell(primaryFieldId, 2, 'Row3');
waitForReactUpdate(300);
typeTextIntoCell(numberFieldId, 0, '10');
waitForReactUpdate(300);
typeTextIntoCell(numberFieldId, 1, '30');
waitForReactUpdate(300);
typeTextIntoCell(numberFieldId, 2, '20');
waitForReactUpdate(500);
// Verify numbers were entered
DatabaseGridSelectors.dataRowCellsForField(numberFieldId)
.eq(0)
.should('contain.text', '10');
// Add sort by Number field
addSortByFieldName('Numbers');
waitForReactUpdate(500);
// Toggle to descending
openSortMenu();
toggleSortDirection(0);
closeSortMenu();
waitForReactUpdate(500);
// Verify order is now Row2 (30), Row3 (20), Row1 (10)
assertRowOrder(primaryFieldId, ['Row2', 'Row3', 'Row1']);
});
});
});
});
it('checkbox sort', () => {
const email = generateRandomEmail();
loginAndCreateGrid(email).then(() => {
getPrimaryFieldId().then((primaryFieldId) => {
// Add a Checkbox field (default name is "Checkbox")
addFieldWithType(FieldType.Checkbox);
waitForReactUpdate(1000);
// Get the Checkbox field ID
cy.get('[data-testid^="grid-field-header-"]')
.last()
.invoke('attr', 'data-testid')
.then((testId) => {
const checkboxFieldId = testId?.replace('grid-field-header-', '') || '';
// Add rows
addRows(2);
waitForReactUpdate(500);
typeTextIntoCell(primaryFieldId, 0, 'Checked');
typeTextIntoCell(primaryFieldId, 1, 'Unchecked');
typeTextIntoCell(primaryFieldId, 2, 'Also Checked');
waitForReactUpdate(500);
// Check first and third rows
toggleCheckbox(checkboxFieldId, 0);
waitForReactUpdate(300);
toggleCheckbox(checkboxFieldId, 2);
waitForReactUpdate(500);
// Add sort by Checkbox field
addSortByFieldName('Checkbox');
waitForReactUpdate(1000);
// Unchecked should be first (false < true in default ascending)
// Verify "Unchecked" row is first
DatabaseGridSelectors.dataRowCellsForField(primaryFieldId)
.first()
.should('contain.text', 'Unchecked');
});
});
});
});
});
describe('Multiple Sorts', () => {
it('multiple sorts - checkbox then text', () => {
const email = generateRandomEmail();
loginAndCreateGrid(email).then(() => {
getPrimaryFieldId().then((primaryFieldId) => {
// Add a Checkbox field (default name is "Checkbox")
addFieldWithType(FieldType.Checkbox);
waitForReactUpdate(1000);
cy.get('[data-testid^="grid-field-header-"]')
.last()
.invoke('attr', 'data-testid')
.then((testId) => {
const checkboxFieldId = testId?.replace('grid-field-header-', '') || '';
// Check current row count and add rows if needed
// Default grid has 3 rows, we need 4 for this test
DatabaseGridSelectors.dataRows().then(($rows) => {
const currentRows = $rows.length;
const targetRows = 4;
const rowsToAdd = Math.max(0, targetRows - currentRows);
if (rowsToAdd > 0) {
addRows(rowsToAdd);
}
waitForReactUpdate(500);
// Set up data: mix of checked/unchecked with various names
typeTextIntoCell(primaryFieldId, 0, 'Beta');
typeTextIntoCell(primaryFieldId, 1, 'Alpha');
typeTextIntoCell(primaryFieldId, 2, 'Delta');
typeTextIntoCell(primaryFieldId, 3, 'Charlie');
waitForReactUpdate(500);
// Check rows 0 and 2 (Beta and Delta)
toggleCheckbox(checkboxFieldId, 0);
waitForReactUpdate(300);
toggleCheckbox(checkboxFieldId, 2);
waitForReactUpdate(500);
// Add first sort by checkbox
addSortByFieldName('Checkbox');
waitForReactUpdate(500);
// Add second sort by name
openSortMenu();
waitForReactUpdate(300);
SortSelectors.addSortButton().click({ force: true });
waitForReactUpdate(500);
DatabaseFilterSelectors.propertyItemByName('Name').click({ force: true });
waitForReactUpdate(1000);
// Expected order (only first 4 rows):
// First unchecked (Alpha, Charlie) sorted alphabetically
// Then checked (Beta, Delta) sorted alphabetically
assertRowOrder(primaryFieldId, ['Alpha', 'Charlie', 'Beta', 'Delta']);
});
});
});
});
});
});
describe('Sort Management', () => {
it('delete sort', () => {
const email = generateRandomEmail();
loginAndCreateGrid(email).then(() => {
getPrimaryFieldId().then((primaryFieldId) => {
addRows(2);
waitForReactUpdate(500);
typeTextIntoCell(primaryFieldId, 0, 'C');
typeTextIntoCell(primaryFieldId, 1, 'A');
typeTextIntoCell(primaryFieldId, 2, 'B');
waitForReactUpdate(500);
// Add sort
addSortByFieldName('Name');
waitForReactUpdate(1000);
// Verify sorted
assertRowOrder(primaryFieldId, ['A', 'B', 'C']);
// Delete sort
openSortMenu();
deleteSort(0);
waitForReactUpdate(500);
// Sort condition chip should not exist
SortSelectors.sortCondition().should('not.exist');
});
});
});
it('delete all sorts', () => {
const email = generateRandomEmail();
loginAndCreateGrid(email).then(() => {
getPrimaryFieldId().then((primaryFieldId) => {
// Add Number field (default name is "Numbers")
addFieldWithType(FieldType.Number);
waitForReactUpdate(1000);
cy.get('[data-testid^="grid-field-header-"]')
.last()
.invoke('attr', 'data-testid')
.then((testId) => {
const numberFieldId = testId?.replace('grid-field-header-', '') || '';
addRows(2);
waitForReactUpdate(500);
typeTextIntoCell(primaryFieldId, 0, 'Row1');
typeTextIntoCell(primaryFieldId, 1, 'Row2');
typeTextIntoCell(primaryFieldId, 2, 'Row3');
typeTextIntoCell(numberFieldId, 0, '3');
typeTextIntoCell(numberFieldId, 1, '1');
typeTextIntoCell(numberFieldId, 2, '2');
waitForReactUpdate(500);
// Add multiple sorts
addSortByFieldName('Name');
waitForReactUpdate(500);
// Add second sort
openSortMenu();
SortSelectors.addSortButton().click({ force: true });
waitForReactUpdate(500);
DatabaseFilterSelectors.propertyItemByName('Numbers').click({ force: true });
waitForReactUpdate(500);
// Delete all sorts
openSortMenu();
deleteAllSorts();
waitForReactUpdate(500);
// Sort condition chip should not exist
SortSelectors.sortCondition().should('not.exist');
});
});
});
});
it('edit field name updates sort display', () => {
const email = generateRandomEmail();
loginAndCreateGrid(email).then(() => {
getPrimaryFieldId().then((primaryFieldId) => {
addRows(1);
waitForReactUpdate(500);
typeTextIntoCell(primaryFieldId, 0, 'A');
typeTextIntoCell(primaryFieldId, 1, 'B');
waitForReactUpdate(500);
// Add sort by Name
addSortByFieldName('Name');
waitForReactUpdate(1000);
// Rename the Name field to "Title"
// Use .last() because there can be both sticky and regular headers
GridFieldSelectors.fieldHeader(primaryFieldId).last().click({ force: true });
waitForReactUpdate(500);
PropertyMenuSelectors.editPropertyMenuItem().first().click({ force: true });
waitForReactUpdate(500);
// Find the name input and change it
cy.get('input[value="Name"]').clear().type('Title', { delay: 30 });
cy.get('body').type('{esc}');
waitForReactUpdate(500);
// Verify sort still works and shows updated field name
SortSelectors.sortCondition().should('exist');
// The sort panel should show "Title" now
openSortMenu();
cy.get('[data-radix-popper-content-wrapper]').last().should('contain.text', 'Title');
});
});
});
});
});