85 lines
3.4 KiB
TypeScript
85 lines
3.4 KiB
TypeScript
|
|
import { describe, it } from 'node:test';
|
||
|
|
import assert from 'node:assert';
|
||
|
|
import fs from 'fs';
|
||
|
|
import path from 'path';
|
||
|
|
|
||
|
|
describe('GraphNodeCard Optimistic Label Updates', () => {
|
||
|
|
const filePath = path.join(process.cwd(), 'src/components/graph/graph-node-card.tsx');
|
||
|
|
const source = fs.readFileSync(filePath, 'utf-8');
|
||
|
|
|
||
|
|
it('uses useRef to track pending optimistic labels', () => {
|
||
|
|
assert.ok(source.includes('useRef'), 'Should import and use useRef for tracking pending operations');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('tracks pending optimistic labels in a Set', () => {
|
||
|
|
assert.ok(
|
||
|
|
source.includes('pendingOptimisticLabels') || source.includes('Set'),
|
||
|
|
'Should track pending labels in a Set to prevent SSE overwrites'
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('preserves optimistic labels when data.labels sync happens', () => {
|
||
|
|
// Should merge server labels with pending optimistic labels
|
||
|
|
assert.ok(
|
||
|
|
source.includes('merged') || source.includes('pending') || source.includes('preserve'),
|
||
|
|
'Should merge server data with pending optimistic labels during sync'
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('adds label to pending set when optimistic update happens', () => {
|
||
|
|
// When optimistically adding, should also track in pending set
|
||
|
|
assert.ok(
|
||
|
|
source.includes('pending') && source.includes('add'),
|
||
|
|
'Should add to pending set when optimistically adding a label'
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('removes label from pending set after successful API response', () => {
|
||
|
|
// After API success, the label is now in server data, so remove from pending
|
||
|
|
assert.ok(
|
||
|
|
source.includes('delete') || source.includes('pending') && source.includes('finally'),
|
||
|
|
'Should clean up pending set after API completes'
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('handles multiple rapid assign/unassign operations', () => {
|
||
|
|
// The pending set approach should handle concurrent operations
|
||
|
|
assert.ok(
|
||
|
|
source.includes('pendingOptimisticLabels') || source.includes('pending'),
|
||
|
|
'Should use a tracking mechanism that handles multiple concurrent operations'
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('GraphNodeCard Label State Isolation', () => {
|
||
|
|
const filePath = path.join(process.cwd(), 'src/components/graph/graph-node-card.tsx');
|
||
|
|
const source = fs.readFileSync(filePath, 'utf-8');
|
||
|
|
|
||
|
|
it('each node has its own localLabels state', () => {
|
||
|
|
// localLabels should be useState, not shared across nodes
|
||
|
|
assert.ok(source.includes('useState'), 'Should use useState for per-node label state');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('localLabels is initialized from data.labels', () => {
|
||
|
|
assert.ok(
|
||
|
|
source.includes('localLabels') && source.includes('data.labels'),
|
||
|
|
'localLabels should be initialized from data.labels prop'
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('syncs with data.labels when parent refreshes', () => {
|
||
|
|
assert.ok(
|
||
|
|
source.includes('useEffect') && source.includes('data.labels'),
|
||
|
|
'Should have useEffect to sync with data.labels changes'
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('prevents sync overwrite during optimistic operations', () => {
|
||
|
|
// This is the key fix - should not blindly overwrite during operations
|
||
|
|
assert.ok(
|
||
|
|
source.includes('pending') || source.includes('skip') || source.includes('preserve'),
|
||
|
|
'Should prevent SSE sync from overwriting optimistic updates'
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|