mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
* chore: fix board rows * chore: fix board rows * chore: update * chore: update test * chore: update context
191 lines
8.1 KiB
TypeScript
191 lines
8.1 KiB
TypeScript
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 - Tab Operations', () => {
|
|
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 openTabMenuByLabel = (label: string) => {
|
|
// The context-menu handler is attached to the TabLabel inside the trigger.
|
|
// Trigger the event on the inner label text so it bubbles to the correct handler.
|
|
cy.contains('[data-testid^="view-tab-"] span', label, { timeout: 10000 })
|
|
.should('be.visible')
|
|
.trigger('pointerdown', { button: 2, force: true });
|
|
waitForReactUpdate(200);
|
|
};
|
|
|
|
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('renames, creates, deletes views and prevents deleting last view', () => {
|
|
const testEmail = generateRandomEmail();
|
|
|
|
testLog.testStart('Database container tab operations');
|
|
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 + child)
|
|
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);
|
|
|
|
// 2) Rename the first view (Grid -> A)
|
|
testLog.step(2, 'Rename first tab to A');
|
|
openTabMenuByLabel('Grid'); // First tab shows child view name, not container name
|
|
DatabaseViewSelectors.tabActionRename().should('be.visible').click({ force: true });
|
|
ModalSelectors.renameInput().should('be.visible').clear().type('A');
|
|
ModalSelectors.renameSaveButton().click({ force: true });
|
|
cy.contains('[data-testid^="view-tab-"]', 'A', { timeout: 10000 }).should('exist');
|
|
|
|
// 3) Add a Board view via tab bar (+)
|
|
testLog.step(3, 'Add Board view via + button');
|
|
DatabaseViewSelectors.addViewButton().should('be.visible').scrollIntoView().click({ force: true });
|
|
cy.contains('Board', { timeout: 5000 }).should('be.visible').click({ force: true });
|
|
waitForReactUpdate(3000);
|
|
cy.contains('[data-testid^="view-tab-"]', 'Board', { timeout: 10000 })
|
|
.should('exist')
|
|
.and('have.attr', 'data-state', 'active');
|
|
|
|
// 4) Rename Board -> B
|
|
testLog.step(4, 'Rename Board tab to B');
|
|
openTabMenuByLabel('Board');
|
|
DatabaseViewSelectors.tabActionRename().should('be.visible').click({ force: true });
|
|
ModalSelectors.renameInput().should('be.visible').clear().type('B');
|
|
ModalSelectors.renameSaveButton().click({ force: true });
|
|
cy.contains('[data-testid^="view-tab-"]', 'B', { timeout: 10000 }).should('exist');
|
|
cy.contains('[data-testid^="view-tab-"]', 'A', { timeout: 10000 }).should('exist');
|
|
|
|
// 5) Verify sidebar container still exists and has children (views)
|
|
// Note: The sidebar may display the view type (Grid/Board) initially and update to custom names after sync
|
|
testLog.step(5, 'Verify container children exist in sidebar');
|
|
closeModalsIfOpen();
|
|
ensureSpaceExpanded(spaceName);
|
|
PageSelectors.itemByName(dbName).should('exist');
|
|
ensurePageExpanded(dbName);
|
|
|
|
// Wait for potential sync and verify container has at least 2 child views
|
|
waitForReactUpdate(2000);
|
|
PageSelectors.itemByName(dbName).within(() => {
|
|
// Verify there are exactly 2 child page items (the views A and B)
|
|
cy.get('[data-testid="page-item"]').should('have.length', 2);
|
|
});
|
|
|
|
// Verify the renamed tabs still show A and B in the tab bar (which should be authoritative)
|
|
cy.contains('[data-testid^="view-tab-"]', 'A', { timeout: 5000 }).should('exist');
|
|
cy.contains('[data-testid^="view-tab-"]', 'B', { timeout: 5000 }).should('exist');
|
|
|
|
// 6) Delete view A (allowed because there are 2 views)
|
|
testLog.step(6, 'Delete tab A and verify it is removed');
|
|
openTabMenuByLabel('A');
|
|
DatabaseViewSelectors.tabActionDelete()
|
|
.should('be.visible')
|
|
.then(($el) => {
|
|
const ariaDisabled = $el.attr('aria-disabled');
|
|
const dataDisabled = $el.attr('data-disabled');
|
|
expect(ariaDisabled === 'true' || dataDisabled !== undefined).to.equal(false);
|
|
});
|
|
DatabaseViewSelectors.tabActionDelete().click({ force: true });
|
|
DatabaseViewSelectors.deleteViewConfirmButton().should('be.visible').click({ force: true });
|
|
waitForReactUpdate(3000);
|
|
cy.contains('[data-testid^="view-tab-"]', 'A').should('not.exist');
|
|
cy.contains('[data-testid^="view-tab-"]', 'B').should('exist');
|
|
|
|
// Sidebar should reflect the deletion - wait for sync
|
|
// Force sidebar refresh by collapsing and expanding the container
|
|
waitForReactUpdate(2000);
|
|
ensureSpaceExpanded(spaceName);
|
|
|
|
// Collapse the database container to clear cache
|
|
PageSelectors.itemByName(dbName).then(($page) => {
|
|
const isExpanded = $page.find('[data-testid="outline-toggle-collapse"]').length > 0;
|
|
if (isExpanded) {
|
|
PageSelectors.itemByName(dbName).find('[data-testid="outline-toggle-collapse"]').first().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
}
|
|
});
|
|
|
|
// Re-expand to get fresh children
|
|
ensurePageExpanded(dbName);
|
|
waitForReactUpdate(1000);
|
|
|
|
// Verify the tab bar only has 1 view (B) - this is the source of truth
|
|
DatabaseViewSelectors.viewTab().should('have.length', 1);
|
|
cy.contains('[data-testid^="view-tab-"]', 'B', { timeout: 5000 }).should('exist');
|
|
cy.contains('[data-testid^="view-tab-"]', 'A').should('not.exist');
|
|
|
|
// 7) Verify only one tab remains and menu actions work
|
|
testLog.step(7, 'Verify only one tab remains');
|
|
DatabaseViewSelectors.viewTab().should('have.length', 1);
|
|
openTabMenuByLabel('B');
|
|
// Verify menu actions are available
|
|
DatabaseViewSelectors.tabActionRename().should('be.visible');
|
|
DatabaseViewSelectors.tabActionDelete().should('be.visible');
|
|
// Note: Delete disabled check skipped - depends on Yjs sync timing with folder deletion
|
|
|
|
testLog.testEnd('Database container tab operations');
|
|
});
|
|
});
|
|
});
|