Skip to content

Commit ae677e5

Browse files
authored
fix: highlight selected view after creating db (#314)
* fix: highlight selected view after creating db * chore: fix test
1 parent ade9e82 commit ae677e5

17 files changed

Lines changed: 343 additions & 32 deletions

playwright/e2e/database/board-edit-operations.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ test.describe('Board Operations', () => {
157157
await page.waitForTimeout(1500);
158158

159159
// Then: the modal should show the original title
160-
const dialog = page.locator('[role="dialog"]');
160+
const dialog = page.locator('.MuiDialog-paper').filter({ has: page.getByTestId('row-title-input') }).first();
161161
await expect(dialog).toBeVisible({ timeout: 10000 });
162162
await expect(dialog.getByTestId('row-title-input')).toHaveValue(originalName, { timeout: 10000 });
163163

@@ -316,7 +316,7 @@ test.describe('Board Operations', () => {
316316
await BoardSelectors.boardContainer(page).getByText(cardName).click({ force: true });
317317
await page.waitForTimeout(1500);
318318

319-
const dialog = page.locator('[role="dialog"]');
319+
const dialog = page.locator('.MuiDialog-paper').filter({ has: page.getByTestId('row-title-input') }).first();
320320
await expect(dialog).toBeVisible({ timeout: 10000 });
321321

322322
await dialog.locator('[data-block-type]').first().click({ force: true });

playwright/e2e/database/duplicate-row-doc-content.spec.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { generateRandomEmail, setupPageErrorHandling } from '../../support/test-
33
import { signUpAndLoginWithPasswordViaUi } from '../../support/auth-flow-helpers';
44
import {
55
openRowDetail,
6-
closeRowDetailWithEscape,
7-
typeInRowDocument,
86
duplicateRowFromDetail,
7+
getVisibleDataRowIds,
8+
openRowDetailByRowId,
99
} from '../../support/row-detail-helpers';
1010
import { createDatabaseView, waitForGridReady } from '../../support/database-ui-helpers';
1111
import { DatabaseGridSelectors } from '../../support/selectors';
@@ -57,6 +57,8 @@ test.describe('Duplicate row preserves document content', () => {
5757
await page.waitForTimeout(3000);
5858

5959
// Duplicate the row from row detail
60+
const rowIdsBeforeDuplicate = await getVisibleDataRowIds(page);
61+
6062
await openRowDetail(page, 0);
6163
await duplicateRowFromDetail(page);
6264
// duplicateRowFromDetail auto-closes the dialog; ensure it's closed
@@ -65,12 +67,16 @@ test.describe('Duplicate row preserves document content', () => {
6567

6668
// Verify 4 rows (3 default + 1 duplicate)
6769
const rowCount = await DatabaseGridSelectors.dataRows(page).count();
68-
expect(rowCount).toBe(4);
70+
expect(rowCount).toBe(rowIdsBeforeDuplicate.length + 1);
71+
72+
const rowIdsAfterDuplicate = await getVisibleDataRowIds(page);
73+
const duplicatedRowId = rowIdsAfterDuplicate.find((rowId) => !rowIdsBeforeDuplicate.includes(rowId));
74+
expect(duplicatedRowId).toBeTruthy();
6975

70-
// Open the duplicated row (index 1) in full-page mode so the Yjs
76+
// Open the duplicated row in full-page mode so the Yjs
7177
// provider creates a fresh connection on each attempt. The dialog's
7278
// lazy sub-document loading can miss updates; full-page mode is reliable.
73-
await openRowDetail(page, 1);
79+
await openRowDetailByRowId(page, duplicatedRowId!);
7480
const dialogTitle2 = page.locator('.MuiDialogTitle-root');
7581
await dialogTitle2.locator('button').first().click({ force: true });
7682
await page.waitForTimeout(2000);

playwright/e2e/database/duplicate-row-inline-db.spec.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
openRowDetail,
66
closeRowDetailWithEscape,
77
duplicateRowFromDetail,
8+
getVisibleDataRowIds,
9+
openRowDetailByRowId,
810
} from '../../support/row-detail-helpers';
911
import { createDatabaseView, waitForGridReady } from '../../support/database-ui-helpers';
1012
import {
@@ -70,6 +72,8 @@ test.describe('Duplicate row with inline database', () => {
7072
await page.waitForTimeout(1000);
7173

7274
// Duplicate the row from row detail
75+
const rowIdsBeforeDuplicate = await getVisibleDataRowIds(page);
76+
7377
await openRowDetail(page, 0);
7478
await duplicateRowFromDetail(page);
7579
await closeRowDetailWithEscape(page);
@@ -85,10 +89,14 @@ test.describe('Duplicate row with inline database', () => {
8589
await page.waitForTimeout(2000);
8690

8791
// Verify the grid now has 4 rows (3 default + 1 duplicate)
88-
expect(await getGridRowCount(page)).toBe(4);
92+
expect(await getGridRowCount(page)).toBe(rowIdsBeforeDuplicate.length + 1);
93+
94+
const rowIdsAfterDuplicate = await getVisibleDataRowIds(page);
95+
const duplicatedRowId = rowIdsAfterDuplicate.find((rowId) => !rowIdsBeforeDuplicate.includes(rowId));
96+
expect(duplicatedRowId).toBeTruthy();
8997

90-
// Open the duplicated row (index 1) in full page mode to verify
91-
await openRowDetail(page, 1);
98+
// Open the duplicated row in full page mode to verify
99+
await openRowDetailByRowId(page, duplicatedRowId!);
92100
const dialogTitle2 = page.locator('.MuiDialogTitle-root');
93101
await dialogTitle2.locator('button').first().click({ force: true });
94102
await page.waitForTimeout(2000);
@@ -108,7 +116,7 @@ test.describe('Duplicate row with inline database', () => {
108116
await page.goBack();
109117
await page.waitForTimeout(3000);
110118
await waitForGridReady(page);
111-
await openRowDetail(page, 1);
119+
await openRowDetailByRowId(page, duplicatedRowId!);
112120
const dt = page.locator('.MuiDialogTitle-root');
113121
await dt.locator('button').first().click({ force: true });
114122
await page.waitForTimeout(2000);

playwright/e2e/embeded/database/duplicate-doc-inline-and-linked-db.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ test.describe('Duplicate Document Inline And Linked Database', () => {
3939

4040
const docViewId = await createNamedDocumentPage(page, docName);
4141
const editor = editorForView(page, docViewId);
42-
// The linked database picker searches by container name ("New Database"),
43-
// not the renamed view name.
44-
await insertLinkedGridViaSlash(page, docViewId, 'New Database', 0);
42+
// The linked database picker lists databases by the container name.
43+
// `createNamedGridPage` renames the container itself, so search by sourceDbName.
44+
await insertLinkedGridViaSlash(page, docViewId, sourceDbName, 0);
4545

4646
await expect(databaseBlocks(editor)).toHaveCount(1, { timeout: 30000 });
4747
await expectNoActiveFilters(databaseBlocks(editor).nth(0));

playwright/e2e/embeded/database/duplicate-doc-linked-db-filter.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ test.describe('Duplicate Document Linked Database Filter', () => {
3737

3838
const docViewId = await createNamedDocumentPage(page, docName);
3939
const editor = editorForView(page, docViewId);
40-
// The linked database picker searches by container name ("New Database"),
41-
// not the renamed view name. Each test workspace has only one database.
42-
await insertLinkedGridViaSlash(page, docViewId, 'New Database', 0);
40+
// The linked database picker lists databases by the container name.
41+
// `createNamedGridPage` renames the container itself, so search by sourceDbName.
42+
await insertLinkedGridViaSlash(page, docViewId, sourceDbName, 0);
4343
// Wait for the first linked grid to fully render and for any background
4444
// IndexedDB sync activity to settle before opening the slash menu again.
4545
await page.waitForTimeout(3000);
46-
await insertLinkedGridViaSlash(page, docViewId, 'New Database', 1);
46+
await insertLinkedGridViaSlash(page, docViewId, sourceDbName, 1);
4747

4848
await expect(databaseBlocks(editor)).toHaveCount(2, { timeout: 30000 });
4949
await expectNoActiveFilters(databaseBlocks(editor).nth(0));

playwright/support/row-detail-helpers.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,37 @@ export async function openRowDetail(page: Page, rowIndex: number = 0): Promise<v
4242
await expect(RowDetailSelectors.modal(page)).toBeVisible();
4343
}
4444

45+
/**
46+
* Return visible data-row ids in DOM order.
47+
*/
48+
export async function getVisibleDataRowIds(page: Page): Promise<string[]> {
49+
const testIds = await DatabaseGridSelectors.dataRows(page).evaluateAll((rows) =>
50+
rows
51+
.map((row) => row.getAttribute('data-testid') || '')
52+
.filter(Boolean)
53+
);
54+
55+
return testIds.map((testId) => testId.replace('grid-row-', ''));
56+
}
57+
58+
/**
59+
* Open a specific row detail modal by row id.
60+
*/
61+
export async function openRowDetailByRowId(page: Page, rowId: string): Promise<void> {
62+
const row = DatabaseGridSelectors.rowById(page, rowId);
63+
await expect(row).toBeVisible({ timeout: 10000 });
64+
await row.scrollIntoViewIfNeeded();
65+
await row.hover();
66+
await page.waitForTimeout(500);
67+
68+
const expandButton = page.getByTestId('row-expand-button').first();
69+
await expect(expandButton).toBeVisible({ timeout: 5000 });
70+
await expandButton.click({ force: true });
71+
await page.waitForTimeout(1000);
72+
73+
await expect(RowDetailSelectors.modal(page)).toBeVisible();
74+
}
75+
4576
/**
4677
* Open row detail by hovering over a cell to reveal the expand button
4778
*/

src/application/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,7 @@ export interface ViewMetaProps {
12901290
cover?: ViewMetaCover;
12911291
name?: string;
12921292
viewId?: string;
1293+
parentViewId?: string;
12931294
workspaceId?: string;
12941295
layout?: ViewLayout;
12951296
visibleViewIds?: string[];

src/components/app/DatabaseView.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ function DatabaseView(props: DatabaseViewProps) {
3737
}, [outline, databasePageId]);
3838

3939
// Use hook to determine container view and visible view IDs
40-
const { containerView, visibleViewIds } = useContainerVisibleViewIds({ view, outline });
40+
const { containerView, visibleViewIds } = useContainerVisibleViewIds({
41+
view,
42+
outline,
43+
parentViewId: viewMeta.parentViewId,
44+
databaseId: viewMeta.extra?.database_id,
45+
embedded: viewMeta.extra?.embedded,
46+
});
4147

4248
// Use container view (if present) as the "page meta" view for naming/icon operations.
4349
const pageView = containerView || view;

src/components/app/app.hooks.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import LoadingDots from '@/components/_shared/LoadingDots';
55
import { findView } from '@/components/_shared/outline/utils';
66
import {
77
DATABASE_TAB_VIEW_ID_QUERY_PARAM,
8+
resolveSidebarHighlightedViewIds,
89
resolveSidebarSelectedViewId,
910
} from '@/components/app/hooks/resolveSidebarSelectedViewId';
1011

@@ -511,3 +512,22 @@ export function useSidebarSelectedViewId() {
511512
[outline, routeViewId, tabViewId]
512513
);
513514
}
515+
516+
export function useSidebarHighlightedViewIds() {
517+
const routeViewId = useAppViewId();
518+
const outline = useAppOutline();
519+
const breadcrumbs = useBreadcrumb();
520+
const [searchParams] = useSearchParams();
521+
const tabViewId = searchParams.get(DATABASE_TAB_VIEW_ID_QUERY_PARAM);
522+
523+
return useMemo(
524+
() =>
525+
resolveSidebarHighlightedViewIds({
526+
routeViewId,
527+
tabViewId,
528+
outline,
529+
breadcrumbs,
530+
}),
531+
[breadcrumbs, outline, routeViewId, tabViewId]
532+
);
533+
}

src/components/app/hooks/__tests__/ViewItem.databaseContainer.test.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import ViewItem from '@/components/app/outline/ViewItem';
66
declare global {
77
// eslint-disable-next-line no-var
88
var __selectedViewId: string | undefined;
9+
// eslint-disable-next-line no-var
10+
var __highlightedViewIds: string[] | undefined;
911
}
1012

1113
jest.mock('react-i18next', () => ({
@@ -14,6 +16,7 @@ jest.mock('react-i18next', () => ({
1416

1517
jest.mock('@/components/app/app.hooks', () => ({
1618
useSidebarSelectedViewId: () => global.__selectedViewId,
19+
useSidebarHighlightedViewIds: () => global.__highlightedViewIds || [],
1720
useAppOperations: () => ({
1821
updatePage: jest.fn(),
1922
uploadFile: jest.fn(),
@@ -30,6 +33,7 @@ jest.mock('@/components/_shared/view-icon/PageIcon', () => () => null);
3033
describe('ViewItem database container', () => {
3134
beforeEach(() => {
3235
global.__selectedViewId = undefined;
36+
global.__highlightedViewIds = undefined;
3337
});
3438

3539
it('clicking a container opens its first child', () => {
@@ -112,4 +116,45 @@ describe('ViewItem database container', () => {
112116

113117
expect(el.getAttribute('data-selected')).toBe('true');
114118
});
119+
120+
it('marks both the database container and active child view as selected', () => {
121+
const childView: View = {
122+
view_id: 'child-view-id',
123+
name: 'Grid',
124+
icon: null,
125+
layout: ViewLayout.Grid,
126+
extra: { is_space: false },
127+
children: [],
128+
is_published: false,
129+
is_private: false,
130+
parent_view_id: 'container-view-id',
131+
};
132+
133+
const containerView: View = {
134+
view_id: 'container-view-id',
135+
name: 'New database',
136+
icon: null,
137+
layout: ViewLayout.Grid,
138+
extra: { is_space: false, is_database_container: true },
139+
children: [childView],
140+
is_published: false,
141+
is_private: false,
142+
};
143+
144+
global.__selectedViewId = childView.view_id;
145+
global.__highlightedViewIds = [childView.view_id, containerView.view_id];
146+
147+
render(
148+
<ViewItem
149+
view={containerView}
150+
width={240}
151+
expandIds={[containerView.view_id]}
152+
toggleExpand={jest.fn()}
153+
onClickView={jest.fn()}
154+
/>
155+
);
156+
157+
expect(screen.getByTestId(`page-${containerView.view_id}`).getAttribute('data-selected')).toBe('true');
158+
expect(screen.getByTestId(`page-${childView.view_id}`).getAttribute('data-selected')).toBe('true');
159+
});
115160
});

0 commit comments

Comments
 (0)