mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2025-11-29 10:47:56 +08:00
232 lines
9.8 KiB
TypeScript
232 lines
9.8 KiB
TypeScript
import { v4 as uuidv4 } from 'uuid';
|
|
import { AuthTestUtils } from '../../../support/auth-utils';
|
|
import { getSlashMenuItemName } from '../../../support/i18n-constants';
|
|
import {
|
|
AddPageSelectors,
|
|
DatabaseGridSelectors,
|
|
EditorSelectors,
|
|
ModalSelectors,
|
|
SlashCommandSelectors,
|
|
waitForReactUpdate
|
|
} from '../../../support/selectors';
|
|
|
|
describe('Embedded Database - Bottom Scroll Preservation', () => {
|
|
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') ||
|
|
err.message.includes('Cannot resolve a DOM point from Slate point') ||
|
|
err.message.includes('No range and node found')) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
cy.viewport(1280, 720);
|
|
});
|
|
|
|
it('should preserve scroll position when creating grid database at bottom of long document', () => {
|
|
const testEmail = generateRandomEmail();
|
|
|
|
cy.task('log', `[TEST START] Testing scroll preservation when creating database at bottom - Test email: ${testEmail}`);
|
|
|
|
// Step 1: Login
|
|
cy.task('log', '[STEP 1] Visiting login page');
|
|
cy.visit('/login', { failOnStatusCode: false });
|
|
cy.wait(2000);
|
|
|
|
const authUtils = new AuthTestUtils();
|
|
cy.task('log', '[STEP 2] Starting authentication');
|
|
authUtils.signInWithTestUrl(testEmail).then(() => {
|
|
cy.task('log', '[STEP 3] Authentication successful');
|
|
cy.url({ timeout: 30000 }).should('include', '/app');
|
|
cy.wait(3000);
|
|
|
|
// Step 2: Create a new document
|
|
cy.task('log', '[STEP 4] Creating new document');
|
|
AddPageSelectors.inlineAddButton().first().as('addBtn');
|
|
cy.get('@addBtn').should('be.visible').click();
|
|
waitForReactUpdate(1000);
|
|
cy.get('[role="menuitem"]').first().as('menuItem');
|
|
cy.get('@menuItem').click();
|
|
waitForReactUpdate(1000);
|
|
|
|
// Handle the new page modal if it appears
|
|
cy.get('body').then(($body) => {
|
|
if ($body.find('[data-testid="new-page-modal"]').length > 0) {
|
|
cy.task('log', '[STEP 4.1] Handling new page modal');
|
|
ModalSelectors.newPageModal().should('be.visible').within(() => {
|
|
ModalSelectors.spaceItemInModal().first().as('spaceItem');
|
|
cy.get('@spaceItem').click();
|
|
waitForReactUpdate(500);
|
|
cy.contains('button', 'Add').click();
|
|
});
|
|
cy.wait(3000);
|
|
} else {
|
|
cy.wait(3000);
|
|
}
|
|
});
|
|
|
|
// Step 3: Wait for editor to be available and stable
|
|
cy.task('log', '[STEP 5] Waiting for editor to be available');
|
|
EditorSelectors.firstEditor().should('exist', { timeout: 15000 });
|
|
waitForReactUpdate(2000); // Give extra time for editor to stabilize
|
|
|
|
// Step 4: Add many lines to exceed screen height
|
|
cy.task('log', '[STEP 6] Adding multiple lines to exceed screen height');
|
|
|
|
// Click editor to focus it
|
|
EditorSelectors.firstEditor().click({ force: true });
|
|
waitForReactUpdate(500);
|
|
|
|
// Build text content with 30 lines (enough to exceed viewport)
|
|
let textContent = '';
|
|
for (let i = 1; i <= 30; i++) {
|
|
textContent += `Line ${i} - This is a longer line of text to ensure we have enough content to scroll{enter}`;
|
|
}
|
|
|
|
cy.task('log', '[STEP 6.1] Typing 30 lines of content');
|
|
// Use cy.focused() to type - more stable than re-querying editor element
|
|
cy.focused().type(textContent, { delay: 1 });
|
|
|
|
cy.task('log', '[STEP 6.2] Content added successfully');
|
|
waitForReactUpdate(2000);
|
|
|
|
// Step 5: Get the scroll container and record initial state
|
|
cy.task('log', '[STEP 7] Finding scroll container');
|
|
cy.get('.appflowy-scroll-container').first().as('scrollContainer');
|
|
|
|
// Step 6: Scroll to the bottom
|
|
cy.task('log', '[STEP 8] Scrolling to bottom of document');
|
|
cy.get('@scrollContainer').then(($container) => {
|
|
const scrollHeight = $container[0].scrollHeight;
|
|
const clientHeight = $container[0].clientHeight;
|
|
const scrollToPosition = scrollHeight - clientHeight;
|
|
|
|
cy.task('log', `[STEP 8.1] Scroll metrics: scrollHeight=${scrollHeight}, clientHeight=${clientHeight}, scrollToPosition=${scrollToPosition}`);
|
|
|
|
// Scroll to bottom
|
|
cy.get('@scrollContainer').scrollTo(0, scrollToPosition);
|
|
waitForReactUpdate(500);
|
|
|
|
// Verify we're at the bottom
|
|
cy.get('@scrollContainer').then(($cont) => {
|
|
const currentScrollTop = $cont[0].scrollTop;
|
|
cy.task('log', `[STEP 8.2] Current scroll position after scrolling: ${currentScrollTop}`);
|
|
|
|
// Allow some tolerance (within 50px of bottom)
|
|
expect(currentScrollTop).to.be.greaterThan(scrollToPosition - 50);
|
|
});
|
|
});
|
|
|
|
// Step 7: Store the scroll position before opening slash menu
|
|
let scrollPositionBeforeSlashMenu = 0;
|
|
|
|
cy.get('@scrollContainer').then(($container) => {
|
|
scrollPositionBeforeSlashMenu = $container[0].scrollTop;
|
|
cy.task('log', `[STEP 9] Scroll position before opening slash menu: ${scrollPositionBeforeSlashMenu}`);
|
|
});
|
|
|
|
// Step 8: Open slash menu at the bottom
|
|
cy.task('log', '[STEP 10] Opening slash menu at bottom');
|
|
// Click editor at the end and type slash
|
|
EditorSelectors.firstEditor().click().type('{enter}/');
|
|
waitForReactUpdate(500);
|
|
|
|
// Step 9: Verify slash menu is visible
|
|
cy.task('log', '[STEP 11] Verifying slash menu is visible');
|
|
SlashCommandSelectors.slashPanel().should('be.visible');
|
|
|
|
// Step 10: Check that scroll position is preserved after opening slash menu
|
|
cy.get('@scrollContainer').then(($container) => {
|
|
const scrollAfterSlashMenu = $container[0].scrollTop;
|
|
cy.task('log', `[STEP 11.1] Scroll position after opening slash menu: ${scrollAfterSlashMenu}`);
|
|
|
|
// Allow some tolerance (within 100px) since the menu might cause minor layout shifts
|
|
const scrollDifference = Math.abs(scrollAfterSlashMenu - scrollPositionBeforeSlashMenu);
|
|
cy.task('log', `[STEP 11.2] Scroll difference: ${scrollDifference}px`);
|
|
|
|
// The scroll should not jump to the top (which would be < 1000)
|
|
// It should stay near the bottom
|
|
expect(scrollAfterSlashMenu).to.be.greaterThan(scrollPositionBeforeSlashMenu - 200);
|
|
|
|
if (scrollDifference > 100) {
|
|
cy.task('log', `[WARNING] Scroll position changed by ${scrollDifference}px when opening slash menu`);
|
|
}
|
|
});
|
|
|
|
// Step 11: Select Grid option from slash menu
|
|
cy.task('log', '[STEP 12] Selecting Grid option from slash menu');
|
|
let scrollBeforeGridCreation = 0;
|
|
|
|
cy.get('@scrollContainer').then(($container) => {
|
|
scrollBeforeGridCreation = $container[0].scrollTop;
|
|
cy.task('log', `[STEP 12.1] Scroll position before creating grid: ${scrollBeforeGridCreation}`);
|
|
});
|
|
|
|
SlashCommandSelectors.slashPanel().within(() => {
|
|
SlashCommandSelectors.slashMenuItem(getSlashMenuItemName('grid')).first().as('gridMenuItem');
|
|
cy.get('@gridMenuItem').should('be.visible').click();
|
|
});
|
|
|
|
waitForReactUpdate(2000);
|
|
|
|
// Step 12: Verify the modal opened (database opens in a modal)
|
|
cy.task('log', '[STEP 13] Verifying database modal opened');
|
|
cy.get('[role="dialog"]', { timeout: 10000 }).should('be.visible');
|
|
|
|
// Step 13: CRITICAL CHECK - Verify scroll position is preserved after creating database
|
|
cy.task('log', '[STEP 14] CRITICAL: Verifying scroll position after creating database');
|
|
cy.get('@scrollContainer').then(($container) => {
|
|
const scrollAfterGridCreation = $container[0].scrollTop;
|
|
const scrollHeight = $container[0].scrollHeight;
|
|
const clientHeight = $container[0].clientHeight;
|
|
|
|
cy.task('log', `[STEP 14.1] Scroll position after creating grid: ${scrollAfterGridCreation}`);
|
|
cy.task('log', `[STEP 14.2] scrollHeight: ${scrollHeight}, clientHeight: ${clientHeight}`);
|
|
|
|
const scrollDifference = Math.abs(scrollAfterGridCreation - scrollBeforeGridCreation);
|
|
cy.task('log', `[STEP 14.3] Scroll difference after grid creation: ${scrollDifference}px`);
|
|
|
|
// CRITICAL ASSERTION: The document should NOT scroll to the top
|
|
// If it scrolled to top, scrollAfterGridCreation would be close to 0
|
|
// We expect it to stay near the bottom
|
|
expect(scrollAfterGridCreation).to.be.greaterThan(scrollBeforeGridCreation - 300);
|
|
|
|
// Also verify it's not at the very top
|
|
expect(scrollAfterGridCreation).to.be.greaterThan(500);
|
|
|
|
if (scrollAfterGridCreation < 500) {
|
|
cy.task('log', `[CRITICAL FAILURE] Document scrolled to top! Position: ${scrollAfterGridCreation}`);
|
|
throw new Error(`Document scrolled to top (position: ${scrollAfterGridCreation}) when creating grid at bottom`);
|
|
}
|
|
|
|
if (scrollDifference > 300) {
|
|
cy.task('log', `[WARNING] Large scroll change detected: ${scrollDifference}px`);
|
|
} else {
|
|
cy.task('log', `[SUCCESS] Scroll position preserved! Difference: ${scrollDifference}px`);
|
|
}
|
|
});
|
|
|
|
// Step 14: Close the modal and verify final state
|
|
cy.task('log', '[STEP 15] Closing database modal');
|
|
cy.get('[role="dialog"]').within(() => {
|
|
cy.get('button').first().click(); // Click close button
|
|
});
|
|
|
|
waitForReactUpdate(1000);
|
|
|
|
// Step 15: Verify the grid database was actually created in the document
|
|
cy.task('log', '[STEP 16] Verifying grid database exists in document');
|
|
cy.get('[class*="appflowy-database"]').should('exist');
|
|
|
|
DatabaseGridSelectors.grid().should('exist');
|
|
|
|
cy.task('log', '[TEST COMPLETE] Scroll preservation test passed successfully');
|
|
});
|
|
});
|
|
});
|