Skip to content

Development

Christopher Schwarz edited this page Aug 4, 2025 · 6 revisions

Development Guide

🛠️ Development Setup

Prerequisites

  • Node.js 16+
  • Directus v11.0.0+
  • TypeScript knowledge
  • pnpm (preferred) or npm

Getting Started

# Clone and install
git clone https://github.com/smartlabsAT/directus-expandable-blocks.git
cd directus-expandable-blocks
pnpm install  # Preferred for better performance
# or
npm install

# Start development
npm run dev

Development Commands

npm run build        # Build the extension (required for API testing)
npm run dev          # Development mode with watcher
npm run test -- --run # Run unit tests (use --run to avoid watcher)
npm run test:ui      # Vitest UI interface
npm run test:coverage -- --run # With coverage reports
npm run type-check   # TypeScript checks
npm run lint         # Run ESLint
npm run lint:fix     # Auto-fix linting issues
npm run qodana       # Run Qodana code quality analysis

# Docker
docker compose restart directus # Restart after build

Note:

  • For interface development, an automatic watcher rebuilds changes
  • For API testing, you MUST run npm run build manually
  • E2E tests have been removed from the project
  • Tests achieve 100% pass rate (533 tests passing, 5 skipped)
  • Package management: Use pnpm for installation, npm for scripts

🐛 Debugging

Enable Debug Mode

{
  "options": {
    "debugMode": true
  }
}

This logs:

  • State changes
  • Dirty detection
  • Save/discard events
  • Watcher triggers

Common Issues

Issue Solution
Blocks not loading Check if props.value exists in watcher
Sort not saving Verify sort_field in relation settings
Always dirty Use deepClone() for original states
Props timing Process data in watchers, not onMounted

📁 Project Structure

src/
├── interface.vue          # Main orchestrator
├── components/           # UI components
│   ├── BlockList.vue
│   ├── BlockItem.vue
│   ├── ItemSelectorDrawer.vue
│   ├── ItemSelectorTable.vue
│   └── ...
├── composables/          # Business logic
│   ├── useExpandableBlocks.ts
│   ├── useBlockState.ts
│   ├── useBlockActions.ts
│   ├── useBlockWatchers.ts
│   ├── useM2AData.ts
│   ├── useUIHelpers.ts
│   └── useItemSelector.ts
├── api/                  # Backend API
│   ├── api.ts           # Main endpoint
│   ├── services/        # Business services
│   └── utils/           # API helpers
├── utils/                # Helpers
└── types/                # TypeScript types

🧪 Testing

Unit Tests

describe('useBlockState', () => {
  it('should track dirty state', () => {
    const state = useBlockState(context);
    // Test implementation
  });
});

Integration Tests

// Test API services
describe('ItemLoader Service', () => {
  it('should load items with permissions', async () => {
    // Test implementation
  });
});

💡 Key Concepts

1. Timing is Critical

// ❌ Wrong
onMounted(() => {
  processData(props.value); // Often null!
});

// ✅ Correct
watch(() => props.value, (newValue) => {
  if (newValue) processData(newValue);
}, { immediate: true });

2. Composables Pattern

Each composable has a specific responsibility:

  • useBlockState - State management
  • useBlockActions - CRUD operations
  • useBlockWatchers - Reactive effects
  • useM2AData - Data transformation
  • useUIHelpers - UI logic
  • useItemSelector - Item selection logic

3. Dirty State Tracking

The extension tracks both:

  • Content changes (deep comparison)
  • Position changes (order tracking)

4. Logging System

// ❌ Never use console.log directly!
console.log('Debug:', data);

// ✅ Always use the logger wrapper
import { logDebug } from '../utils/logger-wrapper';
logDebug('Debug data', { data });

🚀 Performance Tips

  1. Debounce Updates - Prevent excessive emits
  2. Selective Emitting - Only send changed blocks
  3. Lazy Loading - Load block content on expand
  4. Cleanup - Clear watchers and state on unmount

Caching

The extension includes a complete caching infrastructure:

  • Currently DISABLED for development/testing
  • To Enable: Uncomment line 72 in src/api/middleware/cache.ts
  • Benefits: Significantly improves performance for large datasets
  • Configuration: Via environment variables (see Configuration#cache-configuration)

🔒 Security

  • Always validate input
  • Check permissions before actions
  • Never use v-html with user content
  • Let Directus handle validation

📝 TypeScript Type System

The extension is built with TypeScript in strict mode. All types are in src/types/:

src/types/
├── index.ts              # Main exports
├── composable-context.ts # Composable context interfaces
├── data.ts              # Data structure types
├── directus.ts          # Directus-specific types
├── options.ts           # Configuration options
├── props.ts             # Component props
├── relations.ts         # Relationship types
└── translations.ts      # Translation-related types

Key Interfaces

// Main junction record structure
interface JunctionRecord {
  id: string | number;
  collection: string;
  item: string | number | ItemRecord;
  sort?: number;
  [foreignKey: string]: any;
}

// Composable context
interface ExpandableBlocksContext {
  props: Ref<ExpandableBlocksProps>;
  emit: (event: string, ...args: any[]) => void;
  stores: DirectusStores;
}

🧪 Testing Strategy

Unit Testing with Vitest

npm run test -- --run     # Run once
npm run test:ui           # UI interface
npm run test:coverage     # Coverage report

Test Structure:

test/
├── unit/
│   ├── composables/     # Composable tests
│   ├── components/      # Component tests
│   ├── utils/          # Utility tests
│   └── api/            # API service tests
├── mocks/              # Mocked dependencies
└── setup.ts           # Test configuration

Coverage Goals:

  • Statements: > 80%
  • Branches: > 75%
  • Functions: > 80%
  • Lines: > 80%

Testing Best Practices

  1. Test Organization
describe('Feature: Drag and Drop', () => {
  describe('when sorting enabled', () => {
    // Tests
  });
});
  1. Use Factories
function createBlock(overrides = {}) {
  return {
    id: 1,
    collection: 'content_text',
    item: { title: 'Test' },
    ...overrides
  };
}

📋 Logging Guidelines

⚠️ NEVER use console.log directly!

Always use the logger wrapper:

// ❌ Wrong
console.log('Debug:', data);

// ✅ Correct
import { logDebug, logError, logWarn } from '../utils/logger-wrapper';
logDebug('Debug data', { data });

Logging Behavior

  • Development Mode: Logging is automatically enabled
  • Production Mode: Logging is disabled by default
  • Manual Control in Production:
    // Enable logging in browser console
    logger.enable();  // or localStorage.setItem('EXPANDABLE_BLOCKS_DEBUG', 'true')
    
    // Disable logging
    logger.disable();
    
    // Check status
    logger.isEnabled();

Logger Functions

  • logDebug() - Debug information
  • logError() - Errors with stack traces (always in development)
  • logWarn() - Warnings
  • logAction() - User actions
  • logStateChange() - State changes
  • logEvent() - Events
  • logPerformance() - Performance metrics

Scoped Loggers

import { createScopedLogger } from '../utils/logger-wrapper';
const logger = createScopedLogger('MyComponent');

logger.log('Opening drawer', { collection });
logger.error('Failed to load', error);

🎬 Demo Video Recording

The extension includes a professional demo recording system using Playwright:

Creating Demo Videos

npm run demo:product      # Complete presentation
npm run demo:highlights   # Feature highlights
npm run demo:record       # All demos

Output:

  • WebM Videos: demo-test-results/
  • Screenshots: demo-test-results/*/test-finished-*.png
  • HTML Report: demo-results/index.html

📚 Related Documentation

Back to: Home

Directus Expandable Blocks

🏠 Getting Started

📖 Technical Documentation

🛠️ Development

📋 Project Info

🔗 Quick Links


GitHub release

Clone this wiki locally