Make memory and context views mobile-friendly (#813)

* Changes from fix/memory-and-context-mobile-friendly

* fix: Improve file extension detection and add path traversal protection

* refactor: Extract file extension utilities and add path traversal guards

Code review improvements:
- Extract isMarkdownFilename and isImageFilename to shared image-utils.ts
- Remove duplicated code from context-view.tsx and memory-view.tsx
- Add path traversal guard for context fixture utilities (matching memory)
- Add 7 new tests for context fixture path traversal protection
- Total 61 tests pass

Addresses code review feedback from PR #813

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: Add e2e tests for profiles crud and board background persistence

* Update apps/ui/playwright.config.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: Add robust test navigation handling and file filtering

* fix: Format NODE_OPTIONS configuration on single line

* test: Update profiles and board background persistence tests

* test: Replace iPhone 13 Pro with Pixel 5 for mobile test consistency

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
gsxdsm
2026-02-26 03:31:40 -08:00
committed by GitHub
parent 6408f514a4
commit 583c3eb4a6
30 changed files with 3758 additions and 113 deletions

View File

@@ -0,0 +1,237 @@
/**
* Desktop Memory View E2E Tests
*
* Tests for desktop behavior in the memory view:
* - File list and editor visible side-by-side
* - Back button is NOT visible on desktop
* - Toolbar buttons show both icon and text
* - Delete button is visible in toolbar (not hidden like on mobile)
*/
import { test, expect } from '@playwright/test';
import {
resetMemoryDirectory,
setupProjectWithFixture,
getFixturePath,
navigateToMemory,
waitForMemoryFile,
selectMemoryFile,
waitForMemoryContentToLoad,
clickElement,
fillInput,
waitForNetworkIdle,
authenticateForTests,
waitForElementHidden,
} from '../utils';
// Use desktop viewport for desktop tests
test.use({ viewport: { width: 1280, height: 720 } });
test.describe('Desktop Memory View', () => {
test.beforeEach(async () => {
resetMemoryDirectory();
});
test.afterEach(async () => {
resetMemoryDirectory();
});
test('should show file list and editor side-by-side on desktop', async ({ page }) => {
const fileName = 'desktop-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file
await clickElement(page, 'create-memory-button');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(
page,
'new-memory-content',
'# Desktop Test\n\nThis tests desktop view behavior'
);
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Select the file
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// On desktop, file list should be visible after selection
const fileList = page.locator('[data-testid="memory-file-list"]');
await expect(fileList).toBeVisible();
// Editor panel should also be visible (either editor or preview)
const editor = page.locator('[data-testid="memory-editor"], [data-testid="markdown-preview"]');
await expect(editor).toBeVisible();
});
test('should NOT show back button in editor toolbar on desktop', async ({ page }) => {
const fileName = 'no-back-button-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file
await clickElement(page, 'create-memory-button');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(page, 'new-memory-content', '# No Back Button Test');
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Select the file
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// Back button should NOT be visible on desktop
const backButton = page.locator('button[aria-label="Back"]');
await expect(backButton).not.toBeVisible();
});
test('should show buttons with text labels on desktop', async ({ page }) => {
const fileName = 'text-labels-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file
await clickElement(page, 'create-memory-button');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(
page,
'new-memory-content',
'# Text Labels Test\n\nTesting button text labels on desktop'
);
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Select the file
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// Get the toggle preview mode button
const toggleButton = page.locator('[data-testid="toggle-preview-mode"]');
await expect(toggleButton).toBeVisible();
// Button should have text label on desktop
const buttonText = await toggleButton.textContent();
// On desktop, button should have visible text (Edit or Preview)
expect(buttonText?.trim()).not.toBe('');
expect(buttonText?.toLowerCase()).toMatch(/(edit|preview)/);
});
test('should show delete button in toolbar on desktop', async ({ page }) => {
const fileName = 'delete-button-desktop-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file
await clickElement(page, 'create-memory-button');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(page, 'new-memory-content', '# Delete Button Desktop Test');
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Select the file
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// Delete button in toolbar should be visible on desktop
const deleteButton = page.locator('[data-testid="delete-memory-file"]');
await expect(deleteButton).toBeVisible();
});
test('should show file list at fixed width on desktop when file is selected', async ({
page,
}) => {
const fileName = 'fixed-width-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file
await clickElement(page, 'create-memory-button');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(page, 'new-memory-content', '# Fixed Width Test');
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Select the file
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// File list should be visible
const fileList = page.locator('[data-testid="memory-file-list"]');
await expect(fileList).toBeVisible();
// On desktop with file selected, the file list should be at fixed width (w-64 = 256px)
const fileListBox = await fileList.boundingBox();
expect(fileListBox).not.toBeNull();
if (fileListBox) {
// Desktop file list is w-64 = 256px, allow some tolerance for borders
expect(fileListBox.width).toBeLessThanOrEqual(300);
expect(fileListBox.width).toBeGreaterThanOrEqual(200);
}
});
test('should show action buttons inline in header on desktop', async ({ page }) => {
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// On desktop, inline buttons should be visible
const createButton = page.locator('[data-testid="create-memory-button"]');
await expect(createButton).toBeVisible();
const refreshButton = page.locator('[data-testid="refresh-memory-button"]');
await expect(refreshButton).toBeVisible();
});
});

View File

@@ -0,0 +1,192 @@
/**
* Memory View File Extension Edge Cases E2E Tests
*
* Tests for file extension handling in the memory view:
* - Files with valid markdown extensions (.md, .markdown)
* - Files without extensions (edge case for isMarkdownFile)
* - Files with multiple dots in name
*/
import { test, expect } from '@playwright/test';
import {
resetMemoryDirectory,
setupProjectWithFixture,
getFixturePath,
navigateToMemory,
waitForMemoryFile,
selectMemoryFile,
waitForMemoryContentToLoad,
clickElement,
fillInput,
waitForNetworkIdle,
authenticateForTests,
waitForElementHidden,
createMemoryFileOnDisk,
} from '../utils';
// Use desktop viewport for these tests
test.use({ viewport: { width: 1280, height: 720 } });
test.describe('Memory View File Extension Edge Cases', () => {
test.beforeEach(async () => {
resetMemoryDirectory();
});
test.afterEach(async () => {
resetMemoryDirectory();
});
test('should handle file with .md extension', async ({ page }) => {
const fileName = 'standard-file.md';
const content = '# Standard Markdown';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create file via API
createMemoryFileOnDisk(fileName, content);
await waitForNetworkIdle(page);
// Refresh to load the file
await page.reload();
await waitForMemoryFile(page, fileName);
// Select and verify it opens as markdown
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// Should show markdown preview
const markdownPreview = page.locator('[data-testid="markdown-preview"]');
await expect(markdownPreview).toBeVisible();
// Verify content rendered
const h1 = markdownPreview.locator('h1');
await expect(h1).toHaveText('Standard Markdown');
});
test('should handle file with .markdown extension', async ({ page }) => {
const fileName = 'extended-extension.markdown';
const content = '# Extended Extension Test';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create file via API
createMemoryFileOnDisk(fileName, content);
await waitForNetworkIdle(page);
// Refresh to load the file
await page.reload();
await waitForMemoryFile(page, fileName);
// Select and verify
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
const markdownPreview = page.locator('[data-testid="markdown-preview"]');
await expect(markdownPreview).toBeVisible();
});
test('should handle file with multiple dots in name', async ({ page }) => {
const fileName = 'my.detailed.notes.md';
const content = '# Multiple Dots Test';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create file via API
createMemoryFileOnDisk(fileName, content);
await waitForNetworkIdle(page);
// Refresh to load the file
await page.reload();
await waitForMemoryFile(page, fileName);
// Select and verify - should still recognize as markdown
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
const markdownPreview = page.locator('[data-testid="markdown-preview"]');
await expect(markdownPreview).toBeVisible();
});
test('should NOT show file without extension in file list', async ({ page }) => {
const fileName = 'README';
const content = '# File Without Extension';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create file via API (without extension)
createMemoryFileOnDisk(fileName, content);
await waitForNetworkIdle(page);
// Refresh to load the file
await page.reload();
// Wait a moment for files to load
await page.waitForTimeout(1000);
// File should NOT appear in list because isMarkdownFile returns false for no extension
const fileButton = page.locator(`[data-testid="memory-file-${fileName}"]`);
await expect(fileButton).not.toBeVisible();
});
test('should NOT create file without .md extension via UI', async ({ page }) => {
const fileName = 'NOTES';
const content = '# Notes without extension';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create file via UI without extension
await clickElement(page, 'create-memory-button');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(page, 'new-memory-content', content);
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
// File should NOT appear in list because UI enforces .md extension
// (The UI may add .md automatically or show validation error)
const fileButton = page.locator(`[data-testid="memory-file-${fileName}"]`);
await expect(fileButton)
.not.toBeVisible({ timeout: 3000 })
.catch(() => {
// It's OK if it doesn't appear - that's expected behavior
});
});
test('should handle uppercase extensions', async ({ page }) => {
const fileName = 'uppercase.MD';
const content = '# Uppercase Extension';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create file via API with uppercase extension
createMemoryFileOnDisk(fileName, content);
await waitForNetworkIdle(page);
// Refresh to load the file
await page.reload();
await waitForMemoryFile(page, fileName);
// Select and verify - should recognize .MD as markdown (case-insensitive)
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
const markdownPreview = page.locator('[data-testid="markdown-preview"]');
await expect(markdownPreview).toBeVisible();
});
});

View File

@@ -0,0 +1,174 @@
/**
* Mobile Memory View Operations E2E Tests
*
* Tests for file operations on mobile in the memory view:
* - Deleting files via dropdown menu on mobile
* - Creating files via mobile actions panel
*/
import { test, expect, devices } from '@playwright/test';
import {
resetMemoryDirectory,
setupProjectWithFixture,
getFixturePath,
navigateToMemory,
waitForMemoryFile,
clickElement,
fillInput,
waitForNetworkIdle,
authenticateForTests,
memoryFileExistsOnDisk,
waitForElementHidden,
} from '../utils';
// Use mobile viewport for mobile tests in Chromium CI
test.use({ ...devices['Pixel 5'] });
test.describe('Mobile Memory View Operations', () => {
test.beforeEach(async () => {
resetMemoryDirectory();
});
test.afterEach(async () => {
resetMemoryDirectory();
});
test('should create a file via mobile actions panel', async ({ page }) => {
const fileName = 'mobile-created.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file via mobile actions panel
await clickElement(page, 'header-actions-panel-trigger');
await clickElement(page, 'create-memory-button-mobile');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(page, 'new-memory-content', '# Created on Mobile');
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Verify file appears in list
const fileButton = page.locator(`[data-testid="memory-file-${fileName}"]`);
await expect(fileButton).toBeVisible();
// Verify file exists on disk
expect(memoryFileExistsOnDisk(fileName)).toBe(true);
});
test('should delete a file via dropdown menu on mobile', async ({ page }) => {
const fileName = 'delete-via-menu-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file
await clickElement(page, 'header-actions-panel-trigger');
await clickElement(page, 'create-memory-button-mobile');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(page, 'new-memory-content', '# File to Delete');
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Verify file exists
expect(memoryFileExistsOnDisk(fileName)).toBe(true);
// Close actions panel if still open
await page.keyboard.press('Escape');
await page.waitForTimeout(300);
// Click on the file menu dropdown - hover first to make it visible
const fileRow = page.locator(`[data-testid="memory-file-${fileName}"]`);
await fileRow.hover();
const fileMenuButton = page.locator(`[data-testid="memory-file-menu-${fileName}"]`);
await fileMenuButton.click({ force: true });
// Wait for dropdown
await page.waitForTimeout(300);
// Click delete in dropdown
const deleteMenuItem = page.locator(`[data-testid="delete-memory-file-${fileName}"]`);
await deleteMenuItem.click();
// Wait for file to be removed from list
await waitForElementHidden(page, `memory-file-${fileName}`, { timeout: 5000 });
// Verify file no longer exists on disk
expect(memoryFileExistsOnDisk(fileName)).toBe(false);
});
test('should refresh button be available in actions panel', async ({ page }) => {
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Open actions panel
await clickElement(page, 'header-actions-panel-trigger');
// Verify refresh button is visible in actions panel
const refreshButton = page.locator('[data-testid="refresh-memory-button-mobile"]');
await expect(refreshButton).toBeVisible();
});
test('should preview markdown content on mobile', async ({ page }) => {
const fileName = 'preview-test.md';
const markdownContent =
'# Preview Test\n\n**Bold text** and *italic text*\n\n- List item 1\n- List item 2';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file
await clickElement(page, 'header-actions-panel-trigger');
await clickElement(page, 'create-memory-button-mobile');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(page, 'new-memory-content', markdownContent);
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Select the file by clicking on it
const fileButton = page.locator(`[data-testid="memory-file-${fileName}"]`);
await fileButton.click();
// Wait for content to load (preview or editor)
await page.waitForSelector('[data-testid="markdown-preview"], [data-testid="memory-editor"]', {
timeout: 5000,
});
// Memory files open in preview mode by default
const markdownPreview = page.locator('[data-testid="markdown-preview"]');
await expect(markdownPreview).toBeVisible();
// Verify the preview rendered the markdown (check for h1)
const h1 = markdownPreview.locator('h1');
await expect(h1).toHaveText('Preview Test');
});
});

View File

@@ -0,0 +1,273 @@
/**
* Mobile Memory View E2E Tests
*
* Tests for mobile-friendly behavior in the memory view:
* - File list hides when file is selected on mobile
* - Back button appears on mobile to return to file list
* - Toolbar buttons are icon-only on mobile
* - Delete button is hidden on mobile (use dropdown menu instead)
*/
import { test, expect, devices } from '@playwright/test';
import {
resetMemoryDirectory,
setupProjectWithFixture,
getFixturePath,
navigateToMemory,
waitForMemoryFile,
selectMemoryFile,
waitForMemoryContentToLoad,
clickElement,
fillInput,
waitForNetworkIdle,
authenticateForTests,
waitForElementHidden,
} from '../utils';
// Use mobile viewport for mobile tests in Chromium CI
test.use({ ...devices['Pixel 5'] });
test.describe('Mobile Memory View', () => {
test.beforeEach(async () => {
resetMemoryDirectory();
});
test.afterEach(async () => {
resetMemoryDirectory();
});
test('should hide file list when a file is selected on mobile', async ({ page }) => {
const fileName = 'mobile-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file - on mobile, open the actions panel first
await clickElement(page, 'header-actions-panel-trigger');
await clickElement(page, 'create-memory-button-mobile');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(page, 'new-memory-content', '# Mobile Test\n\nThis tests mobile view behavior');
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// File list should be visible before selection
const fileListBefore = page.locator('[data-testid="memory-file-list"]');
await expect(fileListBefore).toBeVisible();
// Select the file
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// On mobile, file list should be hidden after selection (full-screen editor)
const fileListAfter = page.locator('[data-testid="memory-file-list"]');
await expect(fileListAfter).toBeHidden();
});
test('should show back button in editor toolbar on mobile', async ({ page }) => {
const fileName = 'back-button-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file - on mobile, open the actions panel first
await clickElement(page, 'header-actions-panel-trigger');
await clickElement(page, 'create-memory-button-mobile');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(
page,
'new-memory-content',
'# Back Button Test\n\nTesting back button on mobile'
);
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Select the file
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// Back button should be visible on mobile
const backButton = page.locator('button[aria-label="Back"]');
await expect(backButton).toBeVisible();
// Back button should have ArrowLeft icon
const arrowIcon = backButton.locator('svg.lucide-arrow-left');
await expect(arrowIcon).toBeVisible();
});
test('should return to file list when back button is clicked on mobile', async ({ page }) => {
const fileName = 'back-navigation-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file - on mobile, open the actions panel first
await clickElement(page, 'header-actions-panel-trigger');
await clickElement(page, 'create-memory-button-mobile');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(page, 'new-memory-content', '# Back Navigation Test');
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Select the file
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// File list should be hidden after selection
const fileListHidden = page.locator('[data-testid="memory-file-list"]');
await expect(fileListHidden).toBeHidden();
// Click back button
const backButton = page.locator('button[aria-label="Back"]');
await backButton.click();
// File list should be visible again
const fileListVisible = page.locator('[data-testid="memory-file-list"]');
await expect(fileListVisible).toBeVisible();
// Editor should no longer be visible
const editor = page.locator('[data-testid="memory-editor"]');
await expect(editor).not.toBeVisible();
});
test('should show icon-only buttons in toolbar on mobile', async ({ page }) => {
const fileName = 'icon-buttons-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file - on mobile, open the actions panel first
await clickElement(page, 'header-actions-panel-trigger');
await clickElement(page, 'create-memory-button-mobile');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(
page,
'new-memory-content',
'# Icon Buttons Test\n\nTesting icon-only buttons on mobile'
);
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Select the file
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// Get the toggle preview mode button
const toggleButton = page.locator('[data-testid="toggle-preview-mode"]');
await expect(toggleButton).toBeVisible();
// Button should have icon (Eye or Pencil)
const eyeIcon = toggleButton.locator('svg.lucide-eye');
const pencilIcon = toggleButton.locator('svg.lucide-pencil');
// One of the icons should be present
const hasIcon = await (async () => {
const eyeVisible = await eyeIcon.isVisible().catch(() => false);
const pencilVisible = await pencilIcon.isVisible().catch(() => false);
return eyeVisible || pencilVisible;
})();
expect(hasIcon).toBe(true);
// Text label should not be present (or minimal space on mobile)
const buttonText = await toggleButton.textContent();
// On mobile, button should have icon only (no "Edit" or "Preview" text visible)
// The text is wrapped in {!isMobile && <span>}, so it shouldn't render
expect(buttonText?.trim()).toBe('');
});
test('should hide delete button in toolbar on mobile', async ({ page }) => {
const fileName = 'delete-button-test.md';
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// Create a test file - on mobile, open the actions panel first
await clickElement(page, 'header-actions-panel-trigger');
await clickElement(page, 'create-memory-button-mobile');
await page.waitForSelector('[data-testid="create-memory-dialog"]', { timeout: 5000 });
await fillInput(page, 'new-memory-name', fileName);
await fillInput(page, 'new-memory-content', '# Delete Button Test');
await clickElement(page, 'confirm-create-memory');
await waitForElementHidden(page, 'create-memory-dialog', { timeout: 5000 });
await waitForNetworkIdle(page);
await waitForMemoryFile(page, fileName);
// Select the file
await selectMemoryFile(page, fileName);
await waitForMemoryContentToLoad(page);
// Delete button in toolbar should be hidden on mobile
const deleteButton = page.locator('[data-testid="delete-memory-file"]');
await expect(deleteButton).not.toBeVisible();
});
test('should show file list at full width on mobile when no file is selected', async ({
page,
}) => {
await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await navigateToMemory(page);
// File list should be visible
const fileList = page.locator('[data-testid="memory-file-list"]');
await expect(fileList).toBeVisible();
// On mobile with no file selected, the file list should take full width
// Check that the file list container has the w-full class (mobile behavior)
const fileListBox = await fileList.boundingBox();
expect(fileListBox).not.toBeNull();
if (fileListBox) {
// On mobile (Pixel 5 has width 393), the file list should take most of the width
// We check that it's significantly wider than the desktop w-64 (256px)
expect(fileListBox.width).toBeGreaterThan(300);
}
// Editor panel should be hidden on mobile when no file is selected
const editor = page.locator('[data-testid="memory-editor"]');
await expect(editor).not.toBeVisible();
});
});