Skip to content

Commit 3ff0728

Browse files
authored
fix: invalid hook (#236)
* fix: invalid hook * chore: fix lint * fix: person filter * fix: test
1 parent 17d18e8 commit 3ff0728

File tree

5 files changed

+536
-20
lines changed

5 files changed

+536
-20
lines changed
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
/**
2+
* Test for Person Cell in Published/Template Pages
3+
*
4+
* This test verifies that:
5+
* 1. Person cells render correctly in published (read-only) views
6+
* 2. No React context errors occur when viewing templates
7+
* 3. The useMentionableUsers hook handles publish mode gracefully
8+
*
9+
* These tests were created to prevent regression of issues where:
10+
* - useCurrentWorkspaceId threw errors in publish view (no AppProvider)
11+
* - ElementFallbackRender crashed when i18n context wasn't available
12+
*/
13+
14+
import { AuthTestUtils } from '../../support/auth-utils';
15+
import { TestTool } from '../../support/page-utils';
16+
import {
17+
AddPageSelectors,
18+
DatabaseGridSelectors,
19+
FieldType,
20+
GridFieldSelectors,
21+
PageSelectors,
22+
PersonSelectors,
23+
PropertyMenuSelectors,
24+
ShareSelectors,
25+
SidebarSelectors,
26+
waitForReactUpdate,
27+
} from '../../support/selectors';
28+
import { generateRandomEmail } from '../../support/test-config';
29+
import { testLog } from '../../support/test-helpers';
30+
31+
describe('Person Cell in Published Pages', () => {
32+
let testEmail: string;
33+
34+
beforeEach(() => {
35+
testEmail = generateRandomEmail();
36+
37+
// Handle uncaught exceptions - especially the ones we fixed
38+
cy.on('uncaught:exception', (err: Error) => {
39+
// Log the error for debugging
40+
testLog.info(`Caught exception: ${err.message}`);
41+
42+
// These are expected/handled errors
43+
if (
44+
err.message.includes('No workspace or service found') ||
45+
err.message.includes('createThemeNoVars_default is not a function') ||
46+
err.message.includes('View not found') ||
47+
err.message.includes('Record not found') ||
48+
err.message.includes('Request failed') ||
49+
err.message.includes("Failed to execute 'writeText' on 'Clipboard'") ||
50+
err.message.includes('ResizeObserver loop') ||
51+
err.name === 'NotAllowedError'
52+
) {
53+
return false;
54+
}
55+
56+
// FAIL the test if we see these errors - they indicate our fix regressed
57+
if (
58+
err.message.includes('useCurrentWorkspaceId must be used within an AppProvider') ||
59+
err.message.includes('useAppHandlers must be used within an AppProvider') ||
60+
err.message.includes('Invalid hook call') ||
61+
err.message.includes('Minified React error #321')
62+
) {
63+
testLog.info(`CRITICAL ERROR - Context issue detected: ${err.message}`);
64+
// Return true to let the error fail the test
65+
return true;
66+
}
67+
68+
return true;
69+
});
70+
71+
cy.viewport(1280, 720);
72+
});
73+
74+
it('should render Person cell without errors in published database view', () => {
75+
testLog.info('[TEST START] Person cell in published database');
76+
77+
// Step 1: Login
78+
cy.visit('/login', { failOnStatusCode: false });
79+
cy.wait(1000);
80+
81+
const authUtils = new AuthTestUtils();
82+
authUtils.signInWithTestUrl(testEmail).then(() => {
83+
cy.url().should('include', '/app');
84+
testLog.info('Signed in successfully');
85+
86+
// Wait for app to load
87+
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
88+
PageSelectors.names().should('exist', { timeout: 30000 });
89+
cy.wait(2000);
90+
91+
// Step 2: Create a Grid database
92+
testLog.info('[STEP 2] Creating Grid database');
93+
AddPageSelectors.inlineAddButton().first().click({ force: true });
94+
waitForReactUpdate(1000);
95+
AddPageSelectors.addGridButton().should('be.visible').click({ force: true });
96+
cy.wait(5000);
97+
98+
// Verify grid exists
99+
DatabaseGridSelectors.grid().should('exist', { timeout: 15000 });
100+
testLog.info('Grid database created');
101+
102+
// Step 3: Add a Person field to the database
103+
testLog.info('[STEP 3] Adding Person field');
104+
105+
// Click the new property button to add a new field
106+
PropertyMenuSelectors.newPropertyButton().first().scrollIntoView().click({ force: true });
107+
waitForReactUpdate(3000);
108+
109+
// Change to Person type
110+
PropertyMenuSelectors.propertyTypeTrigger().then(($trigger) => {
111+
if ($trigger.length > 0) {
112+
cy.wrap($trigger).first().click({ force: true });
113+
waitForReactUpdate(1000);
114+
PropertyMenuSelectors.propertyTypeOption(FieldType.Person).click({ force: true });
115+
waitForReactUpdate(2000);
116+
} else {
117+
GridFieldSelectors.allFieldHeaders().last().scrollIntoView().click({ force: true });
118+
waitForReactUpdate(1000);
119+
PropertyMenuSelectors.propertyTypeTrigger().first().click({ force: true });
120+
waitForReactUpdate(1000);
121+
PropertyMenuSelectors.propertyTypeOption(FieldType.Person).click({ force: true });
122+
waitForReactUpdate(2000);
123+
}
124+
});
125+
126+
// Close menu
127+
cy.get('body').type('{esc}{esc}');
128+
waitForReactUpdate(1000);
129+
130+
// Verify Person cells exist
131+
PersonSelectors.allPersonCells().should('exist', { timeout: 10000 });
132+
testLog.info('Person field added');
133+
134+
// Step 4: Publish the database
135+
testLog.info('[STEP 4] Publishing the database');
136+
137+
// Make sure share button is visible
138+
ShareSelectors.shareButton().should('be.visible', { timeout: 10000 });
139+
140+
TestTool.openSharePopover();
141+
cy.contains('Publish').should('exist').click({ force: true });
142+
cy.wait(1000);
143+
144+
ShareSelectors.publishConfirmButton().should('be.visible').should('not.be.disabled');
145+
ShareSelectors.publishConfirmButton().click({ force: true });
146+
testLog.info('Clicked Publish button');
147+
cy.wait(5000);
148+
149+
// Verify published
150+
ShareSelectors.publishNamespace().should('be.visible', { timeout: 10000 });
151+
testLog.info('Database published successfully');
152+
153+
// Step 5: Get the published URL and visit it
154+
cy.window().then((win) => {
155+
const origin = win.location.origin;
156+
157+
ShareSelectors.publishNamespace()
158+
.invoke('text')
159+
.then((namespace) => {
160+
ShareSelectors.publishNameInput()
161+
.invoke('val')
162+
.then((publishName) => {
163+
const namespaceText = namespace.trim();
164+
const publishNameText = String(publishName).trim();
165+
const publishedUrl = `${origin}/${namespaceText}/${publishNameText}`;
166+
testLog.info(`Published URL: ${publishedUrl}`);
167+
168+
// Close share popover
169+
cy.get('body').type('{esc}');
170+
cy.wait(1000);
171+
172+
// Step 6: Visit the published page
173+
testLog.info('[STEP 6] Visiting published database page');
174+
cy.visit(publishedUrl, { failOnStatusCode: false });
175+
cy.wait(5000);
176+
177+
// Verify URL
178+
cy.url().should('include', `/${namespaceText}/${publishNameText}`);
179+
180+
// Step 7: Verify the page rendered without errors
181+
testLog.info('[STEP 7] Verifying page rendered correctly');
182+
183+
// The page should be visible
184+
cy.get('body').should('be.visible');
185+
186+
// Check that we don't see an error page
187+
cy.get('body').then(($body) => {
188+
const bodyText = $body.text();
189+
190+
if (bodyText.includes('Something went wrong') || bodyText.includes('Error')) {
191+
// Check if it's an actual error or just UI text
192+
if (bodyText.includes('useCurrentWorkspaceId must be used within')) {
193+
throw new Error('REGRESSION: useCurrentWorkspaceId context error in publish view');
194+
}
195+
if (bodyText.includes('Minified React error #321')) {
196+
throw new Error('REGRESSION: React error #321 (Invalid hook call) in publish view');
197+
}
198+
}
199+
200+
testLog.info('✓ No critical errors detected on page');
201+
});
202+
203+
// Verify database structure is visible
204+
cy.get('[class*="appflowy-database"]', { timeout: 15000 })
205+
.should('exist')
206+
.should('be.visible');
207+
testLog.info('✓ Database container is visible');
208+
209+
// Check that Person cells exist in publish view (they should render as read-only)
210+
cy.get('body').then(($body) => {
211+
const hasPersonCells = $body.find('[data-testid*="person-cell"]').length > 0;
212+
213+
if (hasPersonCells) {
214+
testLog.info('✓ Person cells are visible in publish view');
215+
} else {
216+
testLog.info('Note: Person cells selector not found, checking for general cell structure');
217+
}
218+
});
219+
220+
// Check that Person field column header exists
221+
cy.get('body').then(($body) => {
222+
if ($body.text().includes('Person')) {
223+
testLog.info('✓ Person field column is visible');
224+
}
225+
});
226+
227+
testLog.info('[TEST COMPLETE] Person cell rendered successfully in publish view');
228+
});
229+
});
230+
});
231+
});
232+
});
233+
234+
it('should not throw context errors when viewing published page with Person cells', () => {
235+
testLog.info('[TEST START] Context error prevention test');
236+
237+
// This test specifically checks that the fixes for context errors work
238+
// by monitoring for specific error patterns
239+
240+
const contextErrors: string[] = [];
241+
242+
// Set up error monitoring
243+
cy.on('uncaught:exception', (err: Error) => {
244+
if (
245+
err.message.includes('useCurrentWorkspaceId must be used within') ||
246+
err.message.includes('useAppHandlers must be used within') ||
247+
err.message.includes('Minified React error #321') ||
248+
err.message.includes('Invalid hook call')
249+
) {
250+
contextErrors.push(err.message);
251+
// Don't fail immediately - collect all errors
252+
return false;
253+
}
254+
return false; // Ignore other errors for this test
255+
});
256+
257+
cy.visit('/login', { failOnStatusCode: false });
258+
cy.wait(1000);
259+
260+
const authUtils = new AuthTestUtils();
261+
authUtils.signInWithTestUrl(testEmail).then(() => {
262+
cy.url().should('include', '/app');
263+
264+
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
265+
PageSelectors.names().should('exist', { timeout: 30000 });
266+
cy.wait(2000);
267+
268+
// Create a grid
269+
AddPageSelectors.inlineAddButton().first().click({ force: true });
270+
waitForReactUpdate(1000);
271+
AddPageSelectors.addGridButton().should('be.visible').click({ force: true });
272+
cy.wait(5000);
273+
274+
// Verify grid exists
275+
DatabaseGridSelectors.grid().should('exist', { timeout: 15000 });
276+
277+
// Add Person field
278+
PropertyMenuSelectors.newPropertyButton().first().scrollIntoView().click({ force: true });
279+
waitForReactUpdate(3000);
280+
281+
PropertyMenuSelectors.propertyTypeTrigger().then(($trigger) => {
282+
if ($trigger.length > 0) {
283+
cy.wrap($trigger).first().click({ force: true });
284+
waitForReactUpdate(1000);
285+
PropertyMenuSelectors.propertyTypeOption(FieldType.Person).click({ force: true });
286+
waitForReactUpdate(2000);
287+
}
288+
});
289+
290+
cy.get('body').type('{esc}{esc}');
291+
waitForReactUpdate(1000);
292+
293+
// Publish
294+
ShareSelectors.shareButton().should('be.visible', { timeout: 10000 });
295+
TestTool.openSharePopover();
296+
cy.contains('Publish').click({ force: true });
297+
cy.wait(1000);
298+
ShareSelectors.publishConfirmButton().click({ force: true });
299+
cy.wait(5000);
300+
ShareSelectors.publishNamespace().should('be.visible', { timeout: 10000 });
301+
302+
// Get URL and visit
303+
cy.window().then((win) => {
304+
const origin = win.location.origin;
305+
306+
ShareSelectors.publishNamespace()
307+
.invoke('text')
308+
.then((namespace) => {
309+
ShareSelectors.publishNameInput()
310+
.invoke('val')
311+
.then((publishName) => {
312+
const publishedUrl = `${origin}/${namespace.trim()}/${String(publishName).trim()}`;
313+
314+
cy.get('body').type('{esc}');
315+
cy.wait(500);
316+
317+
// Visit published page
318+
cy.visit(publishedUrl, { failOnStatusCode: false });
319+
cy.wait(5000);
320+
321+
// Wait for potential errors to occur
322+
cy.wait(3000);
323+
324+
// Verify no context errors were caught
325+
cy.wrap(contextErrors).should('have.length', 0);
326+
327+
testLog.info('✓ No context errors detected');
328+
testLog.info('[TEST COMPLETE] Context error prevention verified');
329+
});
330+
});
331+
});
332+
});
333+
});
334+
});

cypress/e2e/database/row-detail.cy.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,15 @@ describe('Database Row Detail Tests (Desktop Parity)', () => {
8181
waitForReactUpdate(1000);
8282

8383
// Verify the title is shown in the modal
84-
cy.get('.MuiDialog-paper').should('contain.text', 'Persistence Test');
84+
cy.get('.MuiDialog-paper', { timeout: 10000 })
85+
.should('be.visible')
86+
.and('contain.text', 'Persistence Test');
8587

86-
// Find the title input and modify it
87-
cy.get('.MuiDialog-paper [data-testid="row-title-input"]')
88+
// Find the title input and modify it - use separate commands for reliability
89+
cy.get('.MuiDialog-paper [data-testid="row-title-input"]', { timeout: 5000 })
8890
.should('exist')
89-
.click({ force: true })
91+
.and('be.visible')
92+
.focus()
9093
.type(' Updated', { delay: 20, force: true });
9194
waitForReactUpdate(1000);
9295

@@ -241,11 +244,15 @@ describe('Database Row Detail Tests (Desktop Parity)', () => {
241244

242245
// Open row detail
243246
openRowDetail(0);
247+
waitForReactUpdate(1000);
244248

245-
assertRowDetailOpen();
249+
// Verify modal is open with explicit wait
250+
cy.get('.MuiDialog-paper', { timeout: 10000 })
251+
.should('exist')
252+
.and('be.visible');
246253

247-
// Press Escape to close
248-
cy.get('body').type('{esc}');
254+
// Press Escape to close - use realPress for more reliable key events
255+
cy.get('body').realPress('Escape');
249256
waitForReactUpdate(500);
250257

251258
assertRowDetailClosed();

0 commit comments

Comments
 (0)