- Refactored isDeltaNegated to properly handle complex negation chains - Added recursive negation detection to account for negations of negations - Replaced console.log with debug statements in tests - Improved test coverage and documentation - Fixed TypeScript type issues
628 lines
22 KiB
TypeScript
628 lines
22 KiB
TypeScript
import Debug from 'debug';
|
|
import { Delta } from '../src/core';
|
|
import { NegationHelper } from '../src/features';
|
|
import { RhizomeNode } from '../src/node';
|
|
import { Lossless } from '../src/views';
|
|
|
|
const debug = Debug('rz:negation:test');
|
|
|
|
describe('Negation System', () => {
|
|
let node: RhizomeNode;
|
|
let lossless: Lossless;
|
|
|
|
beforeEach(() => {
|
|
node = new RhizomeNode();
|
|
lossless = new Lossless(node);
|
|
});
|
|
|
|
describe('Negation Helper', () => {
|
|
it('should create negation deltas correctly', () => {
|
|
const originalDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'name', target: 'entity1', targetContext: 'name' },
|
|
{ localContext: 'value', target: 'Alice' }
|
|
]
|
|
});
|
|
|
|
const negationDelta = NegationHelper.createNegation(
|
|
originalDelta.id,
|
|
'moderator',
|
|
'host1'
|
|
);
|
|
|
|
expect(negationDelta.isNegation).toBe(true);
|
|
expect(negationDelta.negatedDeltaId).toBe(originalDelta.id);
|
|
expect(negationDelta.creator).toBe('moderator');
|
|
expect(negationDelta.pointers).toHaveLength(1);
|
|
expect(negationDelta.pointers[0]).toEqual({
|
|
localContext: 'negates',
|
|
target: originalDelta.id,
|
|
targetContext: 'negated_by'
|
|
});
|
|
});
|
|
|
|
it('should identify negation deltas', () => {
|
|
const regularDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'name', target: 'entity1', targetContext: 'name' }]
|
|
});
|
|
|
|
const negationDelta = NegationHelper.createNegation(
|
|
'delta-to-negate',
|
|
'moderator',
|
|
'host1'
|
|
);
|
|
|
|
expect(NegationHelper.isNegationDelta(regularDelta)).toBe(false);
|
|
expect(NegationHelper.isNegationDelta(negationDelta)).toBe(true);
|
|
});
|
|
|
|
it('should extract negated delta ID', () => {
|
|
const targetDeltaId = 'target-delta-123';
|
|
const negationDelta = NegationHelper.createNegation(
|
|
targetDeltaId,
|
|
'moderator',
|
|
'host1'
|
|
);
|
|
|
|
const extractedId = NegationHelper.getNegatedDeltaId(negationDelta);
|
|
expect(extractedId).toBe(targetDeltaId);
|
|
|
|
const regularDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'name', target: 'entity1', targetContext: 'name' }]
|
|
});
|
|
|
|
expect(NegationHelper.getNegatedDeltaId(regularDelta)).toBeNull();
|
|
});
|
|
|
|
it('should find negations for specific deltas', () => {
|
|
const delta1 = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'name', target: 'entity1', targetContext: 'name' }]
|
|
});
|
|
|
|
const delta2 = new Delta({
|
|
creator: 'user2',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'age', target: 'entity1', targetContext: 'age' }]
|
|
});
|
|
|
|
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
|
const negation2 = NegationHelper.createNegation(delta1.id, 'mod2', 'host1');
|
|
const negation3 = NegationHelper.createNegation(delta2.id, 'mod1', 'host1');
|
|
|
|
const allDeltas = [delta1, delta2, negation1, negation2, negation3];
|
|
|
|
const negationsForDelta1 = NegationHelper.findNegationsFor(delta1.id, allDeltas);
|
|
expect(negationsForDelta1).toHaveLength(2);
|
|
expect(negationsForDelta1.map(d => d.id)).toContain(negation1.id);
|
|
expect(negationsForDelta1.map(d => d.id)).toContain(negation2.id);
|
|
|
|
const negationsForDelta2 = NegationHelper.findNegationsFor(delta2.id, allDeltas);
|
|
expect(negationsForDelta2).toHaveLength(1);
|
|
expect(negationsForDelta2[0].id).toBe(negation3.id);
|
|
});
|
|
|
|
it('should check if deltas are negated', () => {
|
|
const delta1 = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'name', target: 'entity1', targetContext: 'name' }]
|
|
});
|
|
|
|
const delta2 = new Delta({
|
|
creator: 'user2',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'age', target: 'entity1', targetContext: 'age' }]
|
|
});
|
|
|
|
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
|
const allDeltas = [delta1, delta2, negation1];
|
|
|
|
expect(NegationHelper.isDeltaNegated(delta1.id, allDeltas)).toBe(true);
|
|
expect(NegationHelper.isDeltaNegated(delta2.id, allDeltas)).toBe(false);
|
|
});
|
|
|
|
it('should filter out negated deltas', () => {
|
|
const delta1 = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'name', target: 'entity1', targetContext: 'name' }]
|
|
});
|
|
|
|
const delta2 = new Delta({
|
|
creator: 'user2',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'age', target: 'entity1', targetContext: 'age' }]
|
|
});
|
|
|
|
const delta3 = new Delta({
|
|
creator: 'user3',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'email', target: 'entity1', targetContext: 'email' }]
|
|
});
|
|
|
|
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
|
const negation2 = NegationHelper.createNegation(delta2.id, 'mod2', 'host1');
|
|
|
|
const allDeltas = [delta1, delta2, delta3, negation1, negation2];
|
|
const filtered = NegationHelper.filterNegatedDeltas(allDeltas);
|
|
|
|
// Should only include delta3 (delta1 and delta2 are negated, negations themselves are filtered)
|
|
expect(filtered).toHaveLength(1);
|
|
expect(filtered[0].id).toBe(delta3.id);
|
|
});
|
|
|
|
it('should provide negation statistics', () => {
|
|
const delta1 = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'name', target: 'entity1', targetContext: 'name' }]
|
|
});
|
|
|
|
const delta2 = new Delta({
|
|
creator: 'user2',
|
|
host: 'host1',
|
|
pointers: [{ localContext: 'age', target: 'entity1', targetContext: 'age' }]
|
|
});
|
|
|
|
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
|
const allDeltas = [delta1, delta2, negation1];
|
|
|
|
const stats = NegationHelper.getNegationStats(allDeltas);
|
|
|
|
expect(stats.totalDeltas).toBe(3);
|
|
expect(stats.negationDeltas).toBe(1);
|
|
expect(stats.negatedDeltas).toBe(1);
|
|
expect(stats.effectiveDeltas).toBe(1); // only delta2 is effective
|
|
expect(stats.negatedDeltaIds).toContain(delta1.id);
|
|
expect(stats.negationMap.get(delta1.id)).toContain(negation1.id);
|
|
});
|
|
|
|
it('should apply negations chronologically', () => {
|
|
const baseTime = Date.now();
|
|
|
|
// Create deltas with specific timestamps
|
|
const delta1 = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
timeCreated: baseTime,
|
|
pointers: [{ localContext: 'status', target: 'doc1', targetContext: 'status' }]
|
|
});
|
|
|
|
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
|
negation1.timeCreated = baseTime + 1000; // 1 second later
|
|
|
|
const delta2 = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
timeCreated: baseTime + 2000, // 2 seconds later
|
|
pointers: [{ localContext: 'status', target: 'doc1', targetContext: 'status' }]
|
|
});
|
|
|
|
const negation2 = NegationHelper.createNegation(delta2.id, 'mod1', 'host1');
|
|
negation2.timeCreated = baseTime + 3000; // 3 seconds later
|
|
|
|
const allDeltas = [delta1, negation1, delta2, negation2];
|
|
const filtered = NegationHelper.applyNegationsChronologically(allDeltas);
|
|
|
|
// Both deltas should be negated
|
|
expect(filtered).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe('Lossless View Integration', () => {
|
|
it('should filter negated deltas in lossless views', () => {
|
|
// Create original delta
|
|
const originalDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'name', target: 'user123', targetContext: 'name' },
|
|
{ localContext: 'value', target: 'Alice' }
|
|
]
|
|
});
|
|
|
|
// Create negation delta
|
|
const negationDelta = NegationHelper.createNegation(
|
|
originalDelta.id,
|
|
'moderator',
|
|
'host1'
|
|
);
|
|
|
|
|
|
// Create another non-negated delta
|
|
const nonNegatedDelta = new Delta({
|
|
creator: 'user2',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'age', target: 'user123', targetContext: 'age' },
|
|
{ localContext: 'value', target: 25 }
|
|
]
|
|
});
|
|
|
|
// Ingest all deltas
|
|
lossless.ingestDelta(originalDelta);
|
|
lossless.ingestDelta(negationDelta);
|
|
lossless.ingestDelta(nonNegatedDelta);
|
|
|
|
// Get view - should only show non-negated delta
|
|
const view = lossless.view(['user123']);
|
|
|
|
expect(view.user123).toBeDefined();
|
|
|
|
// Should only have age property (name was negated)
|
|
expect(view.user123.propertyDeltas.age).toHaveLength(1);
|
|
expect(view.user123.propertyDeltas.name).toBeUndefined();
|
|
});
|
|
|
|
it('should handle multiple negations of the same delta', () => {
|
|
const originalDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'content', target: 'post1', targetContext: 'content' },
|
|
{ localContext: 'value', target: 'Original content' }
|
|
]
|
|
});
|
|
|
|
const negation1 = NegationHelper.createNegation(originalDelta.id, 'mod1', 'host1');
|
|
const negation2 = NegationHelper.createNegation(originalDelta.id, 'mod2', 'host1');
|
|
|
|
lossless.ingestDelta(originalDelta);
|
|
lossless.ingestDelta(negation1);
|
|
lossless.ingestDelta(negation2);
|
|
|
|
const view = lossless.view(['post1']);
|
|
|
|
// Original delta should be negated (not visible)
|
|
expect(view.post1).toBeUndefined();
|
|
});
|
|
|
|
it('should provide negation statistics for entities', () => {
|
|
const delta1 = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'title', target: 'article1', targetContext: 'title' },
|
|
{ localContext: 'value', target: 'Original Title' }
|
|
]
|
|
});
|
|
|
|
const delta2 = new Delta({
|
|
creator: 'user2',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'content', target: 'article1', targetContext: 'content' },
|
|
{ localContext: 'value', target: 'Article content' }
|
|
]
|
|
});
|
|
|
|
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
|
|
|
lossless.ingestDelta(delta1);
|
|
lossless.ingestDelta(delta2);
|
|
lossless.ingestDelta(negation1);
|
|
|
|
const stats = lossless.getNegationStats('article1');
|
|
|
|
expect(stats.totalDeltas).toBe(3);
|
|
expect(stats.negationDeltas).toBe(1);
|
|
expect(stats.negatedDeltas).toBe(1);
|
|
expect(stats.effectiveDeltas).toBe(1);
|
|
expect(stats.negationsByProperty.title.negated).toBe(1);
|
|
expect(stats.negationsByProperty.content.negated).toBe(0);
|
|
});
|
|
|
|
it('should retrieve negation deltas for entities', () => {
|
|
const originalDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'status', target: 'task1', targetContext: 'status' },
|
|
{ localContext: 'value', target: 'pending' }
|
|
]
|
|
});
|
|
|
|
const negationDelta = NegationHelper.createNegation(
|
|
originalDelta.id,
|
|
'admin',
|
|
'host1'
|
|
);
|
|
|
|
lossless.ingestDelta(originalDelta);
|
|
lossless.ingestDelta(negationDelta);
|
|
|
|
const negations = lossless.getNegationDeltas('task1');
|
|
expect(negations).toHaveLength(1);
|
|
expect(negations[0].id).toBe(negationDelta.id);
|
|
expect(negations[0].creator).toBe('admin');
|
|
});
|
|
|
|
it('should handle negation in transactions', () => {
|
|
const transactionId = 'tx-negation';
|
|
|
|
// Create transaction declaration
|
|
lossless.ingestDelta(new Delta({
|
|
creator: 'system',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: '_transaction', target: transactionId, targetContext: 'size' },
|
|
{ localContext: 'size', target: 2 }
|
|
]
|
|
}));
|
|
|
|
// Create original delta in transaction
|
|
const originalDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: '_transaction', target: transactionId, targetContext: 'deltas' },
|
|
{ localContext: 'comment', target: 'post1', targetContext: 'comments' },
|
|
{ localContext: 'text', target: 'Inappropriate comment' }
|
|
]
|
|
});
|
|
|
|
// Create negation delta in same transaction
|
|
const negationDelta = NegationHelper.createNegation(originalDelta.id, 'moderator', 'host1');
|
|
negationDelta.pointers.unshift({
|
|
localContext: '_transaction',
|
|
target: transactionId,
|
|
targetContext: 'deltas'
|
|
});
|
|
|
|
lossless.ingestDelta(originalDelta);
|
|
lossless.ingestDelta(negationDelta);
|
|
|
|
// Transaction should complete, but original delta should be negated
|
|
const view = lossless.view(['post1']);
|
|
expect(view.post1).toBeUndefined(); // No visible deltas
|
|
});
|
|
|
|
it('should handle chronological negation scenarios', () => {
|
|
const baseTime = Date.now();
|
|
|
|
// User posts content
|
|
const postDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
timeCreated: baseTime,
|
|
pointers: [
|
|
{ localContext: 'content', target: 'post1', targetContext: 'content' },
|
|
{ localContext: 'value', target: 'Original post' }
|
|
]
|
|
});
|
|
|
|
// Moderator negates it
|
|
const negationDelta = NegationHelper.createNegation(postDelta.id, 'moderator', 'host1');
|
|
negationDelta.timeCreated = baseTime + 1000;
|
|
|
|
// User edits content (after negation)
|
|
const editDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
timeCreated: baseTime + 2000,
|
|
pointers: [
|
|
{ localContext: 'content', target: 'post1', targetContext: 'content' },
|
|
{ localContext: 'value', target: 'Edited post' }
|
|
]
|
|
});
|
|
|
|
lossless.ingestDelta(postDelta);
|
|
lossless.ingestDelta(negationDelta);
|
|
lossless.ingestDelta(editDelta);
|
|
|
|
const view = lossless.view(['post1']);
|
|
|
|
// Should show edited content (edit happened after negation)
|
|
expect(view.post1).toBeDefined();
|
|
expect(view.post1.propertyDeltas.content).toHaveLength(1);
|
|
|
|
// The visible delta should be the edit delta
|
|
const visibleDelta = view.post1.propertyDeltas.content[0];
|
|
expect(visibleDelta.id).toBe(editDelta.id);
|
|
});
|
|
});
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should handle negation of non-existent deltas', () => {
|
|
const negationDelta = NegationHelper.createNegation(
|
|
'non-existent-delta-id',
|
|
'moderator',
|
|
'host1'
|
|
);
|
|
|
|
lossless.ingestDelta(negationDelta);
|
|
|
|
// Should not crash and stats should reflect the orphaned negation
|
|
const stats = lossless.getNegationStats('entity1');
|
|
expect(stats.negationDeltas).toBe(0); // No negations for this entity
|
|
});
|
|
|
|
it('should handle self-referential entities in negations', () => {
|
|
// Create a delta that references itself
|
|
const selfRefDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'parent', target: 'node1', targetContext: 'parent' },
|
|
{ localContext: 'child', target: 'node1' } // Self-reference
|
|
]
|
|
});
|
|
|
|
const negationDelta = NegationHelper.createNegation(selfRefDelta.id, 'admin', 'host1');
|
|
|
|
lossless.ingestDelta(selfRefDelta);
|
|
lossless.ingestDelta(negationDelta);
|
|
|
|
const view = lossless.view(['node1']);
|
|
expect(view.node1).toBeUndefined(); // Should be negated
|
|
});
|
|
|
|
it('should handle multiple direct negations of the same delta', () => {
|
|
const testNode = new RhizomeNode();
|
|
const testLossless = new Lossless(testNode);
|
|
|
|
// Create the original delta
|
|
const originalDelta = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'title', target: 'entity2', targetContext: 'title' },
|
|
{ localContext: 'status', target: 'Draft' }
|
|
]
|
|
});
|
|
|
|
// Create two negations of the same delta
|
|
const negation1 = NegationHelper.createNegation(originalDelta.id, 'user2', 'host1');
|
|
const negation2 = NegationHelper.createNegation(originalDelta.id, 'user3', 'host1');
|
|
|
|
// Process all deltas
|
|
testLossless.ingestDelta(originalDelta);
|
|
testLossless.ingestDelta(negation1);
|
|
testLossless.ingestDelta(negation2);
|
|
|
|
// Get the view after processing all deltas
|
|
const view = testLossless.view(['entity2']);
|
|
|
|
// The original delta should be negated (not in view) because it has two direct negations
|
|
expect(view.entity2).toBeUndefined();
|
|
|
|
// Verify the stats
|
|
const stats = testLossless.getNegationStats('entity2');
|
|
expect(stats.negationDeltas).toBe(2);
|
|
expect(stats.negatedDeltas).toBe(1);
|
|
expect(stats.effectiveDeltas).toBe(0);
|
|
});
|
|
|
|
it('should handle complex negation chains', () => {
|
|
const testNode = new RhizomeNode();
|
|
const testLossless = new Lossless(testNode);
|
|
|
|
// Create the original delta
|
|
const deltaA = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'content', target: 'entity3', targetContext: 'content' },
|
|
{ localContext: 'text', target: 'Hello World' }
|
|
]
|
|
});
|
|
|
|
// Create a chain of negations: B negates A, C negates B, D negates C
|
|
const deltaB = NegationHelper.createNegation(deltaA.id, 'user2', 'host1');
|
|
const deltaC = NegationHelper.createNegation(deltaB.id, 'user3', 'host1');
|
|
const deltaD = NegationHelper.createNegation(deltaC.id, 'user4', 'host1');
|
|
|
|
debug('Delta A (original): %s', deltaA.id);
|
|
debug('Delta B (negates A): %s', deltaB.id);
|
|
debug('Delta C (negates B): %s', deltaC.id);
|
|
debug('Delta D (negates C): %s', deltaD.id);
|
|
|
|
// Process all deltas in order
|
|
testLossless.ingestDelta(deltaA);
|
|
testLossless.ingestDelta(deltaB);
|
|
testLossless.ingestDelta(deltaC);
|
|
testLossless.ingestDelta(deltaD);
|
|
|
|
// Get the view after processing all deltas
|
|
const view = testLossless.view(['entity3']);
|
|
|
|
// The original delta should be negated because:
|
|
// - B negates A
|
|
// - C negates B (so A is no longer negated)
|
|
// - D negates C (so B is no longer negated, and A is negated again by B)
|
|
expect(view.entity3).toBeUndefined();
|
|
|
|
// Get all deltas for the entity
|
|
const allDeltas = [deltaA, deltaB, deltaC, deltaD];
|
|
|
|
// Get the stats
|
|
const stats = testLossless.getNegationStats('entity3');
|
|
const isANegated = NegationHelper.isDeltaNegated(deltaA.id, allDeltas);
|
|
const isBNegated = NegationHelper.isDeltaNegated(deltaB.id, allDeltas);
|
|
const isCNegated = NegationHelper.isDeltaNegated(deltaC.id, allDeltas);
|
|
const isDNegated = NegationHelper.isDeltaNegated(deltaD.id, allDeltas);
|
|
|
|
debug('Delta statuses:');
|
|
debug('- A (%s): %s', deltaA.id, isANegated ? 'NEGATED' : 'ACTIVE');
|
|
debug('- B (%s): %s, negates: %s', deltaB.id, isBNegated ? 'NEGATED' : 'ACTIVE', NegationHelper.getNegatedDeltaId(deltaB));
|
|
debug('- C (%s): %s, negates: %s', deltaC.id, isCNegated ? 'NEGATED' : 'ACTIVE', NegationHelper.getNegatedDeltaId(deltaC));
|
|
debug('- D (%s): %s, negates: %s', deltaD.id, isDNegated ? 'NEGATED' : 'ACTIVE', NegationHelper.getNegatedDeltaId(deltaD));
|
|
|
|
debug('Negation stats: %O', {
|
|
totalDeltas: stats.totalDeltas,
|
|
negationDeltas: stats.negationDeltas,
|
|
negatedDeltas: stats.negatedDeltas,
|
|
effectiveDeltas: stats.effectiveDeltas,
|
|
negationsByProperty: stats.negationsByProperty
|
|
});
|
|
|
|
// B, C, D are negation deltas
|
|
expect(stats.negationDeltas).toBe(3);
|
|
|
|
// A and C are effectively negated
|
|
expect(isANegated).toBe(true);
|
|
expect(isCNegated).toBe(true);
|
|
|
|
// B and D are not negated (they are negation deltas that are not themselves negated)
|
|
expect(isBNegated).toBe(false);
|
|
expect(isDNegated).toBe(false);
|
|
|
|
// No deltas remain unnegated
|
|
expect(stats.effectiveDeltas).toBe(0);
|
|
});
|
|
|
|
it('should handle multiple independent negations', () => {
|
|
const testNode = new RhizomeNode();
|
|
const testLossless = new Lossless(testNode);
|
|
|
|
// Create two independent deltas
|
|
const delta1 = new Delta({
|
|
creator: 'user1',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'item', target: 'entity4', targetContext: 'item' },
|
|
{ localContext: 'name', target: 'Item 1' }
|
|
]
|
|
});
|
|
|
|
const delta2 = new Delta({
|
|
creator: 'user2',
|
|
host: 'host1',
|
|
pointers: [
|
|
{ localContext: 'item', target: 'entity4', targetContext: 'item' },
|
|
{ localContext: 'name', target: 'Item 2' }
|
|
]
|
|
});
|
|
|
|
// Create negations for both deltas
|
|
const negation1 = NegationHelper.createNegation(delta1.id, 'user3', 'host1');
|
|
const negation2 = NegationHelper.createNegation(delta2.id, 'user4', 'host1');
|
|
|
|
// Process all deltas
|
|
testLossless.ingestDelta(delta1);
|
|
testLossless.ingestDelta(delta2);
|
|
testLossless.ingestDelta(negation1);
|
|
testLossless.ingestDelta(negation2);
|
|
|
|
// Get the view after processing all deltas
|
|
const view = testLossless.view(['entity4']);
|
|
|
|
// Both deltas should be negated
|
|
expect(view.entity4).toBeUndefined();
|
|
|
|
// Verify the stats
|
|
const stats = testLossless.getNegationStats('entity4');
|
|
expect(stats.negationDeltas).toBe(2);
|
|
expect(stats.negatedDeltas).toBe(2);
|
|
expect(stats.effectiveDeltas).toBe(0);
|
|
});
|
|
});
|
|
}); |