beadboard/tests/components/graph/graph-node-labels-optimistic.test.tsx

85 lines
3.4 KiB
TypeScript
Raw Normal View History

2026-02-24 16:48:16 -08:00
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'
);
});
});