feat: Implement node deletion with shared modal and fix SurrealDB RecordId handling
Implements complete node deletion functionality for both galaxy view and debug panel: **Core Changes:** - Created shared DeleteNodeModal component used by both ThoughtGalaxy and UserMenu - Modal provides consistent UX with proper confirmation messaging - Deletion follows write-through cache pattern: ATproto first, then SurrealDB **SurrealDB RecordId Fixes:** - Fixed SELECT query to use type::thing($table, $recordId) for UUID-based RecordIds - Fixed DELETE query to use type::thing() instead of db.delete() to handle dashes in UUIDs - Without type::thing(), SurrealDB interprets dashes as subtraction operators **Testing & Documentation:** - Added comprehensive Magnitude tests for delete functionality (galaxy view and debug panel) - Updated CLAUDE.md with complete testing workflow documentation - Added pre-commit checklist requiring database verification and test execution - Documented PlaywrightMCP manual testing process before Magnitude test writing **Database Setup:** - Configured docker-compose.yml to use environment variables for credentials - Updated namespace/database to match .env configuration (ponderants/main) **File Changes:** - app/api/nodes/[id]/route.ts: Fixed RecordId query patterns (SELECT and DELETE) - components/DeleteNodeModal.tsx: New shared modal component - components/ThoughtGalaxy.tsx: Uses shared DeleteNodeModal - components/UserMenu.tsx: Replaced browser confirm() with shared DeleteNodeModal - tests/magnitude/03-delete-node.mag.ts: Added debug panel delete test - AGENTS.md: Added testing workflow and pre-commit checklist documentation - docker-compose.yml: Environment variable configuration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
100
AGENTS.md
100
AGENTS.md
@@ -17,6 +17,106 @@ EOF
|
|||||||
|
|
||||||
These credentials should be used for all automated testing (Magnitude, Playwright) and manual testing when needed. Do not attempt to authenticate without using these credentials.
|
These credentials should be used for all automated testing (Magnitude, Playwright) and manual testing when needed. Do not attempt to authenticate without using these credentials.
|
||||||
|
|
||||||
|
**Database Setup**: The application uses SurrealDB running in Docker Compose for the app cache layer:
|
||||||
|
|
||||||
|
1. Start the database services:
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
2. This starts two services:
|
||||||
|
- `surrealdb`: The main SurrealDB instance (port 8000)
|
||||||
|
- `surrealmcp`: SurrealMCP server for MCP access (port 8080)
|
||||||
|
|
||||||
|
3. Start the Next.js development server:
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
4. To stop the services:
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Configuration:
|
||||||
|
- SurrealDB runs in-memory mode (data is not persisted between restarts)
|
||||||
|
- Namespace: `ponderants`
|
||||||
|
- Database: `main`
|
||||||
|
- Credentials: `root/root`
|
||||||
|
|
||||||
|
**Note**: Always start docker compose services before starting the Next.js dev server to ensure the database is available.
|
||||||
|
|
||||||
|
**Testing Workflow**: All new features must follow a rigorous testing process before being committed:
|
||||||
|
|
||||||
|
1. **Manual Testing with Playwright MCP**:
|
||||||
|
- Use Playwright MCP tools to manually test all functionality interactively
|
||||||
|
- Test both happy paths (expected user flows) and unhappy paths (errors, edge cases)
|
||||||
|
- Document each step you verify during manual testing - these become test cases
|
||||||
|
- If you encounter issues during manual testing (e.g., 404 errors, unexpected behavior), investigate and fix them before proceeding
|
||||||
|
- Use the following pattern:
|
||||||
|
```
|
||||||
|
1. Navigate to the feature
|
||||||
|
2. Perform user actions (clicks, typing, etc.)
|
||||||
|
3. Verify expected outcomes
|
||||||
|
4. Test error scenarios
|
||||||
|
5. Verify cleanup/state updates
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Write Comprehensive Magnitude Tests**:
|
||||||
|
- After manually verifying functionality with Playwright MCP, write extensive Magnitude tests covering ALL verified behaviors
|
||||||
|
- Each manual test step should have a corresponding Magnitude test assertion
|
||||||
|
- Test files are located in `tests/magnitude/` with `.mag.ts` extension
|
||||||
|
- Use the test credentials from .env (TEST_BLUESKY_HANDLE, TEST_BLUESKY_PASSWORD)
|
||||||
|
- Include both happy path and unhappy path test cases
|
||||||
|
- Example test structure:
|
||||||
|
```typescript
|
||||||
|
import { test } from 'magnitude-test';
|
||||||
|
|
||||||
|
const TEST_HANDLE = process.env.TEST_BLUESKY_HANDLE;
|
||||||
|
const TEST_PASSWORD = process.env.TEST_BLUESKY_PASSWORD;
|
||||||
|
|
||||||
|
test('Feature description', async (agent) => {
|
||||||
|
await agent.act('Navigate to /page');
|
||||||
|
await agent.act('Perform user action');
|
||||||
|
await agent.check('Verify expected outcome');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Reusable Playwright Scaffolding**:
|
||||||
|
- Abstract common patterns (auth, navigation, etc.) into helper files in `tests/playwright/helpers/`
|
||||||
|
- These helpers should be usable both during manual Playwright MCP testing AND by Magnitude tests
|
||||||
|
- Examples: `tests/playwright/helpers/chat.ts`, `tests/playwright/helpers/galaxy.ts`, `tests/playwright/helpers/node.ts`
|
||||||
|
- For auth setup, use Playwright's global setup pattern (see https://playwright.dev/docs/test-global-setup-teardown)
|
||||||
|
- Current auth setup: `tests/playwright/auth.setup.ts`
|
||||||
|
|
||||||
|
4. **Generating Playwright Code**:
|
||||||
|
- Use https://playwright.dev/docs/test-agents to generate Playwright test code when helpful
|
||||||
|
- This tool can convert natural language test descriptions into Playwright code
|
||||||
|
|
||||||
|
5. **Test Execution**:
|
||||||
|
- Run Magnitude tests: `pnpm test` or `npx magnitude`
|
||||||
|
- Ensure ALL tests pass before committing
|
||||||
|
- If tests fail, fix the implementation or update the tests to match the correct behavior
|
||||||
|
|
||||||
|
6. **Pre-Commit Checklist**:
|
||||||
|
- ✅ All manual testing with Playwright MCP completed and verified
|
||||||
|
- ✅ All Magnitude tests written and cover all verified functionality
|
||||||
|
- ✅ Database verified for expected state after operations (e.g., deletions actually removed records)
|
||||||
|
- ✅ Run ALL magnitude tests: `pnpm test`
|
||||||
|
- ✅ All tests passing
|
||||||
|
- ✅ No console errors or warnings in production code paths
|
||||||
|
- Only commit after ALL checklist items are complete
|
||||||
|
|
||||||
|
7. **Documentation**:
|
||||||
|
- Document test coverage in `tests/README.md`
|
||||||
|
- Add comments to complex test scenarios explaining the business logic being tested
|
||||||
|
|
||||||
|
**Testing Resources**:
|
||||||
|
- Playwright Global Setup/Teardown: https://playwright.dev/docs/test-global-setup-teardown
|
||||||
|
- Playwright Test Agents: https://playwright.dev/docs/test-agents
|
||||||
|
- Magnitude.run Documentation: https://magnitude.run/docs
|
||||||
|
- Project Test README: `tests/README.md`
|
||||||
|
|
||||||
You are an expert-level, full-stack AI coding agent. Your task is to implement
|
You are an expert-level, full-stack AI coding agent. Your task is to implement
|
||||||
the "Ponderants" application. Product Vision: Ponderants is an AI-powered
|
the "Ponderants" application. Product Vision: Ponderants is an AI-powered
|
||||||
thought partner that interviews a user to capture, structure, and visualize
|
thought partner that interviews a user to capture, structure, and visualize
|
||||||
|
|||||||
@@ -50,9 +50,13 @@ export async function DELETE(
|
|||||||
// 1. Fetch the node from SurrealDB to verify ownership and get atp_uri
|
// 1. Fetch the node from SurrealDB to verify ownership and get atp_uri
|
||||||
const db = await connectToDB();
|
const db = await connectToDB();
|
||||||
|
|
||||||
|
// Parse the ID to extract table and record ID parts
|
||||||
|
// Format: "node:e9e38d09-c0f4-4834-a6ba-c92dfa4c0910" or "node:⟨e9e38d09-c0f4-4834-a6ba-c92dfa4c0910⟩"
|
||||||
|
const cleanId = id.replace(/^node:/, '').replace(/[⟨⟩]/g, '');
|
||||||
|
|
||||||
const nodeResult = await db.query<[Array<{ id: string; user_did: string; atp_uri: string }>]>(
|
const nodeResult = await db.query<[Array<{ id: string; user_did: string; atp_uri: string }>]>(
|
||||||
'SELECT id, user_did, atp_uri FROM node WHERE id = $nodeId',
|
'SELECT id, user_did, atp_uri FROM node WHERE id = type::thing($table, $recordId)',
|
||||||
{ nodeId: id }
|
{ table: 'node', recordId: cleanId }
|
||||||
);
|
);
|
||||||
|
|
||||||
const node = nodeResult[0]?.[0];
|
const node = nodeResult[0]?.[0];
|
||||||
@@ -114,7 +118,11 @@ export async function DELETE(
|
|||||||
|
|
||||||
// 4. Delete from SurrealDB cache (only after successful ATproto deletion)
|
// 4. Delete from SurrealDB cache (only after successful ATproto deletion)
|
||||||
try {
|
try {
|
||||||
await db.delete(id);
|
// Use type::thing() to properly construct the RecordId for deletion
|
||||||
|
await db.query('DELETE FROM type::thing($table, $recordId)', {
|
||||||
|
table: 'node',
|
||||||
|
recordId: cleanId,
|
||||||
|
});
|
||||||
console.log('[DELETE /api/nodes/[id]] ✓ Deleted node from SurrealDB cache');
|
console.log('[DELETE /api/nodes/[id]] ✓ Deleted node from SurrealDB cache');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[DELETE /api/nodes/[id]] ⚠ SurrealDB cache deletion failed (non-critical):', error);
|
console.warn('[DELETE /api/nodes/[id]] ⚠ SurrealDB cache deletion failed (non-critical):', error);
|
||||||
|
|||||||
58
components/DeleteNodeModal.tsx
Normal file
58
components/DeleteNodeModal.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Modal, Stack, Text, Group, Button } from '@mantine/core';
|
||||||
|
import { IconTrash } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
interface DeleteNodeModalProps {
|
||||||
|
opened: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
nodeTitle: string | null;
|
||||||
|
isDeleting: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeleteNodeModal({
|
||||||
|
opened,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
nodeTitle,
|
||||||
|
isDeleting,
|
||||||
|
}: DeleteNodeModalProps) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title="Delete Node"
|
||||||
|
centered
|
||||||
|
zIndex={1001}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text>
|
||||||
|
Are you sure you want to delete "{nodeTitle}"? This will:
|
||||||
|
</Text>
|
||||||
|
<Stack gap="xs" ml="md">
|
||||||
|
<Text size="sm">• Remove the post from Bluesky</Text>
|
||||||
|
<Text size="sm">• Delete the node from your galaxy</Text>
|
||||||
|
<Text size="sm" fw={600} c="red">This action cannot be undone.</Text>
|
||||||
|
</Stack>
|
||||||
|
<Group justify="flex-end" gap="sm">
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={onClose}
|
||||||
|
disabled={isDeleting}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={onConfirm}
|
||||||
|
loading={isDeleting}
|
||||||
|
leftSection={<IconTrash size={16} />}
|
||||||
|
>
|
||||||
|
Delete Permanently
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,9 +7,10 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from '@react-three/drei';
|
} from '@react-three/drei';
|
||||||
import { Suspense, useEffect, useRef, useState } from 'react';
|
import { Suspense, useEffect, useRef, useState } from 'react';
|
||||||
import { Stack, Text as MantineText, Paper, Title, Box, CloseButton, Group, Anchor, useComputedColorScheme, Button, Modal } from '@mantine/core';
|
import { Stack, Text as MantineText, Paper, Title, Box, CloseButton, Group, Anchor, useComputedColorScheme, Button } from '@mantine/core';
|
||||||
import { IconTrash } from '@tabler/icons-react';
|
import { IconTrash } from '@tabler/icons-react';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
|
import { DeleteNodeModal } from './DeleteNodeModal';
|
||||||
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
@@ -482,41 +483,13 @@ export function ThoughtGalaxy() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Delete confirmation modal */}
|
{/* Delete confirmation modal */}
|
||||||
<Modal
|
<DeleteNodeModal
|
||||||
opened={deleteConfirmOpen}
|
opened={deleteConfirmOpen}
|
||||||
onClose={() => setDeleteConfirmOpen(false)}
|
onClose={() => setDeleteConfirmOpen(false)}
|
||||||
title="Delete Node"
|
onConfirm={handleDeleteNode}
|
||||||
centered
|
nodeTitle={selectedNode?.title || null}
|
||||||
zIndex={1001}
|
isDeleting={isDeleting}
|
||||||
>
|
/>
|
||||||
<Stack gap="md">
|
|
||||||
<MantineText>
|
|
||||||
Are you sure you want to delete this node? This will:
|
|
||||||
</MantineText>
|
|
||||||
<Stack gap="xs" ml="md">
|
|
||||||
<MantineText size="sm">• Remove the post from Bluesky</MantineText>
|
|
||||||
<MantineText size="sm">• Delete the node from your galaxy</MantineText>
|
|
||||||
<MantineText size="sm" fw={600} c="red">This action cannot be undone.</MantineText>
|
|
||||||
</Stack>
|
|
||||||
<Group justify="flex-end" gap="sm">
|
|
||||||
<Button
|
|
||||||
variant="subtle"
|
|
||||||
onClick={() => setDeleteConfirmOpen(false)}
|
|
||||||
disabled={isDeleting}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="red"
|
|
||||||
onClick={handleDeleteNode}
|
|
||||||
loading={isDeleting}
|
|
||||||
leftSection={<IconTrash size={16} />}
|
|
||||||
>
|
|
||||||
Delete Permanently
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Canvas
|
<Canvas
|
||||||
camera={{ position: [0, 5, 10], fov: 60 }}
|
camera={{ position: [0, 5, 10], fov: 60 }}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useMantineColorScheme } from '@mantine/core';
|
|||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
import { IconSun, IconMoon, IconDeviceDesktop } from '@tabler/icons-react';
|
import { IconSun, IconMoon, IconDeviceDesktop } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { DeleteNodeModal } from './DeleteNodeModal';
|
||||||
|
|
||||||
interface UserProfile {
|
interface UserProfile {
|
||||||
did: string;
|
did: string;
|
||||||
@@ -28,6 +29,8 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
|
|||||||
const [nodes, setNodes] = useState<Node[]>([]);
|
const [nodes, setNodes] = useState<Node[]>([]);
|
||||||
const [nodesLoading, setNodesLoading] = useState(false);
|
const [nodesLoading, setNodesLoading] = useState(false);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||||
|
const [nodeToDelete, setNodeToDelete] = useState<Node | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fetch user profile on mount
|
// Fetch user profile on mount
|
||||||
@@ -65,12 +68,15 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Delete a node (debug) - Matches ThoughtGalaxy delete pattern
|
// Delete a node (debug) - Matches ThoughtGalaxy delete pattern
|
||||||
const handleDebugDelete = async (nodeId: string) => {
|
const handleDebugDelete = async () => {
|
||||||
|
if (!nodeToDelete) return;
|
||||||
|
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
|
setDeleteConfirmOpen(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Extract clean ID from SurrealDB RecordId format (removes angle brackets ⟨⟩)
|
// Extract clean ID from SurrealDB RecordId format (removes angle brackets ⟨⟩)
|
||||||
const cleanId = String(nodeId).replace(/[⟨⟩]/g, '');
|
const cleanId = String(nodeToDelete.id).replace(/[⟨⟩]/g, '');
|
||||||
|
|
||||||
const response = await fetch(`/api/nodes/${cleanId}`, {
|
const response = await fetch(`/api/nodes/${cleanId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@@ -89,7 +95,8 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update local state to remove the deleted node
|
// Update local state to remove the deleted node
|
||||||
setNodes((prevNodes) => prevNodes.filter((n) => n.id !== nodeId));
|
setNodes((prevNodes) => prevNodes.filter((n) => n.id !== nodeToDelete.id));
|
||||||
|
setNodeToDelete(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[UserMenu Debug] Delete error:', error);
|
console.error('[UserMenu Debug] Delete error:', error);
|
||||||
notifications.show({
|
notifications.show({
|
||||||
@@ -269,9 +276,8 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
|
|||||||
<Text size="xs" fw={600}>{node.title}</Text>
|
<Text size="xs" fw={600}>{node.title}</Text>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (confirm(`Delete "${node.title}"?`)) {
|
setNodeToDelete(node);
|
||||||
handleDebugDelete(node.id);
|
setDeleteConfirmOpen(true);
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
background: '#fa5252',
|
background: '#fa5252',
|
||||||
@@ -282,6 +288,7 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
|
|||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontSize: '0.65rem',
|
fontSize: '0.65rem',
|
||||||
}}
|
}}
|
||||||
|
disabled={isDeleting}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
@@ -306,6 +313,15 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
|
|||||||
Log out
|
Log out
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.Dropdown>
|
</Menu.Dropdown>
|
||||||
|
|
||||||
|
{/* Delete confirmation modal - shared with ThoughtGalaxy */}
|
||||||
|
<DeleteNodeModal
|
||||||
|
opened={deleteConfirmOpen}
|
||||||
|
onClose={() => setDeleteConfirmOpen(false)}
|
||||||
|
onConfirm={handleDebugDelete}
|
||||||
|
nodeTitle={nodeToDelete?.title || null}
|
||||||
|
isDeleting={isDeleting}
|
||||||
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,27 @@ services:
|
|||||||
- --log
|
- --log
|
||||||
- trace
|
- trace
|
||||||
- --user
|
- --user
|
||||||
- root
|
- ${SURREALDB_USER}
|
||||||
- --pass
|
- --pass
|
||||||
- root
|
- ${SURREALDB_PASS}
|
||||||
- memory
|
- memory
|
||||||
|
volumes:
|
||||||
|
- ./surreal/data:/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
|
surrealmcp:
|
||||||
|
image: surrealdb/surrealmcp:latest
|
||||||
|
command: >
|
||||||
|
start
|
||||||
|
--bind-address 0.0.0.0:8080
|
||||||
|
--server-url http://localhost:8080
|
||||||
|
-e ws://surrealdb:8000/rpc
|
||||||
|
--ns ${SURREALDB_NS} --db ${SURREALDB_DB} -u ${SURREALDB_USER} -p ${SURREALDB_PASS}
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
depends_on:
|
||||||
|
- surrealdb
|
||||||
|
|||||||
@@ -135,3 +135,70 @@ test('Node deletion removes associated links', async (agent) => {
|
|||||||
await agent.check('The link between the nodes is no longer visible');
|
await agent.check('The link between the nodes is no longer visible');
|
||||||
await agent.check('Only one node remains in the galaxy');
|
await agent.check('Only one node remains in the galaxy');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('User can delete node from debug panel in Profile menu', async (agent) => {
|
||||||
|
// Act: Log in
|
||||||
|
await agent.act('Navigate to /login');
|
||||||
|
await agent.act(`Type "${TEST_HANDLE}" into the "Your Handle" input field`);
|
||||||
|
await agent.act('Click the "Log in with Bluesky" button');
|
||||||
|
await agent.check('The page URL contains "bsky.social"');
|
||||||
|
await agent.act(`Type "${TEST_HANDLE}" into the username/identifier field`);
|
||||||
|
await agent.act(`Type "${TEST_PASSWORD}" into the password field`);
|
||||||
|
await agent.act('Click the submit/authorize button');
|
||||||
|
await agent.check('The page URL contains "/chat"');
|
||||||
|
|
||||||
|
// Act: Create a test node via chat
|
||||||
|
await agent.act('Type "Test node for debug panel deletion" into the chat input');
|
||||||
|
await agent.act('Press Enter or click send');
|
||||||
|
await agent.check('AI responds with a message');
|
||||||
|
|
||||||
|
// Act: Trigger node creation and publish
|
||||||
|
await agent.act('Wait for the AI to suggest creating a node or manually trigger node creation');
|
||||||
|
await agent.check('A node draft is created in the editor');
|
||||||
|
await agent.act('Click the "Publish" button');
|
||||||
|
await agent.check('A success notification appears');
|
||||||
|
|
||||||
|
// Act: Open Profile menu
|
||||||
|
await agent.act('Click the "Profile" button in the navigation sidebar');
|
||||||
|
await agent.check('The Profile menu opens');
|
||||||
|
|
||||||
|
// Check: Verify debug panel is visible (development mode only)
|
||||||
|
await agent.check('A "Debug: SurrealDB Nodes" section is visible');
|
||||||
|
|
||||||
|
// Act: Fetch nodes from debug panel
|
||||||
|
await agent.act('Click the "Fetch Nodes" button');
|
||||||
|
await agent.check('The button shows a count greater than 0');
|
||||||
|
await agent.check('At least one node is listed in the debug panel');
|
||||||
|
|
||||||
|
// Check: Verify the test node appears
|
||||||
|
await agent.check('The node "Test node for debug panel deletion" is visible');
|
||||||
|
|
||||||
|
// Act: Click delete button in debug panel
|
||||||
|
await agent.act('Click the "Delete" button next to the test node');
|
||||||
|
|
||||||
|
// Check: Verify delete confirmation modal appears
|
||||||
|
await agent.check('A delete confirmation modal appears');
|
||||||
|
await agent.check('The modal is displayed above the profile menu');
|
||||||
|
await agent.check('The modal shows the node title "Test node for debug panel deletion"');
|
||||||
|
await agent.check('The modal explains this will remove the post from Bluesky');
|
||||||
|
await agent.check('The modal shows "This action cannot be undone"');
|
||||||
|
await agent.check('The modal has a "Delete Permanently" button');
|
||||||
|
await agent.check('The modal has a "Cancel" button');
|
||||||
|
|
||||||
|
// Act: Confirm deletion
|
||||||
|
await agent.act('Click the "Delete Permanently" button');
|
||||||
|
|
||||||
|
// Check: Verify deletion succeeded
|
||||||
|
await agent.check('A success notification appears saying "Node deleted"');
|
||||||
|
await agent.check('The notification says "Node has been deleted from Bluesky and your galaxy"');
|
||||||
|
await agent.check('The modal closes');
|
||||||
|
|
||||||
|
// Check: Verify node is removed from debug panel
|
||||||
|
await agent.check('The "Fetch Nodes" button shows a count of 0 or the node is no longer in the list');
|
||||||
|
|
||||||
|
// Act: Verify node is deleted from Bluesky and database
|
||||||
|
await agent.act('Refresh the page');
|
||||||
|
await agent.act('Click the "Profile" button again');
|
||||||
|
await agent.act('Click the "Fetch Nodes" button');
|
||||||
|
await agent.check('The node "Test node for debug panel deletion" is not in the list');
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user