imp part 5
This commit is contained in:
@@ -26,17 +26,7 @@ class ContentValidationService:
|
|||||||
"""
|
"""
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
# Stage 3: Enforce "no cluster, no task" rule when feature flag enabled
|
# Validate entity_type is set
|
||||||
from django.conf import settings
|
|
||||||
if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False):
|
|
||||||
if not task.cluster:
|
|
||||||
errors.append({
|
|
||||||
'field': 'cluster',
|
|
||||||
'code': 'missing_cluster',
|
|
||||||
'message': 'Task must be associated with a cluster before content generation',
|
|
||||||
})
|
|
||||||
|
|
||||||
# Stage 3: Validate entity_type is set
|
|
||||||
if not task.content_type:
|
if not task.content_type:
|
||||||
errors.append({
|
errors.append({
|
||||||
'field': 'content_type',
|
'field': 'content_type',
|
||||||
|
|||||||
@@ -52,26 +52,12 @@ class ClusteringService:
|
|||||||
|
|
||||||
# Delegate to AI task
|
# Delegate to AI task
|
||||||
from igny8_core.ai.tasks import run_ai_task
|
from igny8_core.ai.tasks import run_ai_task
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'ids': keyword_ids,
|
'ids': keyword_ids,
|
||||||
'sector_id': sector_id
|
'sector_id': sector_id
|
||||||
}
|
}
|
||||||
|
|
||||||
# Stage 1: When USE_SITE_BUILDER_REFACTOR is enabled, payload can include
|
|
||||||
# taxonomy hints and dimension metadata for enhanced clustering.
|
|
||||||
# TODO (Stage 2/3): Enhance clustering to collect and use:
|
|
||||||
# - Taxonomy hints from SiteBlueprintTaxonomy
|
|
||||||
# - Dimension metadata (context_type, dimension_meta) for clusters
|
|
||||||
# - Attribute values from Keywords.attribute_values
|
|
||||||
if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False):
|
|
||||||
logger.info(
|
|
||||||
f"Clustering with refactor enabled: {len(keyword_ids)} keywords, "
|
|
||||||
f"sector_id={sector_id}, account_id={account.id}"
|
|
||||||
)
|
|
||||||
# Future: Add taxonomy hints and dimension metadata to payload
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if hasattr(run_ai_task, 'delay'):
|
if hasattr(run_ai_task, 'delay'):
|
||||||
# Celery available - queue async
|
# Celery available - queue async
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from .models import Keywords, Clusters, ContentIdeas
|
from .models import Keywords, Clusters, ContentIdeas
|
||||||
from igny8_core.auth.models import SeedKeyword
|
from igny8_core.auth.models import SeedKeyword
|
||||||
@@ -69,12 +68,6 @@ class KeywordSerializer(serializers.ModelSerializer):
|
|||||||
]
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'account_id', 'keyword', 'volume', 'difficulty', 'country']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'account_id', 'keyword', 'volume', 'difficulty', 'country']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
# Only include Stage 1 fields when feature flag is enabled
|
|
||||||
if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False):
|
|
||||||
self.fields['attribute_values'] = serializers.JSONField(read_only=True)
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Validate that either seed_keyword_id OR custom keyword fields are provided"""
|
"""Validate that either seed_keyword_id OR custom keyword fields are provided"""
|
||||||
# For create operations, need either seed_keyword_id OR custom keyword
|
# For create operations, need either seed_keyword_id OR custom keyword
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
|
|||||||
# Set IGNY8_USE_UNIFIED_EXCEPTION_HANDLER=True to enable unified exception handler
|
# Set IGNY8_USE_UNIFIED_EXCEPTION_HANDLER=True to enable unified exception handler
|
||||||
# Set IGNY8_DEBUG_THROTTLE=True to bypass rate limiting in development
|
# Set IGNY8_DEBUG_THROTTLE=True to bypass rate limiting in development
|
||||||
IGNY8_DEBUG_THROTTLE = os.getenv('IGNY8_DEBUG_THROTTLE', str(DEBUG)).lower() == 'true'
|
IGNY8_DEBUG_THROTTLE = os.getenv('IGNY8_DEBUG_THROTTLE', str(DEBUG)).lower() == 'true'
|
||||||
USE_SITE_BUILDER_REFACTOR = os.getenv('USE_SITE_BUILDER_REFACTOR', 'false').lower() == 'true'
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = [
|
||||||
'*', # Allow all hosts for flexibility
|
'*', # Allow all hosts for flexibility
|
||||||
|
|||||||
152
docs/fixes/phase2-module-activation.md
Normal file
152
docs/fixes/phase2-module-activation.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Phase 2 Module Activation Guide
|
||||||
|
|
||||||
|
> Reference document for activating disabled modules (Linker, Optimizer, SiteBuilder)
|
||||||
|
|
||||||
|
## Current Status (as of December 2025)
|
||||||
|
|
||||||
|
| Module | Status | Backend Flag | Migration |
|
||||||
|
|--------|--------|--------------|-----------|
|
||||||
|
| **SiteBuilder** | ❌ DEPRECATED | `site_builder_enabled` | Disabled via 0011 |
|
||||||
|
| **Linker** | ⏸️ Phase 2 | `linker_enabled` | Disabled via 0011 |
|
||||||
|
| **Optimizer** | ⏸️ Phase 2 | `optimizer_enabled` | Disabled via 0011 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How Module Disabling Works
|
||||||
|
|
||||||
|
### 1. Database Flag (GlobalIntegrationSettings)
|
||||||
|
```python
|
||||||
|
# backend/igny8_core/modules/system/global_settings_models.py
|
||||||
|
site_builder_enabled = models.BooleanField(default=False)
|
||||||
|
linker_enabled = models.BooleanField(default=False)
|
||||||
|
optimizer_enabled = models.BooleanField(default=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Migration Set Defaults
|
||||||
|
```python
|
||||||
|
# backend/igny8_core/modules/system/migrations/0011_disable_phase2_modules.py
|
||||||
|
# Sets all three modules to disabled for existing records
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. API Returns Settings
|
||||||
|
```python
|
||||||
|
# backend/igny8_core/modules/system/settings_views.py
|
||||||
|
# GET /api/module-settings/ returns enabled/disabled status
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Frontend Checks Settings
|
||||||
|
```typescript
|
||||||
|
// frontend/src/store/moduleStore.ts
|
||||||
|
// useModuleStore.isModuleEnabled('linker') → checks API response
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Sidebar Hides Menu Items
|
||||||
|
```tsx
|
||||||
|
// frontend/src/layout/AppSidebar.tsx
|
||||||
|
if (isModuleEnabled('linker')) {
|
||||||
|
// Add menu item
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Activation Steps for Phase 2
|
||||||
|
|
||||||
|
### Option A: Via Django Admin (Recommended)
|
||||||
|
|
||||||
|
1. Log into Django Admin (`/admin/`)
|
||||||
|
2. Navigate to **System → Global Integration Settings**
|
||||||
|
3. Edit the singleton record
|
||||||
|
4. Set `linker_enabled` or `optimizer_enabled` to `True`
|
||||||
|
5. Save
|
||||||
|
|
||||||
|
### Option B: Via Database
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE system_globalintegrationsettings
|
||||||
|
SET linker_enabled = TRUE
|
||||||
|
WHERE id = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option C: Via Management Command (TBD)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py enable_module linker
|
||||||
|
python manage.py enable_module optimizer
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-Activation Checklist
|
||||||
|
|
||||||
|
Before enabling a Phase 2 module:
|
||||||
|
|
||||||
|
### Linker Module
|
||||||
|
- [ ] Verify `modules/linker/views.py` ViewSet is functional
|
||||||
|
- [ ] Verify `pages/Linker/` frontend pages exist
|
||||||
|
- [ ] Test API endpoints manually
|
||||||
|
- [ ] Add route protection for `/linker/*` paths
|
||||||
|
- [ ] Update documentation status
|
||||||
|
|
||||||
|
### Optimizer Module
|
||||||
|
- [ ] Verify `modules/optimizer/views.py` ViewSet is functional
|
||||||
|
- [ ] Verify `business/optimization/` services work
|
||||||
|
- [ ] Verify `ai/functions/optimize.py` AI function
|
||||||
|
- [ ] Verify `pages/Optimizer/` frontend pages exist
|
||||||
|
- [ ] Test API endpoints manually
|
||||||
|
- [ ] Add route protection for `/optimizer/*` paths
|
||||||
|
- [ ] Update documentation status
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Route Protection (TODO for Phase 2)
|
||||||
|
|
||||||
|
Currently, direct URL access (e.g., `/linker`) still works even when module is disabled.
|
||||||
|
|
||||||
|
### Recommended Implementation:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// frontend/src/components/common/ModuleGuard.tsx
|
||||||
|
export function ModuleGuard({ module, children }: { module: string; children: React.ReactNode }) {
|
||||||
|
const { isModuleEnabled } = useModuleStore();
|
||||||
|
|
||||||
|
if (!isModuleEnabled(module)) {
|
||||||
|
return <Navigate to="/" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In routes:
|
||||||
|
<Route path="/linker/*" element={
|
||||||
|
<ModuleGuard module="linker">
|
||||||
|
<LinkerPage />
|
||||||
|
</ModuleGuard>
|
||||||
|
} />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SiteBuilder (DEPRECATED)
|
||||||
|
|
||||||
|
**Do NOT activate SiteBuilder.** This module is deprecated and code is being removed.
|
||||||
|
|
||||||
|
### Removed Items (Task 5.1)
|
||||||
|
- ✅ `frontend/src/__tests__/sites/` - Test directory deleted
|
||||||
|
- ✅ `USE_SITE_BUILDER_REFACTOR` - Feature flag removed from settings.py
|
||||||
|
- ✅ Feature flag checks - Removed from clustering_service.py, validation_service.py, serializers.py
|
||||||
|
- ✅ Tasks.tsx - Removed SiteBuilder filter logic
|
||||||
|
|
||||||
|
### Remaining References (Documentation Only)
|
||||||
|
- Migration comments (safe to keep)
|
||||||
|
- CHANGELOG.md entries (historical)
|
||||||
|
- IGNY8-APP.md (documents deprecated status)
|
||||||
|
- Database field (kept for backward compatibility)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [LINKER.md](../10-MODULES/LINKER.md) - Full Linker module docs
|
||||||
|
- [OPTIMIZER.md](../10-MODULES/OPTIMIZER.md) - Full Optimizer module docs
|
||||||
|
- [SYSTEM-SETTINGS.md](../10-MODULES/SYSTEM-SETTINGS.md) - Settings model reference
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests for Bulk Generation UI
|
|
||||||
* Phase 5: Sites Renderer & Bulk Generation
|
|
||||||
*/
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
||||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
||||||
import { builderApi } from '../../../services/siteBuilder.api';
|
|
||||||
|
|
||||||
// Mock API
|
|
||||||
vi.mock('../../../services/siteBuilder.api');
|
|
||||||
|
|
||||||
describe('Bulk Generation', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('generates all pages when button clicked', async () => {
|
|
||||||
// Test: Bulk page generation works
|
|
||||||
const mockGenerate = vi.fn().mockResolvedValue({
|
|
||||||
success: true,
|
|
||||||
pages_queued: 5,
|
|
||||||
task_ids: [1, 2, 3, 4, 5],
|
|
||||||
});
|
|
||||||
|
|
||||||
(builderApi as any).generateAllPages = mockGenerate;
|
|
||||||
|
|
||||||
// This would be tested in the actual component
|
|
||||||
expect(mockGenerate).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('tracks progress during generation', async () => {
|
|
||||||
// Test: Progress tracking works
|
|
||||||
const mockProgress = {
|
|
||||||
pages_queued: 5,
|
|
||||||
task_ids: [1, 2, 3, 4, 5],
|
|
||||||
celery_task_id: 'task-123',
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(mockProgress.pages_queued).toBe(5);
|
|
||||||
expect(mockProgress.task_ids).toHaveLength(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates tasks for selected pages only', async () => {
|
|
||||||
// Test: Selected pages can be generated
|
|
||||||
const selectedPageIds = [1, 3, 5];
|
|
||||||
const mockGenerate = vi.fn().mockResolvedValue({
|
|
||||||
success: true,
|
|
||||||
pages_queued: 3,
|
|
||||||
task_ids: selectedPageIds,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectedPageIds).toHaveLength(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('force regenerates when option selected', async () => {
|
|
||||||
// Test: Force regenerate works
|
|
||||||
const mockGenerate = vi.fn().mockResolvedValue({
|
|
||||||
success: true,
|
|
||||||
pages_queued: 5,
|
|
||||||
task_ids: [1, 2, 3, 4, 5],
|
|
||||||
});
|
|
||||||
|
|
||||||
await mockGenerate(1, { force: true });
|
|
||||||
expect(mockGenerate).toHaveBeenCalledWith(1, { force: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests for Component Library
|
|
||||||
* Phase 7: UI Components & Prompt Management
|
|
||||||
*/
|
|
||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import { HeroBlock } from '../../components/shared/blocks/HeroBlock';
|
|
||||||
import { FeatureGridBlock } from '../../components/shared/blocks/FeatureGridBlock';
|
|
||||||
import { StatsPanel } from '../../components/shared/blocks/StatsPanel';
|
|
||||||
|
|
||||||
describe('Component Library', () => {
|
|
||||||
it('all components render correctly', () => {
|
|
||||||
// Test: All components render correctly
|
|
||||||
const { container: hero } = render(
|
|
||||||
<HeroBlock title="Test Hero" />
|
|
||||||
);
|
|
||||||
expect(hero).toBeDefined();
|
|
||||||
|
|
||||||
const { container: features } = render(
|
|
||||||
<FeatureGridBlock features={[]} />
|
|
||||||
);
|
|
||||||
expect(features).toBeDefined();
|
|
||||||
|
|
||||||
const { container: stats } = render(
|
|
||||||
<StatsPanel stats={[]} />
|
|
||||||
);
|
|
||||||
expect(stats).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('component library is complete', () => {
|
|
||||||
// Test: Component library is complete
|
|
||||||
const components = [
|
|
||||||
'HeroBlock',
|
|
||||||
'FeatureGridBlock',
|
|
||||||
'StatsPanel',
|
|
||||||
'ServicesBlock',
|
|
||||||
'ProductsBlock',
|
|
||||||
'TestimonialsBlock',
|
|
||||||
'ContactFormBlock',
|
|
||||||
'CTABlock',
|
|
||||||
'ImageGalleryBlock',
|
|
||||||
'VideoBlock',
|
|
||||||
'TextBlock',
|
|
||||||
'QuoteBlock',
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(components.length).toBeGreaterThanOrEqual(12);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('no duplicate components exist', () => {
|
|
||||||
// Test: No duplicate components
|
|
||||||
const components = [
|
|
||||||
'HeroBlock',
|
|
||||||
'FeatureGridBlock',
|
|
||||||
'StatsPanel',
|
|
||||||
'HeroBlock', // Duplicate
|
|
||||||
];
|
|
||||||
|
|
||||||
const unique = [...new Set(components)];
|
|
||||||
expect(unique.length).toBeLessThan(components.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('components are accessible', () => {
|
|
||||||
// Test: All components are accessible
|
|
||||||
const { container } = render(
|
|
||||||
<HeroBlock title="Test" />
|
|
||||||
);
|
|
||||||
|
|
||||||
const heading = container.querySelector('h2');
|
|
||||||
expect(heading).toBeDefined();
|
|
||||||
expect(heading?.textContent).toBe('Test');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('components are responsive', () => {
|
|
||||||
// Test: All components are responsive
|
|
||||||
// Responsiveness is tested via CSS, not JS
|
|
||||||
// This test verifies responsive classes exist
|
|
||||||
const responsiveClasses = ['sm:', 'md:', 'lg:', 'xl:'];
|
|
||||||
expect(responsiveClasses.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests for Site Integration UI
|
|
||||||
* Phase 6: Site Integration & Multi-Destination Publishing
|
|
||||||
*/
|
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
|
||||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
||||||
import PlatformSelector from '../../components/integration/PlatformSelector';
|
|
||||||
import IntegrationStatus from '../../components/integration/IntegrationStatus';
|
|
||||||
|
|
||||||
describe('Site Integration', () => {
|
|
||||||
it('platform selector allows selecting platform', () => {
|
|
||||||
// Test: Site integrations work correctly
|
|
||||||
const handleChange = vi.fn();
|
|
||||||
const { container } = render(
|
|
||||||
<PlatformSelector value="wordpress" onChange={handleChange} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const select = container.querySelector('select');
|
|
||||||
expect(select).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('integration status displays sync status', () => {
|
|
||||||
// Test: Site integrations work correctly
|
|
||||||
const { container } = render(
|
|
||||||
<IntegrationStatus
|
|
||||||
syncEnabled={true}
|
|
||||||
syncStatus="success"
|
|
||||||
lastSyncAt="2025-01-18T10:00:00Z"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('publishing settings save correctly', async () => {
|
|
||||||
// Test: Publishing settings work
|
|
||||||
const mockSave = vi.fn().mockResolvedValue({ success: true });
|
|
||||||
|
|
||||||
await mockSave({
|
|
||||||
defaultDestinations: ['sites', 'wordpress'],
|
|
||||||
autoPublishEnabled: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mockSave).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('multi-destination publishing works', async () => {
|
|
||||||
// Test: Multi-destination publishing works
|
|
||||||
const destinations = ['sites', 'wordpress', 'shopify'];
|
|
||||||
const mockPublish = vi.fn().mockResolvedValue({
|
|
||||||
success: true,
|
|
||||||
results: destinations.map(d => ({ destination: d, success: true })),
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await mockPublish(1, destinations);
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(result.results).toHaveLength(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests for Layout System
|
|
||||||
* Phase 7: UI Components & Prompt Management
|
|
||||||
*/
|
|
||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import LayoutSelector from '../../components/sites/LayoutSelector';
|
|
||||||
import LayoutPreview from '../../components/sites/LayoutPreview';
|
|
||||||
|
|
||||||
describe('Layout System', () => {
|
|
||||||
it('layout selector displays all layouts', () => {
|
|
||||||
// Test: Layout system works
|
|
||||||
const layouts = [
|
|
||||||
{ id: 'default', name: 'Default', category: 'marketing' },
|
|
||||||
{ id: 'minimal', name: 'Minimal', category: 'marketing' },
|
|
||||||
{ id: 'magazine', name: 'Magazine', category: 'blog' },
|
|
||||||
{ id: 'ecommerce', name: 'Ecommerce', category: 'ecommerce' },
|
|
||||||
{ id: 'portfolio', name: 'Portfolio', category: 'portfolio' },
|
|
||||||
{ id: 'blog', name: 'Blog', category: 'blog' },
|
|
||||||
{ id: 'corporate', name: 'Corporate', category: 'corporate' },
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(layouts).toHaveLength(7);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('layout preview renders correctly', () => {
|
|
||||||
// Test: Layout system works
|
|
||||||
const { container } = render(
|
|
||||||
<LayoutPreview
|
|
||||||
layoutId="default"
|
|
||||||
layoutName="Default Layout"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('template system works', () => {
|
|
||||||
// Test: Template system works
|
|
||||||
const templates = [
|
|
||||||
{ id: 'marketing', name: 'Marketing Template' },
|
|
||||||
{ id: 'landing', name: 'Landing Template' },
|
|
||||||
{ id: 'blog', name: 'Blog Template' },
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(templates).toHaveLength(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cms styling system works', () => {
|
|
||||||
// Test: CMS styling system works
|
|
||||||
const stylePresets = ['modern', 'classic', 'minimal', 'bold', 'elegant', 'tech'];
|
|
||||||
const colorSchemes = ['blue', 'purple', 'green', 'dark'];
|
|
||||||
const typographyPresets = ['modern', 'classic', 'editorial', 'minimal', 'tech'];
|
|
||||||
|
|
||||||
expect(stylePresets.length).toBeGreaterThan(0);
|
|
||||||
expect(colorSchemes.length).toBeGreaterThan(0);
|
|
||||||
expect(typographyPresets.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests for Prompt Management UI
|
|
||||||
* Phase 7: UI Components & Prompt Management
|
|
||||||
*/
|
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
|
||||||
|
|
||||||
describe('Prompt Management', () => {
|
|
||||||
it('prompt management UI loads prompts', async () => {
|
|
||||||
// Test: Prompt management UI works
|
|
||||||
const mockPrompts = [
|
|
||||||
{ prompt_type: 'site_structure_generation', prompt_value: 'Test prompt' },
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(mockPrompts).toHaveLength(1);
|
|
||||||
expect(mockPrompts[0].prompt_type).toBe('site_structure_generation');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('site builder section displays in prompts page', () => {
|
|
||||||
// Test: Prompt management UI works
|
|
||||||
const promptTypes = [
|
|
||||||
'clustering',
|
|
||||||
'ideas',
|
|
||||||
'content_generation',
|
|
||||||
'site_structure_generation',
|
|
||||||
];
|
|
||||||
|
|
||||||
const siteBuilderPrompts = promptTypes.filter(t => t === 'site_structure_generation');
|
|
||||||
expect(siteBuilderPrompts).toHaveLength(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('prompt editor saves site structure generation prompt', async () => {
|
|
||||||
// Test: Prompt management UI works
|
|
||||||
const mockSave = vi.fn().mockResolvedValue({ success: true });
|
|
||||||
|
|
||||||
await mockSave({
|
|
||||||
prompt_type: 'site_structure_generation',
|
|
||||||
prompt_value: 'Updated prompt',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mockSave).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests for Site Management UI
|
|
||||||
* Phase 6: Site Integration & Multi-Destination Publishing
|
|
||||||
*/
|
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
|
|
||||||
describe('Site Management', () => {
|
|
||||||
it('site management dashboard displays sites', () => {
|
|
||||||
// Test: Site management UI works (core features)
|
|
||||||
const mockSites = [
|
|
||||||
{ id: 1, name: 'Site 1', status: 'active' },
|
|
||||||
{ id: 2, name: 'Site 2', status: 'active' },
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(mockSites).toHaveLength(2);
|
|
||||||
expect(mockSites[0].name).toBe('Site 1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('site content editor loads pages', async () => {
|
|
||||||
// Test: Site management UI works (core features)
|
|
||||||
const mockPages = [
|
|
||||||
{ id: 1, title: 'Home', slug: 'home' },
|
|
||||||
{ id: 2, title: 'About', slug: 'about' },
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(mockPages).toHaveLength(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('page manager allows reordering', () => {
|
|
||||||
// Test: Site management UI works (core features)
|
|
||||||
const pages = [
|
|
||||||
{ id: 1, order: 1 },
|
|
||||||
{ id: 2, order: 2 },
|
|
||||||
{ id: 3, order: 3 },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Simulate drag-drop reorder
|
|
||||||
const reordered = [pages[2], pages[0], pages[1]];
|
|
||||||
expect(reordered[0].id).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('task integration shows site builder tasks', () => {
|
|
||||||
// Test: Task integration works
|
|
||||||
const mockTasks = [
|
|
||||||
{ id: 1, title: '[Site Builder] Home Page', status: 'queued' },
|
|
||||||
{ id: 2, title: '[Site Builder] About Page', status: 'queued' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const siteBuilderTasks = mockTasks.filter(t => t.title.startsWith('[Site Builder]'));
|
|
||||||
expect(siteBuilderTasks).toHaveLength(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests for Advanced Site Management
|
|
||||||
* Phase 7: UI Components & Prompt Management
|
|
||||||
*/
|
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
|
||||||
|
|
||||||
describe('Advanced Site Management', () => {
|
|
||||||
it('site management works end-to-end', async () => {
|
|
||||||
// Test: Site management works end-to-end (advanced features)
|
|
||||||
const workflow = [
|
|
||||||
'load_sites',
|
|
||||||
'select_site',
|
|
||||||
'view_pages',
|
|
||||||
'edit_content',
|
|
||||||
'save_changes',
|
|
||||||
'preview_site',
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(workflow).toHaveLength(6);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('site preview loads deployment URL', async () => {
|
|
||||||
// Test: Site management works end-to-end (advanced features)
|
|
||||||
const mockDeployment = {
|
|
||||||
id: 1,
|
|
||||||
deployment_url: 'https://test-site.igny8.com',
|
|
||||||
status: 'deployed',
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(mockDeployment.deployment_url).toBeDefined();
|
|
||||||
expect(mockDeployment.status).toBe('deployed');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('site settings saves SEO metadata', async () => {
|
|
||||||
// Test: Site management works end-to-end (advanced features)
|
|
||||||
const mockSave = vi.fn().mockResolvedValue({ success: true });
|
|
||||||
|
|
||||||
await mockSave({
|
|
||||||
seo_metadata: {
|
|
||||||
meta_title: 'Test Site',
|
|
||||||
meta_description: 'Test Description',
|
|
||||||
og_title: 'Test OG Title',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mockSave).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -108,19 +108,13 @@ export const createTasksPageConfig = (
|
|||||||
toggleContentKey: 'description',
|
toggleContentKey: 'description',
|
||||||
toggleContentLabel: 'Idea & Content Outline',
|
toggleContentLabel: 'Idea & Content Outline',
|
||||||
render: (value: string, row: Task) => {
|
render: (value: string, row: Task) => {
|
||||||
const isSiteBuilder = value?.startsWith('[Site Builder]');
|
const displayTitle = value || 'Untitled';
|
||||||
const displayTitle = isSiteBuilder && value ? value.replace('[Site Builder] ', '') : (value || 'Untitled');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-base font-light text-gray-900 dark:text-white">
|
<span className="text-base font-light text-gray-900 dark:text-white">
|
||||||
{displayTitle}
|
{displayTitle}
|
||||||
</span>
|
</span>
|
||||||
{isSiteBuilder && (
|
|
||||||
<Badge color="purple" size="xs" variant="soft">
|
|
||||||
<span className="text-[11px] font-normal">Site Builder</span>
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -160,15 +160,8 @@ export default function Tasks() {
|
|||||||
try {
|
try {
|
||||||
const ordering = sortBy ? `${sortDirection === 'desc' ? '-' : ''}${sortBy}` : '-created_at';
|
const ordering = sortBy ? `${sortDirection === 'desc' ? '-' : ''}${sortBy}` : '-created_at';
|
||||||
|
|
||||||
// Build search term - combine user search with Site Builder filter if needed
|
|
||||||
let finalSearchTerm = searchTerm;
|
|
||||||
if (sourceFilter === 'site_builder') {
|
|
||||||
// If user has a search term, combine it with Site Builder prefix
|
|
||||||
finalSearchTerm = searchTerm ? `[Site Builder] ${searchTerm}` : '[Site Builder]';
|
|
||||||
}
|
|
||||||
|
|
||||||
const filters: TasksFilters = {
|
const filters: TasksFilters = {
|
||||||
...(finalSearchTerm && { search: finalSearchTerm }),
|
...(searchTerm && { search: searchTerm }),
|
||||||
...(statusFilter && { status: statusFilter }),
|
...(statusFilter && { status: statusFilter }),
|
||||||
...(clusterFilter && { cluster_id: clusterFilter }),
|
...(clusterFilter && { cluster_id: clusterFilter }),
|
||||||
...(structureFilter && { content_structure: structureFilter }),
|
...(structureFilter && { content_structure: structureFilter }),
|
||||||
|
|||||||
Reference in New Issue
Block a user