mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-04-24 13:55:00 +00:00
Merge pull request #14 from johndoe6345789/copilot/implement-roadmap-features
Implement Constraint Management UI
This commit is contained in:
@@ -54,7 +54,7 @@ This project is a full-stack web application featuring:
|
||||
- 🛠️ **Admin Panel** - Manage tables, columns, and data through a beautiful UI
|
||||
- 📊 **Table Manager** - Create and drop tables with visual column definition
|
||||
- 🔧 **Column Manager** - Add, modify, and drop columns from existing tables
|
||||
- 🔒 **Constraint Manager** - Add and manage UNIQUE and CHECK constraints (API ready, UI in progress)
|
||||
- 🔒 **Constraint Manager** - Add and manage UNIQUE and CHECK constraints (fully implemented)
|
||||
- 📊 **SQL Query Interface** - Execute custom queries with safety validation
|
||||
- 🔒 **JWT Authentication** with secure session management
|
||||
- 📦 **DrizzleORM** - Support for PostgreSQL, MySQL, and SQLite
|
||||
@@ -767,10 +767,9 @@ See [ROADMAP.md](ROADMAP.md) for planned features and improvements.
|
||||
- ✅ Table Manager - Create and drop tables with visual column builder
|
||||
- ✅ Column Manager - Add, modify, and drop columns from existing tables
|
||||
- ✅ Schema management interface for table and column operations
|
||||
- 🔄 Constraint Manager - Add and manage UNIQUE and CHECK constraints (API complete, UI in progress)
|
||||
- ✅ Constraint Manager - Add and manage UNIQUE and CHECK constraints (fully implemented)
|
||||
|
||||
**Upcoming features:**
|
||||
- Complete constraint management UI
|
||||
- Visual database designer
|
||||
- Multi-database server connections
|
||||
- Advanced query builder
|
||||
|
||||
@@ -60,11 +60,11 @@ See `src/config/features.json` for the complete feature configuration.
|
||||
- [x] ✅ Create schema management interface
|
||||
- [x] ✅ Implement table creation/editing UI (API ready, UI implemented)
|
||||
- [x] ✅ Add column type management UI (API ready, UI implemented)
|
||||
- [ ] Add data validation and constraints management 🏗️ **IN PROGRESS**
|
||||
- [x] Add data validation and constraints management ✅ **COMPLETED**
|
||||
- [x] ✅ Implement constraints API (UNIQUE, CHECK constraints)
|
||||
- [x] ✅ Add constraint listing endpoint
|
||||
- [x] ✅ Add constraint creation/deletion endpoints
|
||||
- [ ] Build constraints management UI
|
||||
- [x] ✅ Build constraints management UI
|
||||
- [ ] Add PRIMARY KEY constraint support
|
||||
- [ ] Add DEFAULT value management
|
||||
- [ ] Add NOT NULL constraint management
|
||||
|
||||
25
TESTING.md
25
TESTING.md
@@ -149,9 +149,9 @@ All tests verify that:
|
||||
| Feature Config | - | - | - | 40 | 40 |
|
||||
| Table Manager | 7 | 2 (2 skipped) | 3 | - | 12 |
|
||||
| Column Manager | 9 | 2 (2 skipped) | 3 | - | 14 |
|
||||
| Constraint Manager | 14 | 0 (UI pending) | 3 | 4 | 21 |
|
||||
| Constraint Manager | 14 | 3 (3 skipped) | 4 | 4 | 25 |
|
||||
| Admin Dashboard | - | 3 | 3 | - | 6 |
|
||||
| **Total** | **30** | **7** | **12** | **44** | **93** |
|
||||
| **Total** | **30** | **10** | **16** | **44** | **100** |
|
||||
|
||||
## Feature: Constraint Management Tests
|
||||
|
||||
@@ -186,6 +186,25 @@ Tests for the Constraint Management API endpoints (`/api/admin/constraints`):
|
||||
- Error handling for all CRUD operations
|
||||
- Support for UNIQUE and CHECK constraints
|
||||
|
||||
### End-to-End Tests (Playwright UI Tests)
|
||||
|
||||
#### 2. `tests/e2e/AdminDashboard.e2e.ts` - Constraints Manager UI
|
||||
|
||||
**UI Tests:**
|
||||
- 🔄 Display Constraints tab (requires auth - skipped)
|
||||
- 🔄 Show table selector in Constraints Manager (requires auth - skipped)
|
||||
- 🔄 Open add constraint dialog (requires auth - skipped)
|
||||
|
||||
**Security Tests:**
|
||||
- ✅ Blocks constraint API access without authentication
|
||||
|
||||
**Note:** UI tests are skipped because they require an authenticated session. These can be enabled when a test authentication mechanism is implemented.
|
||||
|
||||
**Components Implemented:**
|
||||
- ✅ `ConstraintManagerTab.tsx` - Main UI component for managing constraints
|
||||
- ✅ `ConstraintDialog.tsx` - Reusable dialog for add/delete constraint operations
|
||||
- ✅ Integration with admin dashboard navigation and handlers
|
||||
|
||||
### Unit Tests
|
||||
|
||||
#### 2. `src/utils/featureConfig.test.ts`
|
||||
@@ -269,4 +288,4 @@ When adding new features:
|
||||
|
||||
**Last Updated:** January 2026
|
||||
**Test Framework:** Playwright + Vitest
|
||||
**Coverage Status:** ✅ API Validation | 🔄 UI Tests (partial - needs auth)
|
||||
**Coverage Status:** ✅ API Validation | 🔄 UI Tests (partial - needs auth) | ✅ Constraint Manager UI Complete
|
||||
|
||||
@@ -5,6 +5,7 @@ import CodeIcon from '@mui/icons-material/Code';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import RuleIcon from '@mui/icons-material/Rule';
|
||||
import StorageIcon from '@mui/icons-material/Storage';
|
||||
import TableChartIcon from '@mui/icons-material/TableChart';
|
||||
import ViewColumnIcon from '@mui/icons-material/ViewColumn';
|
||||
@@ -43,6 +44,7 @@ import {
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import ConstraintManagerTab from '@/components/admin/ConstraintManagerTab';
|
||||
import { theme } from '@/utils/theme';
|
||||
|
||||
const DRAWER_WIDTH = 240;
|
||||
@@ -491,6 +493,71 @@ export default function AdminDashboard() {
|
||||
}
|
||||
};
|
||||
|
||||
// Constraint Management Handlers
|
||||
const handleAddConstraint = async (tableName: string, data: any) => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSuccessMessage('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/constraints', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tableName,
|
||||
...data,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || 'Failed to add constraint');
|
||||
}
|
||||
|
||||
setSuccessMessage(result.message || 'Constraint added successfully');
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDropConstraint = async (tableName: string, constraintName: string) => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSuccessMessage('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/constraints', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tableName,
|
||||
constraintName,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || 'Failed to drop constraint');
|
||||
}
|
||||
|
||||
setSuccessMessage(result.message || 'Constraint dropped successfully');
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
@@ -555,6 +622,14 @@ export default function AdminDashboard() {
|
||||
<ListItemText primary="Column Manager" />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton onClick={() => setTabValue(4)}>
|
||||
<ListItemIcon>
|
||||
<RuleIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Constraints" />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Box>
|
||||
</Drawer>
|
||||
@@ -763,6 +838,14 @@ export default function AdminDashboard() {
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tabValue} index={4}>
|
||||
<ConstraintManagerTab
|
||||
tables={tables}
|
||||
onAddConstraint={handleAddConstraint}
|
||||
onDropConstraint={handleDropConstraint}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
{successMessage && (
|
||||
<Alert severity="success" sx={{ mt: 2 }} onClose={() => setSuccessMessage('')}>
|
||||
{successMessage}
|
||||
|
||||
188
src/components/admin/ConstraintDialog.tsx
Normal file
188
src/components/admin/ConstraintDialog.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
type ConstraintDialogProps = {
|
||||
open: boolean;
|
||||
mode: 'add' | 'delete';
|
||||
constraintTypes: Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
requiresColumn: boolean;
|
||||
requiresExpression: boolean;
|
||||
}>;
|
||||
selectedConstraint?: any;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: any) => Promise<void>;
|
||||
};
|
||||
|
||||
export default function ConstraintDialog({
|
||||
open,
|
||||
mode,
|
||||
constraintTypes,
|
||||
selectedConstraint,
|
||||
onClose,
|
||||
onSubmit,
|
||||
}: ConstraintDialogProps) {
|
||||
const [constraintName, setConstraintName] = useState('');
|
||||
const [constraintType, setConstraintType] = useState('UNIQUE');
|
||||
const [columnName, setColumnName] = useState('');
|
||||
const [checkExpression, setCheckExpression] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
// Reset form when dialog closes
|
||||
setConstraintName('');
|
||||
setConstraintType('UNIQUE');
|
||||
setColumnName('');
|
||||
setCheckExpression('');
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (mode === 'add') {
|
||||
const data: any = {
|
||||
constraintName,
|
||||
constraintType,
|
||||
};
|
||||
|
||||
// Get the current constraint type config
|
||||
const currentType = constraintTypes.find(ct => ct.name === constraintType);
|
||||
|
||||
if (currentType?.requiresColumn) {
|
||||
data.columnName = columnName;
|
||||
}
|
||||
|
||||
if (currentType?.requiresExpression) {
|
||||
data.checkExpression = checkExpression;
|
||||
}
|
||||
|
||||
await onSubmit(data);
|
||||
} else if (mode === 'delete') {
|
||||
// For delete, we just need to confirm
|
||||
await onSubmit({});
|
||||
}
|
||||
onClose();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getTitle = () => {
|
||||
if (mode === 'add') {
|
||||
return 'Add Constraint';
|
||||
}
|
||||
return `Delete Constraint: ${selectedConstraint?.constraint_name}`;
|
||||
};
|
||||
|
||||
const isFormValid = () => {
|
||||
if (mode === 'delete') {
|
||||
return true; // Always valid for delete
|
||||
}
|
||||
|
||||
if (!constraintName.trim() || !constraintType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentType = constraintTypes.find(ct => ct.name === constraintType);
|
||||
|
||||
if (currentType?.requiresColumn && !columnName.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentType?.requiresExpression && !checkExpression.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const currentType = constraintTypes.find(ct => ct.name === constraintType);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>{getTitle()}</DialogTitle>
|
||||
<DialogContent>
|
||||
{mode === 'delete' ? (
|
||||
<Typography variant="body2" color="error" gutterBottom>
|
||||
Are you sure you want to delete the constraint "
|
||||
{selectedConstraint?.constraint_name}"? This action cannot be undone.
|
||||
</Typography>
|
||||
) : (
|
||||
<>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Constraint Name"
|
||||
value={constraintName}
|
||||
onChange={e => setConstraintName(e.target.value)}
|
||||
sx={{ mt: 2, mb: 2 }}
|
||||
helperText="A unique name for this constraint"
|
||||
/>
|
||||
|
||||
<Select
|
||||
fullWidth
|
||||
value={constraintType}
|
||||
onChange={e => setConstraintType(e.target.value)}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
{constraintTypes.map((type) => (
|
||||
<MenuItem key={type.name} value={type.name}>
|
||||
{type.name} - {type.description}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
{currentType?.requiresColumn && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Column Name"
|
||||
value={columnName}
|
||||
onChange={e => setColumnName(e.target.value)}
|
||||
sx={{ mb: 2 }}
|
||||
helperText="The column to apply this constraint to"
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentType?.requiresExpression && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Check Expression"
|
||||
value={checkExpression}
|
||||
onChange={e => setCheckExpression(e.target.value)}
|
||||
sx={{ mb: 2 }}
|
||||
multiline
|
||||
rows={3}
|
||||
helperText="Boolean expression for the check constraint (e.g., price > 0)"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
variant="contained"
|
||||
color={mode === 'delete' ? 'error' : 'primary'}
|
||||
disabled={loading || !isFormValid()}
|
||||
>
|
||||
{mode === 'add' ? 'Add Constraint' : 'Delete Constraint'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
203
src/components/admin/ConstraintManagerTab.tsx
Normal file
203
src/components/admin/ConstraintManagerTab.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
'use client';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
MenuItem,
|
||||
Paper,
|
||||
Select,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { getConstraintTypes, getFeatureById } from '@/utils/featureConfig';
|
||||
import ConstraintDialog from './ConstraintDialog';
|
||||
|
||||
type ConstraintManagerTabProps = {
|
||||
tables: Array<{ table_name: string }>;
|
||||
onAddConstraint: (tableName: string, data: any) => Promise<void>;
|
||||
onDropConstraint: (tableName: string, constraintName: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export default function ConstraintManagerTab({
|
||||
tables,
|
||||
onAddConstraint,
|
||||
onDropConstraint,
|
||||
}: ConstraintManagerTabProps) {
|
||||
const [selectedTable, setSelectedTable] = useState('');
|
||||
const [constraints, setConstraints] = useState<any[]>([]);
|
||||
const [dialogState, setDialogState] = useState<{
|
||||
open: boolean;
|
||||
mode: 'add' | 'delete';
|
||||
}>({ open: false, mode: 'add' });
|
||||
const [selectedConstraint, setSelectedConstraint] = useState<any>(null);
|
||||
|
||||
// Get feature configuration from JSON
|
||||
const feature = getFeatureById('constraint-management');
|
||||
const constraintTypes = getConstraintTypes();
|
||||
|
||||
// Check if actions are enabled from config
|
||||
const canAdd = feature?.ui.actions.includes('add');
|
||||
const canDelete = feature?.ui.actions.includes('delete');
|
||||
|
||||
// Fetch constraints when table is selected
|
||||
const fetchConstraints = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/admin/constraints?tableName=${selectedTable}`,
|
||||
{
|
||||
method: 'GET',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setConstraints(data.constraints || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch constraints:', error);
|
||||
}
|
||||
}, [selectedTable]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTable) {
|
||||
fetchConstraints();
|
||||
} else {
|
||||
setConstraints([]);
|
||||
}
|
||||
}, [selectedTable, fetchConstraints]);
|
||||
|
||||
const handleConstraintOperation = async (data: any) => {
|
||||
if (dialogState.mode === 'add') {
|
||||
await onAddConstraint(selectedTable, data);
|
||||
} else if (dialogState.mode === 'delete' && selectedConstraint) {
|
||||
await onDropConstraint(selectedTable, selectedConstraint.constraint_name);
|
||||
}
|
||||
await fetchConstraints(); // Refresh constraints list
|
||||
};
|
||||
|
||||
const openAddDialog = () => {
|
||||
setSelectedConstraint(null);
|
||||
setDialogState({ open: true, mode: 'add' });
|
||||
};
|
||||
|
||||
const openDeleteDialog = (constraint: any) => {
|
||||
setSelectedConstraint(constraint);
|
||||
setDialogState({ open: true, mode: 'delete' });
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
setDialogState({ ...dialogState, open: false });
|
||||
setSelectedConstraint(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
{feature?.name || 'Constraint Manager'}
|
||||
</Typography>
|
||||
|
||||
{feature?.description && (
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
{feature.description}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Box sx={{ mt: 2, mb: 2 }}>
|
||||
<Select
|
||||
value={selectedTable}
|
||||
onChange={e => setSelectedTable(e.target.value)}
|
||||
displayEmpty
|
||||
fullWidth
|
||||
sx={{ maxWidth: 400 }}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Select a table</em>
|
||||
</MenuItem>
|
||||
{tables.map(table => (
|
||||
<MenuItem key={table.table_name} value={table.table_name}>
|
||||
{table.table_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
|
||||
{selectedTable && (
|
||||
<>
|
||||
<Box sx={{ mt: 2, mb: 2 }}>
|
||||
{canAdd && (
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={openAddDialog}
|
||||
>
|
||||
Add Constraint
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Paper sx={{ mt: 2 }}>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Constraint Name</TableCell>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>Column</TableCell>
|
||||
<TableCell>Expression</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{constraints.map((constraint) => (
|
||||
<TableRow key={constraint.constraint_name}>
|
||||
<TableCell>{constraint.constraint_name}</TableCell>
|
||||
<TableCell>{constraint.constraint_type}</TableCell>
|
||||
<TableCell>{constraint.column_name || '-'}</TableCell>
|
||||
<TableCell>{constraint.check_clause || '-'}</TableCell>
|
||||
<TableCell align="right">
|
||||
{canDelete && (
|
||||
<Button
|
||||
size="small"
|
||||
color="error"
|
||||
startIcon={<DeleteIcon />}
|
||||
onClick={() => openDeleteDialog(constraint)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{constraints.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} align="center">
|
||||
No constraints found for this table
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
</>
|
||||
)}
|
||||
|
||||
<ConstraintDialog
|
||||
open={dialogState.open}
|
||||
mode={dialogState.mode}
|
||||
constraintTypes={constraintTypes}
|
||||
selectedConstraint={selectedConstraint}
|
||||
onSubmit={handleConstraintOperation}
|
||||
onClose={closeDialog}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -95,5 +95,44 @@ test.describe('Admin Dashboard', () => {
|
||||
|
||||
expect(response.status()).toBe(401);
|
||||
});
|
||||
|
||||
test('should not allow constraint management without auth', async ({ page }) => {
|
||||
const response = await page.request.get('/api/admin/constraints?tableName=test');
|
||||
|
||||
expect(response.status()).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Constraints Manager UI', () => {
|
||||
test.skip('should display Constraints tab after login', async ({ page }) => {
|
||||
// This test would require actual authentication
|
||||
// Skipping for now as it needs a real admin user
|
||||
|
||||
// await page.goto('/admin/dashboard');
|
||||
// await expect(page.getByText('Constraints')).toBeVisible();
|
||||
});
|
||||
|
||||
test.skip('should show table selector in Constraints Manager', async ({ page }) => {
|
||||
// This test would require authentication
|
||||
// Skipping for now
|
||||
|
||||
// await page.goto('/admin/dashboard');
|
||||
// await page.getByText('Constraints').click();
|
||||
|
||||
// await expect(page.getByText(/select a table/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test.skip('should open add constraint dialog', async ({ page }) => {
|
||||
// This test would require authentication
|
||||
// Skipping for now
|
||||
|
||||
// await page.goto('/admin/dashboard');
|
||||
// await page.getByText('Constraints').click();
|
||||
// Select a table first
|
||||
// await page.getByRole('button', { name: /add constraint/i }).click();
|
||||
|
||||
// await expect(page.getByText('Add Constraint')).toBeVisible();
|
||||
// await expect(page.getByLabel(/constraint name/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user