247 lines
8.7 KiB
TypeScript
247 lines
8.7 KiB
TypeScript
import * as _RhizomeImports from "../src";
|
|
/**
|
|
* Tests for lossless view compose() and decompose() bidirectional conversion
|
|
* Ensures that deltas can be composed into lossless views and decomposed back
|
|
* to the original deltas with all pointer relationships preserved.
|
|
*/
|
|
|
|
import { RhizomeNode } from '../src/node';
|
|
import { Delta } from '../src/core';
|
|
|
|
describe('Lossless View Compose/Decompose', () => {
|
|
let node: RhizomeNode;
|
|
|
|
beforeEach(() => {
|
|
node = new RhizomeNode();
|
|
});
|
|
|
|
describe('Bidirectional Conversion', () => {
|
|
it('should compose and decompose simple entity deltas correctly', () => {
|
|
// Create simple entity deltas
|
|
const nameDeltas = [
|
|
new Delta({
|
|
creator: 'test-creator',
|
|
host: 'test-host',
|
|
pointers: [
|
|
{ localContext: 'users', target: 'alice', targetContext: 'name' },
|
|
{ localContext: 'name', target: 'Alice Smith' }
|
|
]
|
|
}),
|
|
new Delta({
|
|
creator: 'test-creator',
|
|
host: 'test-host',
|
|
pointers: [
|
|
{ localContext: 'users', target: 'alice', targetContext: 'email' },
|
|
{ localContext: 'email', target: 'alice@example.com' }
|
|
]
|
|
})
|
|
];
|
|
|
|
// Ingest the deltas
|
|
nameDeltas.forEach(delta => node.lossless.ingestDelta(delta));
|
|
|
|
// Compose lossless view
|
|
const composed = node.lossless.compose(['alice']);
|
|
const aliceView = composed['alice'];
|
|
|
|
expect(aliceView).toBeDefined();
|
|
expect(aliceView.id).toBe('alice');
|
|
expect(aliceView.propertyDeltas.name).toHaveLength(1);
|
|
expect(aliceView.propertyDeltas.email).toHaveLength(1);
|
|
|
|
// Decompose back to deltas
|
|
const decomposed = node.lossless.decompose(aliceView);
|
|
|
|
expect(decomposed).toHaveLength(2);
|
|
|
|
// Check that original deltas are preserved
|
|
const originalIds = nameDeltas.map(d => d.id).sort();
|
|
const decomposedIds = decomposed.map(d => d.id).sort();
|
|
expect(decomposedIds).toEqual(originalIds);
|
|
|
|
// Verify pointer structure is preserved
|
|
const nameDataDelta = decomposed.find(d =>
|
|
d.pointers.some(p => p.localContext === 'name' && p.target === 'Alice Smith')
|
|
);
|
|
expect(nameDataDelta).toBeDefined();
|
|
expect(nameDataDelta?.pointers).toHaveLength(2);
|
|
|
|
const upPointer = nameDataDelta?.pointers.find(p => p.targetContext === 'name');
|
|
expect(upPointer).toBeDefined();
|
|
expect(upPointer?.target).toBe('alice');
|
|
expect(upPointer?.localContext).toBe('users');
|
|
});
|
|
|
|
it('should handle multi-pointer relationship deltas correctly', () => {
|
|
// Create a complex relationship delta
|
|
const relationshipDelta = new Delta({
|
|
creator: 'test-creator',
|
|
host: 'test-host',
|
|
pointers: [
|
|
{ localContext: 'users', target: 'alice', targetContext: 'relationships' },
|
|
{ localContext: 'partner', target: 'bob' },
|
|
{ localContext: 'type', target: 'friendship' },
|
|
{ localContext: 'since', target: '2020-01-15' },
|
|
{ localContext: 'intensity', target: 8 }
|
|
]
|
|
});
|
|
|
|
node.lossless.ingestDelta(relationshipDelta);
|
|
|
|
// Compose and decompose
|
|
const composed = node.lossless.compose(['alice']);
|
|
const aliceView = composed['alice'];
|
|
const decomposed = node.lossless.decompose(aliceView);
|
|
|
|
expect(decomposed).toHaveLength(1);
|
|
const reconstituted = decomposed[0];
|
|
|
|
// Should have all 5 pointers
|
|
expect(reconstituted.pointers).toHaveLength(5);
|
|
|
|
// Check that all pointer types are preserved
|
|
const contexts = reconstituted.pointers.map(p => p.localContext).sort();
|
|
expect(contexts).toEqual(['users', 'partner', 'type', 'since', 'intensity'].sort());
|
|
|
|
// Check that the "up" pointer to alice is correctly reconstructed
|
|
const upPointer = reconstituted.pointers.find(p => p.targetContext === 'relationships');
|
|
expect(upPointer).toBeDefined();
|
|
expect(upPointer?.target).toBe('alice');
|
|
expect(upPointer?.localContext).toBe('users');
|
|
|
|
// Check scalar values are preserved
|
|
const intensityPointer = reconstituted.pointers.find(p => p.localContext === 'intensity');
|
|
expect(intensityPointer?.target).toBe(8);
|
|
});
|
|
|
|
it('should handle reference relationships correctly', () => {
|
|
// Create entities first
|
|
const aliceDelta = new Delta({
|
|
creator: 'test-creator',
|
|
host: 'test-host',
|
|
pointers: [
|
|
{ localContext: 'users', target: 'alice', targetContext: 'name' },
|
|
{ localContext: 'name', target: 'Alice' }
|
|
]
|
|
});
|
|
|
|
const bobDelta = new Delta({
|
|
creator: 'test-creator',
|
|
host: 'test-host',
|
|
pointers: [
|
|
{ localContext: 'users', target: 'bob', targetContext: 'name' },
|
|
{ localContext: 'name', target: 'Bob' }
|
|
]
|
|
});
|
|
|
|
// Create friendship relationship
|
|
const friendshipDelta = new Delta({
|
|
creator: 'test-creator',
|
|
host: 'test-host',
|
|
pointers: [
|
|
{ localContext: 'users', target: 'alice', targetContext: 'friends' },
|
|
{ localContext: 'friend', target: 'bob', targetContext: 'friends' }
|
|
]
|
|
});
|
|
|
|
[aliceDelta, bobDelta, friendshipDelta].forEach(d => node.lossless.ingestDelta(d));
|
|
|
|
// Compose Alice's view
|
|
const composed = node.lossless.compose(['alice']);
|
|
const aliceView = composed['alice'];
|
|
|
|
expect(aliceView.propertyDeltas.friends).toHaveLength(1);
|
|
|
|
// Decompose and verify the friendship delta is correctly reconstructed
|
|
const decomposed = node.lossless.decompose(aliceView);
|
|
const friendshipReconstituted = decomposed.find(d =>
|
|
d.pointers.some(p => p.localContext === 'friend')
|
|
);
|
|
|
|
expect(friendshipReconstituted).toBeDefined();
|
|
expect(friendshipReconstituted?.pointers).toHaveLength(2);
|
|
|
|
// Check both reference pointers are preserved
|
|
const alicePointer = friendshipReconstituted?.pointers.find(p => p.target === 'alice');
|
|
const bobPointer = friendshipReconstituted?.pointers.find(p => p.target === 'bob');
|
|
|
|
expect(alicePointer).toBeDefined();
|
|
expect(alicePointer?.targetContext).toBe('friends');
|
|
expect(bobPointer).toBeDefined();
|
|
expect(bobPointer?.targetContext).toBe('friends');
|
|
});
|
|
|
|
it('should preserve delta metadata correctly', () => {
|
|
const originalDelta = new Delta({
|
|
creator: 'test-creator',
|
|
host: 'test-host',
|
|
pointers: [
|
|
{ localContext: 'users', target: 'alice', targetContext: 'name' },
|
|
{ localContext: 'name', target: 'Alice' }
|
|
]
|
|
});
|
|
|
|
node.lossless.ingestDelta(originalDelta);
|
|
|
|
const composed = node.lossless.compose(['alice']);
|
|
const decomposed = node.lossless.decompose(composed['alice']);
|
|
|
|
expect(decomposed).toHaveLength(1);
|
|
const reconstituted = decomposed[0];
|
|
|
|
// Check metadata preservation
|
|
expect(reconstituted.id).toBe(originalDelta.id);
|
|
expect(reconstituted.creator).toBe(originalDelta.creator);
|
|
expect(reconstituted.host).toBe(originalDelta.host);
|
|
expect(reconstituted.timeCreated).toBe(originalDelta.timeCreated);
|
|
});
|
|
|
|
it('should handle multiple deltas for the same property', () => {
|
|
// Create multiple name changes for alice
|
|
const nameDeltas = [
|
|
new Delta({
|
|
creator: 'test-creator',
|
|
host: 'test-host',
|
|
pointers: [
|
|
{ localContext: 'users', target: 'alice', targetContext: 'name' },
|
|
{ localContext: 'name', target: 'Alice' }
|
|
]
|
|
}),
|
|
new Delta({
|
|
creator: 'test-creator',
|
|
host: 'test-host',
|
|
pointers: [
|
|
{ localContext: 'users', target: 'alice', targetContext: 'name' },
|
|
{ localContext: 'name', target: 'Alice Smith' }
|
|
]
|
|
}),
|
|
new Delta({
|
|
creator: 'test-creator',
|
|
host: 'test-host',
|
|
pointers: [
|
|
{ localContext: 'users', target: 'alice', targetContext: 'name' },
|
|
{ localContext: 'name', target: 'Alice Johnson' }
|
|
]
|
|
})
|
|
];
|
|
|
|
nameDeltas.forEach(d => node.lossless.ingestDelta(d));
|
|
|
|
const composed = node.lossless.compose(['alice']);
|
|
const aliceView = composed['alice'];
|
|
|
|
// Should have 3 deltas for the name property
|
|
expect(aliceView.propertyDeltas.name).toHaveLength(3);
|
|
|
|
const decomposed = node.lossless.decompose(aliceView);
|
|
|
|
// Should decompose back to 3 separate deltas
|
|
expect(decomposed).toHaveLength(3);
|
|
|
|
// All original delta IDs should be preserved
|
|
const originalIds = nameDeltas.map(d => d.id).sort();
|
|
const decomposedIds = decomposed.map(d => d.id).sort();
|
|
expect(decomposedIds).toEqual(originalIds);
|
|
});
|
|
});
|
|
}); |