mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-04-24 13:55:00 +00:00
Refactor admin components to use atomic component library and add utilities
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
@@ -1,23 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
Checkbox,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
} from '../atoms';
|
||||
import Button from '../atoms/Button';
|
||||
import TextField from '../atoms/TextField';
|
||||
import Typography from '../atoms/Typography';
|
||||
import IconButton from '../atoms/IconButton';
|
||||
|
||||
type Column = {
|
||||
name: string;
|
||||
@@ -141,21 +139,15 @@ export default function CreateTableDialog({
|
||||
sx={{ mr: 1 }}
|
||||
/>
|
||||
{columns.length > 1 && (
|
||||
<IconButton onClick={() => removeColumn(index)} color="error" size="small">
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => removeColumn(index)} color="error" size="small" icon="Delete" />
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
<Button startIcon={<AddIcon />} onClick={addColumn} variant="outlined">
|
||||
Add Column
|
||||
</Button>
|
||||
<Button startIcon="Add" onClick={addColumn} variant="outlined" text="Add Column" />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleCreate} variant="contained" disabled={loading || !tableName.trim()}>
|
||||
Create Table
|
||||
</Button>
|
||||
<Button onClick={handleClose} text="Cancel" />
|
||||
<Button onClick={handleCreate} variant="contained" disabled={loading || !tableName.trim()} text="Create Table" />
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
TableHead,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
} from '../atoms';
|
||||
import IconButton from '../atoms/IconButton';
|
||||
|
||||
type DataGridProps = {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
MenuItem,
|
||||
Select,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
} from '../atoms';
|
||||
import Button from '../atoms/Button';
|
||||
import Typography from '../atoms/Typography';
|
||||
|
||||
type DropTableDialogProps = {
|
||||
open: boolean;
|
||||
@@ -70,15 +70,14 @@ export default function DropTableDialog({
|
||||
</Select>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleClose} text="Cancel" />
|
||||
<Button
|
||||
onClick={handleDrop}
|
||||
color="error"
|
||||
variant="contained"
|
||||
disabled={loading || !selectedTable}
|
||||
>
|
||||
Drop Table
|
||||
</Button>
|
||||
text="Drop Table"
|
||||
/>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
52
src/components/atoms/Button.generated.stories.tsx
Normal file
52
src/components/atoms/Button.generated.stories.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import Button from './Button';
|
||||
import { generateMeta, generateStories } from '@/utils/storybook/storyGenerator';
|
||||
|
||||
/**
|
||||
* Example of using story generator with features.json configuration
|
||||
* This demonstrates how to leverage the storybookStories section from features.json
|
||||
*/
|
||||
|
||||
// Generate meta from features.json
|
||||
const meta = generateMeta(Button, 'Button', {
|
||||
title: 'Atoms/Button',
|
||||
}) satisfies Meta<typeof Button>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// Generate stories from features.json
|
||||
const generatedStories = generateStories<typeof Button>('Button');
|
||||
|
||||
// Export individual stories
|
||||
export const Primary: Story = generatedStories.primary || {
|
||||
args: {
|
||||
variant: 'contained',
|
||||
color: 'primary',
|
||||
text: 'Primary Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary: Story = generatedStories.secondary || {
|
||||
args: {
|
||||
variant: 'outlined',
|
||||
color: 'secondary',
|
||||
text: 'Secondary Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcon: Story = generatedStories.withIcon || {
|
||||
args: {
|
||||
variant: 'contained',
|
||||
startIcon: 'Add',
|
||||
text: 'Add Item',
|
||||
},
|
||||
};
|
||||
|
||||
export const Loading: Story = generatedStories.loading || {
|
||||
args: {
|
||||
variant: 'contained',
|
||||
disabled: true,
|
||||
text: 'Loading...',
|
||||
},
|
||||
};
|
||||
@@ -44,4 +44,5 @@ export {
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Chip,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
|
||||
76
src/utils/storybook/storyGenerator.ts
Normal file
76
src/utils/storybook/storyGenerator.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { getAllStorybookStories, getStorybookStoriesForComponent, StorybookStory } from '@/utils/featureConfig';
|
||||
|
||||
/**
|
||||
* Generate Storybook meta configuration from features.json
|
||||
*/
|
||||
export function generateMeta<T>(
|
||||
component: T,
|
||||
componentName: string,
|
||||
customMeta?: Partial<Meta<T>>
|
||||
): Meta<T> {
|
||||
const stories = getStorybookStoriesForComponent(componentName);
|
||||
const defaultStory = stories.default;
|
||||
|
||||
return {
|
||||
title: `Components/${componentName}`,
|
||||
component: component as any,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
...defaultStory?.parameters,
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
...customMeta,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a single story from features.json story definition
|
||||
*/
|
||||
export function generateStory<T>(
|
||||
storyConfig: StorybookStory
|
||||
): StoryObj<T> {
|
||||
return {
|
||||
name: storyConfig.name,
|
||||
args: storyConfig.args || {},
|
||||
parameters: storyConfig.parameters,
|
||||
// Note: play functions would need to be converted from strings to actual functions
|
||||
// This is a limitation of JSON - we can only store the play steps as strings
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate all stories for a component from features.json
|
||||
*/
|
||||
export function generateStories<T>(componentName: string): Record<string, StoryObj<T>> {
|
||||
const stories = getStorybookStoriesForComponent(componentName);
|
||||
const result: Record<string, StoryObj<T>> = {};
|
||||
|
||||
for (const [key, storyConfig] of Object.entries(stories)) {
|
||||
result[key] = generateStory<T>(storyConfig);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available story configurations
|
||||
*/
|
||||
export function listStorybookComponents(): string[] {
|
||||
return Object.keys(getAllStorybookStories());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create mock handlers for stories
|
||||
*/
|
||||
export function createMockHandlers(handlerNames: string[]): Record<string, () => void> {
|
||||
const handlers: Record<string, () => void> = {};
|
||||
|
||||
for (const name of handlerNames) {
|
||||
handlers[name] = () => {
|
||||
console.log(`Mock handler called: ${name}`);
|
||||
};
|
||||
}
|
||||
|
||||
return handlers;
|
||||
}
|
||||
135
tests/utils/playbookRunner.ts
Normal file
135
tests/utils/playbookRunner.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Page, expect } from '@playwright/test';
|
||||
import { getAllPlaywrightPlaybooks, PlaywrightPlaybook, PlaywrightStep } from '@/utils/featureConfig';
|
||||
|
||||
/**
|
||||
* Execute a single Playwright step from a playbook
|
||||
*/
|
||||
export async function executeStep(page: Page, step: PlaywrightStep, variables: Record<string, string> = {}) {
|
||||
// Replace variables in step properties
|
||||
const replaceVars = (str: string | undefined): string => {
|
||||
if (!str) return '';
|
||||
return str.replace(/\{\{(\w+)\}\}/g, (_, key) => variables[key] || '');
|
||||
};
|
||||
|
||||
const selector = replaceVars(step.selector);
|
||||
const value = replaceVars(step.value);
|
||||
const url = replaceVars(step.url);
|
||||
const text = replaceVars(step.text);
|
||||
|
||||
switch (step.action) {
|
||||
case 'goto':
|
||||
if (url) {
|
||||
await page.goto(url);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'click':
|
||||
if (selector) {
|
||||
await page.click(selector);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'fill':
|
||||
if (selector && value) {
|
||||
await page.fill(selector, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'select':
|
||||
if (selector && value) {
|
||||
await page.selectOption(selector, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wait':
|
||||
if (step.timeout) {
|
||||
await page.waitForTimeout(step.timeout);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'expect':
|
||||
if (url === 'redirected') {
|
||||
await expect(page).toHaveURL(new RegExp(selector || ''));
|
||||
} else if (text === 'visible' && selector) {
|
||||
await expect(page.locator(selector)).toBeVisible();
|
||||
} else if (text && selector) {
|
||||
await expect(page.locator(selector)).toContainText(text);
|
||||
} else if (text) {
|
||||
// Check for status code in text
|
||||
const statusCode = parseInt(text, 10);
|
||||
if (!isNaN(statusCode)) {
|
||||
// This is a status code check - would need API interception
|
||||
// For now, we skip this as it requires special handling
|
||||
console.warn(`Status code check (${statusCode}) not yet implemented in playbook runner`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'screenshot':
|
||||
if (selector) {
|
||||
await page.locator(selector).screenshot({ path: `screenshots/${Date.now()}-${selector.replace(/[^a-z0-9]/gi, '_')}.png` });
|
||||
} else {
|
||||
await page.screenshot({ path: `screenshots/${Date.now()}-page.png` });
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`Unknown step action: ${step.action}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a full playbook from features.json
|
||||
*/
|
||||
export async function runPlaybook(
|
||||
page: Page,
|
||||
playbookName: string,
|
||||
variables: Record<string, string> = {},
|
||||
options: { runCleanup?: boolean } = {}
|
||||
) {
|
||||
const playbooks = getAllPlaywrightPlaybooks();
|
||||
const playbook = playbooks[playbookName];
|
||||
|
||||
if (!playbook) {
|
||||
throw new Error(`Playbook not found: ${playbookName}`);
|
||||
}
|
||||
|
||||
console.log(`Running playbook: ${playbook.name}`);
|
||||
console.log(`Description: ${playbook.description}`);
|
||||
|
||||
// Execute main steps
|
||||
for (const step of playbook.steps) {
|
||||
await executeStep(page, step, variables);
|
||||
}
|
||||
|
||||
// Execute cleanup steps if requested and they exist
|
||||
if (options.runCleanup && playbook.cleanup) {
|
||||
console.log('Running cleanup steps...');
|
||||
for (const step of playbook.cleanup) {
|
||||
await executeStep(page, step, variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all playbooks by tag
|
||||
*/
|
||||
export function getPlaybooksByTag(tag: string): Record<string, PlaywrightPlaybook> {
|
||||
const allPlaybooks = getAllPlaywrightPlaybooks();
|
||||
const filtered: Record<string, PlaywrightPlaybook> = {};
|
||||
|
||||
for (const [name, playbook] of Object.entries(allPlaybooks)) {
|
||||
if (playbook.tags?.includes(tag)) {
|
||||
filtered[name] = playbook;
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available playbooks
|
||||
*/
|
||||
export function listPlaybooks(): string[] {
|
||||
return Object.keys(getAllPlaywrightPlaybooks());
|
||||
}
|
||||
Reference in New Issue
Block a user