-
Couldn't load subscription status.
- Fork 3
Development
Christopher Schwarz edited this page Aug 4, 2025
·
6 revisions
- Node.js 16+
- Directus v11.0.0+
- TypeScript knowledge
- pnpm (preferred) or npm
# 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 devnpm 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 buildNote:
- For interface development, an automatic watcher rebuilds changes
- For API testing, you MUST run
npm run buildmanually - 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
{
"options": {
"debugMode": true
}
}This logs:
- State changes
- Dirty detection
- Save/discard events
- Watcher triggers
| 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
|
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
describe('useBlockState', () => {
it('should track dirty state', () => {
const state = useBlockState(context);
// Test implementation
});
});// Test API services
describe('ItemLoader Service', () => {
it('should load items with permissions', async () => {
// Test implementation
});
});// ❌ Wrong
onMounted(() => {
processData(props.value); // Often null!
});
// ✅ Correct
watch(() => props.value, (newValue) => {
if (newValue) processData(newValue);
}, { immediate: true });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
The extension tracks both:
- Content changes (deep comparison)
- Position changes (order tracking)
// ❌ Never use console.log directly!
console.log('Debug:', data);
// ✅ Always use the logger wrapper
import { logDebug } from '../utils/logger-wrapper';
logDebug('Debug data', { data });- Debounce Updates - Prevent excessive emits
- Selective Emitting - Only send changed blocks
- Lazy Loading - Load block content on expand
- Cleanup - Clear watchers and state on unmount
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)
- Always validate input
- Check permissions before actions
- Never use
v-htmlwith user content - Let Directus handle validation
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
// 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;
}npm run test -- --run # Run once
npm run test:ui # UI interface
npm run test:coverage # Coverage reportTest 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%
- Test Organization
describe('Feature: Drag and Drop', () => {
describe('when sorting enabled', () => {
// Tests
});
});- Use Factories
function createBlock(overrides = {}) {
return {
id: 1,
collection: 'content_text',
item: { title: 'Test' },
...overrides
};
}Always use the logger wrapper:
// ❌ Wrong
console.log('Debug:', data);
// ✅ Correct
import { logDebug, logError, logWarn } from '../utils/logger-wrapper';
logDebug('Debug data', { data });- 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();
-
logDebug()- Debug information -
logError()- Errors with stack traces (always in development) -
logWarn()- Warnings -
logAction()- User actions -
logStateChange()- State changes -
logEvent()- Events -
logPerformance()- Performance metrics
import { createScopedLogger } from '../utils/logger-wrapper';
const logger = createScopedLogger('MyComponent');
logger.log('Opening drawer', { collection });
logger.error('Failed to load', error);The extension includes a professional demo recording system using Playwright:
npm run demo:product # Complete presentation
npm run demo:highlights # Feature highlights
npm run demo:record # All demosOutput:
- WebM Videos:
demo-test-results/ - Screenshots:
demo-test-results/*/test-finished-*.png - HTML Report:
demo-results/index.html
- Configuration - Interface configuration options
- Architecture Overview - Technical architecture
- Contributing - Contribution guidelines
- Installation - Installation instructions
Back to: Home