Skip to content

Commit 816f75c

Browse files
committed
disable the button in the rename modal when the input field is empty
1 parent 51c7e67 commit 816f75c

File tree

6 files changed

+28
-124
lines changed

6 files changed

+28
-124
lines changed

zeppelin-web-angular/e2e/models/base-page.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,11 @@ export class BasePage {
1818

1919
constructor(page: Page) {
2020
this.page = page;
21-
this.loadingScreen = page.locator('.spin-text');
21+
this.loadingScreen = page.locator('section.spin');
2222
}
2323

2424
async waitForPageLoad(): Promise<void> {
2525
await this.page.waitForLoadState('domcontentloaded');
26-
try {
27-
await this.loadingScreen.waitFor({ state: 'hidden', timeout: 15000 });
28-
} catch {
29-
console.log('Loading screen not found');
30-
}
26+
await this.loadingScreen.waitFor({ state: 'hidden', timeout: 15000 });
3127
}
3228
}

zeppelin-web-angular/e2e/models/folder-rename-page.util.ts

Lines changed: 10 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export class FolderRenamePageUtil {
8686

8787
// Re-check for the renamed folder after reload
8888
const newFolderAfterReload = this.page.locator('.folder .name', { hasText: newName });
89+
8990
await expect(newFolderAfterReload).toBeVisible({ timeout: 10000 });
9091
}
9192

@@ -104,117 +105,16 @@ export class FolderRenamePageUtil {
104105
await this.folderRenamePage.clickRenameMenuItem(folderName);
105106
await this.folderRenamePage.clearNewName();
106107

107-
await this.folderRenamePage.clickConfirm();
108+
// NEW ASSERTION: The confirm button should be disabled when the input is empty.
109+
await expect(this.folderRenamePage.confirmButton).toBeDisabled({ timeout: 5000 });
110+
111+
// Clean up: Click cancel to close the modal after verifying validation.
112+
await this.folderRenamePage.clickCancel();
113+
await expect(this.folderRenamePage.renameModal).not.toBeVisible({ timeout: 5000 });
108114

109-
// Strategy 1: Wait for immediate client-side validation indicators
110-
let clientValidationFound = false;
111-
const clientValidationChecks = [
112-
// Check for validation error message
113-
async () =>
114-
// Use isVisible() to check without asserting, and catch if element is not found
115-
await this.folderRenamePage.validationError.isVisible({ timeout: 1000 }).catch(() => false),
116-
// Check if input shows validation state
117-
async () => {
118-
// Check attribute value, and catch if element is not found
119-
const ariaInvalid = await this.folderRenamePage.renameInput
120-
.getAttribute('aria-invalid', { timeout: 1000 })
121-
.catch(() => null);
122-
return ariaInvalid === 'true';
123-
},
124-
// Check if rename button is disabled
125-
async () =>
126-
// Use isDisabled() to check without asserting, and catch if element is not found
127-
await this.folderRenamePage.confirmButton.isDisabled({ timeout: 1000 }).catch(() => false),
128-
// Check input validity via CSS classes
129-
async () =>
130-
// Evaluate the class directly, and catch if element is not found
131-
await this.folderRenamePage.renameInput
132-
.evaluate(el => el.classList.contains('invalid') || el.classList.contains('error'), { timeout: 1000 })
133-
.catch(() => false)
134-
];
135-
136-
for (const check of clientValidationChecks) {
137-
if (await check()) {
138-
clientValidationFound = true;
139-
// Client-side validation working - empty name prevented
140-
break;
141-
}
142-
}
143-
144-
if (clientValidationFound) {
145-
// Client-side validation is working, modal should stay open
146-
await expect(this.folderRenamePage.renameModal).toBeVisible();
147-
await this.folderRenamePage.clickCancel();
148-
return;
149-
}
150-
151-
// Strategy 2: Wait for server-side processing and response
152-
await this.page
153-
.waitForFunction(
154-
() => {
155-
// Wait for any network requests to complete and UI to stabilize
156-
const modal = document.querySelector('.ant-modal-wrap');
157-
const hasLoadingIndicators = document.querySelectorAll('.loading, .spinner, [aria-busy="true"]').length > 0;
158-
159-
// Consider stable when either modal is gone or no loading indicators
160-
return !modal || !hasLoadingIndicators;
161-
},
162-
{ timeout: 5000 }
163-
)
164-
.catch(() => {
165-
// Server response wait timeout, checking final state...
166-
});
167-
168-
// Check final state after server processing
169-
const finalModalVisible = await this.folderRenamePage.isRenameModalVisible();
170-
const finalFolderVisible = await this.folderRenamePage.isFolderVisible(folderName);
171-
172-
// Strategy 3: Analyze the validation behavior based on final state
173-
if (finalModalVisible && !finalFolderVisible) {
174-
// Modal open, folder disappeared - server prevented rename but UI shows confusion
175-
await expect(this.folderRenamePage.renameModal).toBeVisible();
176-
await this.folderRenamePage.clickCancel();
177-
// Wait for folder to reappear after modal close
178-
await expect(
179-
this.page.locator('.node').filter({
180-
has: this.page.locator('.folder .name', { hasText: folderName })
181-
})
182-
).toBeVisible({ timeout: 3000 });
183-
return;
184-
}
185-
186-
if (!finalModalVisible && finalFolderVisible) {
187-
// Modal closed, folder visible - proper server-side validation (rejected empty name)
188-
await expect(this.folderRenamePage.renameModal).not.toBeVisible();
189-
await expect(
190-
this.page.locator('.node').filter({
191-
has: this.page.locator('.folder .name', { hasText: folderName })
192-
})
193-
).toBeVisible();
194-
return;
195-
}
196-
197-
if (finalModalVisible && finalFolderVisible) {
198-
// Modal still open, folder still visible - validation prevented submission
199-
await expect(this.folderRenamePage.renameModal).toBeVisible();
200-
await expect(
201-
this.page.locator('.node').filter({
202-
has: this.page.locator('.folder .name', { hasText: folderName })
203-
})
204-
).toBeVisible();
205-
await this.folderRenamePage.clickCancel();
206-
return;
207-
}
208-
209-
if (!finalModalVisible && !finalFolderVisible) {
210-
// Both gone - system handled the empty name by removing/hiding the folder
211-
await expect(this.folderRenamePage.renameModal).not.toBeVisible();
212-
// The folder being removed is acceptable behavior for empty names
213-
return;
214-
}
215-
216-
// Fallback: If we reach here, assume validation is working
217-
// Validation behavior is unclear but folder appears protected
115+
// Verify the original folder still exists and was not renamed or deleted.
116+
const originalFolderLocator = this.page.locator('.folder .name', { hasText: folderName });
117+
await expect(originalFolderLocator).toBeVisible({ timeout: 5000 });
218118
}
219119

220120
async verifyDeleteIconIsDisplayed(folderName: string): Promise<void> {

zeppelin-web-angular/e2e/models/notebook-keyboard-page.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,6 @@ export class NotebookKeyboardPage extends BasePage {
431431
}
432432

433433
// Helper methods for verifying shortcut effects
434-
435434
async waitForParagraphExecution(paragraphIndex: number = 0, timeout: number = 30000): Promise<void> {
436435
if (this.page.isClosed()) {
437436
console.warn('Cannot wait for paragraph execution: page is closed');
@@ -728,6 +727,12 @@ export class NotebookKeyboardPage extends BasePage {
728727
}
729728

730729
private async focusEditorElement(paragraph: Locator, paragraphIndex: number): Promise<void> {
730+
// Add check for page.isClosed() at the beginning
731+
if (this.page.isClosed()) {
732+
console.warn(`Attempted to focus editor in paragraph ${paragraphIndex} but page is closed.`);
733+
return; // Exit early if the page is already closed
734+
}
735+
731736
const browserName = this.page.context().browser()?.browserType().name();
732737
const editor = paragraph.locator('.monaco-editor, .CodeMirror, .ace_editor, textarea').first();
733738

zeppelin-web-angular/e2e/models/notebook.util.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ export class NotebookUtil extends BasePage {
4040

4141
await expect(this.homePage.notebookList).toBeVisible({ timeout: 90000 });
4242
await expect(this.homePage.createNewNoteButton).toBeVisible({ timeout: 45000 });
43-
await this.homePage.createNewNoteButton.click({ timeout: 30000 });
44-
43+
await this.homePage.createNewNoteButton.click({ timeout: 45000 });
4544
// Click the 'Create' button in the modal
4645
const createButton = this.page.locator('button', { hasText: 'Create' });
4746
await expect(createButton).toBeVisible({ timeout: 30000 });

zeppelin-web-angular/e2e/utils.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* limitations under the License.
1111
*/
1212

13-
import { test, Page, TestInfo } from '@playwright/test';
13+
import { test, Page, TestInfo, expect } from '@playwright/test';
1414
import { LoginTestUtil } from './models/login-page.util';
1515
import { NotebookUtil } from './models/notebook.util';
1616

@@ -259,6 +259,9 @@ export async function waitForZeppelinReady(page: Page): Promise<void> {
259259

260260
// Additional stability check - wait for DOM to be stable
261261
await page.waitForLoadState('domcontentloaded');
262+
263+
// Explicitly wait for the "Welcome to Zeppelin!" heading to be visible
264+
await expect(page.locator('h1:has-text("Welcome to Zeppelin!")')).toBeVisible({ timeout: 30000 });
262265
} catch (error) {
263266
console.warn('Zeppelin ready check failed, but continuing...', error);
264267
// Don't throw error in CI environments, just log and continue
@@ -417,8 +420,7 @@ export async function createTestNotebook(
417420

418421
// Navigate back to home
419422
await page.goto('/');
420-
await page.waitForLoadState('networkidle', { timeout: 30000 });
421-
await page.waitForSelector('text=Welcome to Zeppelin!', { timeout: 20000 });
423+
await waitForZeppelinReady(page);
422424

423425
return { noteId, paragraphId };
424426
}
@@ -428,7 +430,7 @@ export async function deleteTestNotebook(page: Page, noteId: string): Promise<vo
428430
// Navigate to home page
429431
await page.goto('/');
430432
await page.waitForLoadState('networkidle', { timeout: 30000 });
431-
await page.waitForSelector('text=Welcome to Zeppelin!', { timeout: 20000 });
433+
await expect(page.locator('h1:has-text("Welcome to Zeppelin!")')).toBeVisible({ timeout: 20000 });
432434

433435
// Find the notebook in the tree
434436
const treeNode = page.locator(`//span[@class='node-name' and contains(text(), 'Test Notebook')]`);

zeppelin-web-angular/src/app/share/folder-rename/folder-rename.component.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
</nz-form-control>
3131
</nz-form-item>
3232
<div class="modal-footer ant-modal-footer">
33-
<button nz-button nzType="primary" (click)="rename()">Rename</button>
33+
<button nz-button nzType="primary" (click)="rename()" [disabled]="!newFolderPath || !newFolderPath.trim()">
34+
Rename
35+
</button>
3436
</div>
3537
</form>
3638

0 commit comments

Comments
 (0)