Remove obsolete scripts and files, update site builder configurations
- Deleted the `import_plans.py`, `run_tests.py`, and `test_run.py` scripts as they are no longer needed. - Updated the initial migration dependency in `0001_initial.py` to reflect recent changes in the `igny8_core_auth` app. - Enhanced the implementation plan documentation to include new phases and updates on the site builder project. - Updated the `vite.config.ts` and `package.json` to integrate testing configurations and dependencies for the site builder.
This commit is contained in:
2133
site-builder/package-lock.json
generated
2133
site-builder/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,9 @@
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.2",
|
||||
@@ -19,6 +21,8 @@
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/node": "^24.10.0",
|
||||
"@types/react": "^19.2.2",
|
||||
@@ -31,6 +35,8 @@
|
||||
"globals": "^16.5.0",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.46.3",
|
||||
"vite": "^7.2.2"
|
||||
"vite": "^7.2.2",
|
||||
"vitest": "^2.1.5",
|
||||
"jsdom": "^25.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
106
site-builder/src/pages/preview/__tests__/PreviewCanvas.test.tsx
Normal file
106
site-builder/src/pages/preview/__tests__/PreviewCanvas.test.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { PreviewCanvas } from '../PreviewCanvas';
|
||||
import { useSiteDefinitionStore } from '../../../state/siteDefinitionStore';
|
||||
|
||||
vi.mock('../../../state/siteDefinitionStore');
|
||||
|
||||
describe('PreviewCanvas', () => {
|
||||
const mockSelectPage = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('shows placeholder when no structure or pages', () => {
|
||||
(useSiteDefinitionStore as any).mockReturnValue({
|
||||
structure: undefined,
|
||||
pages: [],
|
||||
selectedSlug: undefined,
|
||||
selectPage: mockSelectPage,
|
||||
});
|
||||
|
||||
render(<PreviewCanvas />);
|
||||
|
||||
expect(screen.getByText(/generate a blueprint to see live previews/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders pages from structure', () => {
|
||||
const mockStructure = {
|
||||
site: { name: 'Test Site', primary_navigation: ['home', 'about'] },
|
||||
pages: [
|
||||
{ slug: 'home', title: 'Home', type: 'home', blocks: [] },
|
||||
{ slug: 'about', title: 'About', type: 'about', blocks: [] },
|
||||
],
|
||||
};
|
||||
|
||||
(useSiteDefinitionStore as any).mockReturnValue({
|
||||
structure: mockStructure,
|
||||
pages: [],
|
||||
selectedSlug: 'home',
|
||||
selectPage: mockSelectPage,
|
||||
});
|
||||
|
||||
render(<PreviewCanvas />);
|
||||
|
||||
expect(screen.getByText('Home')).toBeInTheDocument();
|
||||
// Check for navigation button specifically (there are multiple "home" elements)
|
||||
const navButtons = screen.getAllByText('home');
|
||||
expect(navButtons.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('renders pages from pages array when structure not available', () => {
|
||||
const mockPages = [
|
||||
{
|
||||
id: 1,
|
||||
site_blueprint: 1,
|
||||
slug: 'services',
|
||||
title: 'Services',
|
||||
type: 'services',
|
||||
status: 'ready',
|
||||
order: 0,
|
||||
blocks_json: [],
|
||||
},
|
||||
];
|
||||
|
||||
(useSiteDefinitionStore as any).mockReturnValue({
|
||||
structure: undefined,
|
||||
pages: mockPages,
|
||||
selectedSlug: 'services',
|
||||
selectPage: mockSelectPage,
|
||||
});
|
||||
|
||||
render(<PreviewCanvas />);
|
||||
|
||||
expect(screen.getByText('Services')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders page blocks when available', () => {
|
||||
const mockStructure = {
|
||||
site: { name: 'Test Site' },
|
||||
pages: [
|
||||
{
|
||||
slug: 'home',
|
||||
title: 'Home',
|
||||
type: 'home',
|
||||
blocks: [
|
||||
{ type: 'hero', heading: 'Welcome', subheading: 'Get started today' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
(useSiteDefinitionStore as any).mockReturnValue({
|
||||
structure: mockStructure,
|
||||
pages: [],
|
||||
selectedSlug: 'home',
|
||||
selectPage: mockSelectPage,
|
||||
});
|
||||
|
||||
render(<PreviewCanvas />);
|
||||
|
||||
expect(screen.getByText('Welcome')).toBeInTheDocument();
|
||||
expect(screen.getByText('Get started today')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
172
site-builder/src/pages/wizard/__tests__/WizardPage.test.tsx
Normal file
172
site-builder/src/pages/wizard/__tests__/WizardPage.test.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { WizardPage } from '../WizardPage';
|
||||
import { useBuilderStore } from '../../../state/builderStore';
|
||||
import { useSiteDefinitionStore } from '../../../state/siteDefinitionStore';
|
||||
|
||||
// Mock stores
|
||||
vi.mock('../../../state/builderStore');
|
||||
vi.mock('../../../state/siteDefinitionStore');
|
||||
|
||||
describe('WizardPage', () => {
|
||||
const mockSetField = vi.fn();
|
||||
const mockNextStep = vi.fn();
|
||||
const mockPreviousStep = vi.fn();
|
||||
const mockSetStep = vi.fn();
|
||||
const mockUpdateStyle = vi.fn();
|
||||
const mockAddObjective = vi.fn();
|
||||
const mockRemoveObjective = vi.fn();
|
||||
const mockSubmitWizard = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(useBuilderStore as any).mockReturnValue({
|
||||
form: {
|
||||
siteId: null,
|
||||
sectorId: null,
|
||||
siteName: '',
|
||||
businessType: '',
|
||||
industry: '',
|
||||
targetAudience: '',
|
||||
hostingType: 'igny8_sites',
|
||||
businessBrief: '',
|
||||
objectives: [],
|
||||
style: {},
|
||||
},
|
||||
currentStep: 0,
|
||||
isSubmitting: false,
|
||||
error: undefined,
|
||||
activeBlueprint: undefined,
|
||||
setField: mockSetField,
|
||||
nextStep: mockNextStep,
|
||||
previousStep: mockPreviousStep,
|
||||
setStep: mockSetStep,
|
||||
updateStyle: mockUpdateStyle,
|
||||
addObjective: mockAddObjective,
|
||||
removeObjective: mockRemoveObjective,
|
||||
submitWizard: mockSubmitWizard,
|
||||
refreshPages: vi.fn(),
|
||||
});
|
||||
(useSiteDefinitionStore as any).mockReturnValue({
|
||||
structure: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders wizard with step indicators', () => {
|
||||
render(<WizardPage />);
|
||||
|
||||
expect(screen.getByText('Site builder wizard')).toBeInTheDocument();
|
||||
expect(screen.getByText('Business')).toBeInTheDocument();
|
||||
expect(screen.getByText('Brief')).toBeInTheDocument();
|
||||
expect(screen.getByText('Objectives')).toBeInTheDocument();
|
||||
expect(screen.getByText('Style')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('allows navigation between steps', () => {
|
||||
render(<WizardPage />);
|
||||
|
||||
const briefButton = screen.getByText('Brief');
|
||||
fireEvent.click(briefButton);
|
||||
|
||||
expect(mockSetStep).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('disables back button on first step', () => {
|
||||
render(<WizardPage />);
|
||||
|
||||
const backButton = screen.getByText('Back');
|
||||
expect(backButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('enables back button after first step', () => {
|
||||
(useBuilderStore as any).mockReturnValue({
|
||||
form: {},
|
||||
currentStep: 1,
|
||||
isSubmitting: false,
|
||||
error: undefined,
|
||||
activeBlueprint: undefined,
|
||||
setField: mockSetField,
|
||||
nextStep: mockNextStep,
|
||||
previousStep: mockPreviousStep,
|
||||
setStep: mockSetStep,
|
||||
updateStyle: mockUpdateStyle,
|
||||
addObjective: mockAddObjective,
|
||||
removeObjective: mockRemoveObjective,
|
||||
submitWizard: mockSubmitWizard,
|
||||
refreshPages: vi.fn(),
|
||||
});
|
||||
|
||||
render(<WizardPage />);
|
||||
|
||||
const backButton = screen.getByText('Back');
|
||||
expect(backButton).not.toBeDisabled();
|
||||
|
||||
fireEvent.click(backButton);
|
||||
expect(mockPreviousStep).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows error message when present', () => {
|
||||
(useBuilderStore as any).mockReturnValue({
|
||||
form: {},
|
||||
currentStep: 0,
|
||||
isSubmitting: false,
|
||||
error: 'Test error message',
|
||||
activeBlueprint: undefined,
|
||||
setField: mockSetField,
|
||||
nextStep: mockNextStep,
|
||||
previousStep: mockPreviousStep,
|
||||
setStep: mockSetStep,
|
||||
updateStyle: mockUpdateStyle,
|
||||
addObjective: mockAddObjective,
|
||||
removeObjective: mockRemoveObjective,
|
||||
submitWizard: mockSubmitWizard,
|
||||
refreshPages: vi.fn(),
|
||||
});
|
||||
|
||||
render(<WizardPage />);
|
||||
|
||||
expect(screen.getByText('Test error message')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows loading state when submitting', () => {
|
||||
(useBuilderStore as any).mockReturnValue({
|
||||
form: {
|
||||
siteId: null,
|
||||
sectorId: null,
|
||||
siteName: '',
|
||||
businessType: '',
|
||||
industry: '',
|
||||
targetAudience: '',
|
||||
hostingType: 'igny8_sites',
|
||||
businessBrief: '',
|
||||
objectives: [],
|
||||
style: {
|
||||
palette: 'Vibrant modern palette',
|
||||
typography: 'Sans-serif',
|
||||
personality: 'Confident',
|
||||
heroImagery: 'Real people',
|
||||
},
|
||||
},
|
||||
currentStep: 3,
|
||||
isSubmitting: true,
|
||||
error: undefined,
|
||||
activeBlueprint: undefined,
|
||||
setField: mockSetField,
|
||||
nextStep: mockNextStep,
|
||||
previousStep: mockPreviousStep,
|
||||
setStep: mockSetStep,
|
||||
updateStyle: mockUpdateStyle,
|
||||
addObjective: mockAddObjective,
|
||||
removeObjective: mockRemoveObjective,
|
||||
submitWizard: mockSubmitWizard,
|
||||
refreshPages: vi.fn(),
|
||||
});
|
||||
|
||||
render(<WizardPage />);
|
||||
|
||||
// When submitting, button text changes to "Generating…"
|
||||
const submitButton = screen.getByText(/generating/i);
|
||||
expect(submitButton).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
9
site-builder/src/setupTests.ts
Normal file
9
site-builder/src/setupTests.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { expect, afterEach } from 'vitest';
|
||||
import { cleanup } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
|
||||
// Cleanup after each test
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
92
site-builder/src/state/__tests__/builderStore.test.ts
Normal file
92
site-builder/src/state/__tests__/builderStore.test.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useBuilderStore } from '../builderStore';
|
||||
import type { BuilderFormData } from '../../types/siteBuilder';
|
||||
|
||||
describe('builderStore', () => {
|
||||
beforeEach(() => {
|
||||
useBuilderStore.getState().reset();
|
||||
});
|
||||
|
||||
it('initializes with default form values', () => {
|
||||
const state = useBuilderStore.getState();
|
||||
expect(state.form.siteName).toBe('');
|
||||
expect(state.form.hostingType).toBe('igny8_sites');
|
||||
expect(state.form.objectives).toEqual(['Launch a conversion-focused marketing site']);
|
||||
expect(state.currentStep).toBe(0);
|
||||
});
|
||||
|
||||
it('updates form fields', () => {
|
||||
const { setField } = useBuilderStore.getState();
|
||||
setField('siteName', 'Test Site');
|
||||
setField('businessType', 'SaaS');
|
||||
|
||||
const state = useBuilderStore.getState();
|
||||
expect(state.form.siteName).toBe('Test Site');
|
||||
expect(state.form.businessType).toBe('SaaS');
|
||||
});
|
||||
|
||||
it('navigates steps correctly', () => {
|
||||
const { nextStep, previousStep, setStep } = useBuilderStore.getState();
|
||||
|
||||
expect(useBuilderStore.getState().currentStep).toBe(0);
|
||||
|
||||
nextStep();
|
||||
expect(useBuilderStore.getState().currentStep).toBe(1);
|
||||
|
||||
nextStep();
|
||||
expect(useBuilderStore.getState().currentStep).toBe(2);
|
||||
|
||||
previousStep();
|
||||
expect(useBuilderStore.getState().currentStep).toBe(1);
|
||||
|
||||
setStep(3);
|
||||
expect(useBuilderStore.getState().currentStep).toBe(3);
|
||||
|
||||
// Should not go beyond max step
|
||||
nextStep();
|
||||
expect(useBuilderStore.getState().currentStep).toBe(3);
|
||||
});
|
||||
|
||||
it('manages objectives list', () => {
|
||||
const { addObjective, removeObjective } = useBuilderStore.getState();
|
||||
|
||||
addObjective('Increase brand awareness');
|
||||
expect(useBuilderStore.getState().form.objectives).toContain('Increase brand awareness');
|
||||
|
||||
addObjective('Drive conversions');
|
||||
expect(useBuilderStore.getState().form.objectives.length).toBe(3); // 1 default + 2 added
|
||||
|
||||
removeObjective(0);
|
||||
expect(useBuilderStore.getState().form.objectives.length).toBe(2);
|
||||
expect(useBuilderStore.getState().form.objectives).not.toContain('Launch a conversion-focused marketing site');
|
||||
});
|
||||
|
||||
it('updates style preferences', () => {
|
||||
const { updateStyle } = useBuilderStore.getState();
|
||||
|
||||
updateStyle({ palette: 'Dark mode palette' });
|
||||
expect(useBuilderStore.getState().form.style.palette).toBe('Dark mode palette');
|
||||
|
||||
updateStyle({ personality: 'Professional, trustworthy' });
|
||||
expect(useBuilderStore.getState().form.style.personality).toBe('Professional, trustworthy');
|
||||
expect(useBuilderStore.getState().form.style.palette).toBe('Dark mode palette'); // Previous value preserved
|
||||
});
|
||||
|
||||
it('resets to initial state', () => {
|
||||
const { setField, nextStep, addObjective, reset } = useBuilderStore.getState();
|
||||
|
||||
setField('siteName', 'Modified');
|
||||
nextStep();
|
||||
addObjective('Test objective');
|
||||
|
||||
reset();
|
||||
|
||||
const state = useBuilderStore.getState();
|
||||
expect(state.form.siteName).toBe('');
|
||||
expect(state.currentStep).toBe(0);
|
||||
expect(state.form.objectives).toEqual(['Launch a conversion-focused marketing site']);
|
||||
expect(state.isSubmitting).toBe(false);
|
||||
expect(state.error).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
95
site-builder/src/state/__tests__/siteDefinitionStore.test.ts
Normal file
95
site-builder/src/state/__tests__/siteDefinitionStore.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useSiteDefinitionStore } from '../siteDefinitionStore';
|
||||
import type { SiteStructure, PageBlueprint } from '../../types/siteBuilder';
|
||||
|
||||
describe('siteDefinitionStore', () => {
|
||||
beforeEach(() => {
|
||||
useSiteDefinitionStore.setState({
|
||||
structure: undefined,
|
||||
pages: [],
|
||||
selectedSlug: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('initializes with empty state', () => {
|
||||
const state = useSiteDefinitionStore.getState();
|
||||
expect(state.pages).toEqual([]);
|
||||
expect(state.structure).toBeUndefined();
|
||||
expect(state.selectedSlug).toBeUndefined();
|
||||
});
|
||||
|
||||
it('sets structure and auto-selects first page', () => {
|
||||
const mockStructure: SiteStructure = {
|
||||
site: { name: 'Test Site' },
|
||||
pages: [
|
||||
{ slug: 'home', title: 'Home', type: 'home', blocks: [] },
|
||||
{ slug: 'about', title: 'About', type: 'about', blocks: [] },
|
||||
],
|
||||
};
|
||||
|
||||
useSiteDefinitionStore.getState().setStructure(mockStructure);
|
||||
|
||||
const state = useSiteDefinitionStore.getState();
|
||||
expect(state.structure).toEqual(mockStructure);
|
||||
expect(state.selectedSlug).toBe('home');
|
||||
});
|
||||
|
||||
it('sets pages and auto-selects first page if none selected', () => {
|
||||
const mockPages: PageBlueprint[] = [
|
||||
{
|
||||
id: 1,
|
||||
site_blueprint: 1,
|
||||
slug: 'services',
|
||||
title: 'Services',
|
||||
type: 'services',
|
||||
status: 'ready',
|
||||
order: 0,
|
||||
blocks_json: [],
|
||||
},
|
||||
];
|
||||
|
||||
useSiteDefinitionStore.getState().setPages(mockPages);
|
||||
|
||||
const state = useSiteDefinitionStore.getState();
|
||||
expect(state.pages).toEqual(mockPages);
|
||||
expect(state.selectedSlug).toBe('services');
|
||||
});
|
||||
|
||||
it('preserves selected slug when setting pages if already selected', () => {
|
||||
useSiteDefinitionStore.setState({ selectedSlug: 'about' });
|
||||
|
||||
const mockPages: PageBlueprint[] = [
|
||||
{
|
||||
id: 1,
|
||||
site_blueprint: 1,
|
||||
slug: 'home',
|
||||
title: 'Home',
|
||||
type: 'home',
|
||||
status: 'ready',
|
||||
order: 0,
|
||||
blocks_json: [],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
site_blueprint: 1,
|
||||
slug: 'about',
|
||||
title: 'About',
|
||||
type: 'about',
|
||||
status: 'ready',
|
||||
order: 1,
|
||||
blocks_json: [],
|
||||
},
|
||||
];
|
||||
|
||||
useSiteDefinitionStore.getState().setPages(mockPages);
|
||||
|
||||
const state = useSiteDefinitionStore.getState();
|
||||
expect(state.selectedSlug).toBe('about');
|
||||
});
|
||||
|
||||
it('selects page by slug', () => {
|
||||
useSiteDefinitionStore.getState().selectPage('contact');
|
||||
expect(useSiteDefinitionStore.getState().selectedSlug).toBe('contact');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"types": ["vite/client"],
|
||||
"types": ["vite/client", "vitest/globals"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
|
||||
@@ -20,6 +20,11 @@ export default defineConfig({
|
||||
'@shared': sharedComponentsPath,
|
||||
},
|
||||
},
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
setupFiles: './src/setupTests.ts',
|
||||
globals: true,
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5175,
|
||||
|
||||
Reference in New Issue
Block a user