igniteui-angular-sovn
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,056 lines (901 loc) • 49.9 kB
text/typescript
import { IgxTransactionService } from './igx-transaction';
import { Transaction, TransactionType, HierarchicalTransaction } from './transaction';
import { SampleTestData } from '../../test-utils/sample-test-data.spec';
import { IgxHierarchicalTransactionService } from './igx-hierarchical-transaction';
describe('IgxTransaction', () => {
describe('IgxTransaction UNIT tests', () => {
it('Should initialize transactions log properly', () => {
const trans = new IgxTransactionService();
expect(trans).toBeDefined();
expect(trans['_transactions']).toBeDefined();
expect(trans['_transactions'].length).toEqual(0);
expect(trans['_redoStack']).toBeDefined();
expect(trans['_redoStack'].length).toEqual(0);
expect(trans['_states']).toBeDefined();
expect(trans['_states'].size).toEqual(0);
});
it('Should add transactions to the transactions log', () => {
const trans = new IgxTransactionService();
const transactions: Transaction[] = [
{ id: '1', type: TransactionType.ADD, newValue: 1 },
{ id: '2', type: TransactionType.ADD, newValue: 2 },
{ id: '3', type: TransactionType.ADD, newValue: 3 },
{ id: '1', type: TransactionType.UPDATE, newValue: 4 },
{ id: '5', type: TransactionType.ADD, newValue: 5 },
{ id: '6', type: TransactionType.ADD, newValue: 6 },
{ id: '2', type: TransactionType.DELETE, newValue: 7 },
{ id: '8', type: TransactionType.ADD, newValue: 8 },
{ id: '9', type: TransactionType.ADD, newValue: 9 },
{ id: '8', type: TransactionType.UPDATE, newValue: 10 }
];
expect(trans['_transactions'].length).toEqual(0);
expect(trans['_redoStack'].length).toEqual(0);
let transactionIndex = 1;
transactions.forEach((transaction) => {
trans.add(transaction);
expect(trans.getTransactionLog(transaction.id).pop()).toEqual(transaction);
expect(trans['_transactions'].length).toEqual(transactionIndex);
expect(trans['_redoStack'].length).toEqual(0);
transactionIndex++;
});
});
it('Should throw an error when trying to add duplicate transaction', () => {
const trans = new IgxTransactionService();
const transactions: Transaction[] = [
{ id: '1', type: TransactionType.ADD, newValue: 1 },
{ id: '2', type: TransactionType.ADD, newValue: 2 },
{ id: '3', type: TransactionType.ADD, newValue: 3 },
{ id: '1', type: TransactionType.UPDATE, newValue: 4 },
{ id: '5', type: TransactionType.ADD, newValue: 5 },
{ id: '6', type: TransactionType.ADD, newValue: 6 },
{ id: '2', type: TransactionType.DELETE, newValue: 7 },
{ id: '8', type: TransactionType.ADD, newValue: 8 },
{ id: '9', type: TransactionType.ADD, newValue: 9 },
{ id: '8', type: TransactionType.UPDATE, newValue: 10 }
];
transactions.forEach(t => trans.add(t));
const transaction = { id: '6', type: TransactionType.ADD, newValue: 6 };
expect(trans.getTransactionLog('6').pop()).toEqual(transaction);
const msg = `Cannot add this transaction. Transaction with id: ${transaction.id} has been already added.`;
expect(() => trans.add(transaction)).toThrowError(msg);
});
it('Should throw an error when trying to update transaction with no recordRef', () => {
const trans = new IgxTransactionService();
const transactions: Transaction[] = [
{ id: '1', type: TransactionType.ADD, newValue: 1 },
{ id: '2', type: TransactionType.ADD, newValue: 2 },
{ id: '3', type: TransactionType.ADD, newValue: 3 },
{ id: '1', type: TransactionType.UPDATE, newValue: 4 },
{ id: '5', type: TransactionType.ADD, newValue: 5 },
{ id: '6', type: TransactionType.ADD, newValue: 6 },
{ id: '2', type: TransactionType.DELETE, newValue: 7 },
{ id: '8', type: TransactionType.ADD, newValue: 8 },
{ id: '9', type: TransactionType.ADD, newValue: 9 },
{ id: '8', type: TransactionType.UPDATE, newValue: 10 }
];
transactions.forEach(transaction => trans.add(transaction));
const updateTransaction = { id: '2', type: TransactionType.DELETE, newValue: 7 };
expect(trans.getTransactionLog('2').pop()).toEqual(updateTransaction);
const msg = `Cannot add this transaction. This is first transaction of type ${updateTransaction.type} ` +
`for id ${updateTransaction.id}. For first transaction of this type recordRef is mandatory.`;
expect(() => {
updateTransaction.newValue = 107;
trans.add(updateTransaction);
}).toThrowError(msg);
});
it('Should throw an error when trying to delete an already deleted item', () => {
const trans = new IgxTransactionService();
const recordRef = { key: 'Key1', value: 1 };
const deleteTransaction: Transaction = { id: 'Key1', type: TransactionType.DELETE, newValue: null };
trans.add(deleteTransaction, recordRef);
expect(trans.getTransactionLog('Key1').pop()).toEqual(deleteTransaction);
const msg = `Cannot add this transaction. Transaction with id: ${deleteTransaction.id} has been already deleted.`;
expect(() => trans.add(deleteTransaction)).toThrowError(msg);
});
it('Should throw an error when trying to update an already deleted item', () => {
const trans = new IgxTransactionService();
const recordRef = { key: 'Key1', value: 1 };
const deleteTransaction: Transaction = { id: 'Key1', type: TransactionType.DELETE, newValue: null };
trans.add(deleteTransaction, recordRef);
expect(trans.getTransactionLog('Key1').pop()).toEqual(deleteTransaction);
const msg = `Cannot add this transaction. Transaction with id: ${deleteTransaction.id} has been already deleted.`;
expect(() => {
deleteTransaction.type = TransactionType.UPDATE;
deleteTransaction.newValue = 5;
trans.add(deleteTransaction);
}).toThrowError(msg);
});
it('Should get a transaction by transaction id', () => {
const trans = new IgxTransactionService();
let transaction: Transaction = { id: '0', type: TransactionType.ADD, newValue: 0 };
trans.add(transaction);
expect(trans.getTransactionLog('0').pop()).toEqual(transaction);
transaction = { id: '1', type: TransactionType.ADD, newValue: 1 };
trans.add(transaction);
expect(trans.getTransactionLog('1').pop()).toEqual(transaction);
transaction = { id: '2', type: TransactionType.ADD, newValue: 2 };
trans.add(transaction);
expect(trans.getTransactionLog('2').pop()).toEqual(transaction);
transaction = { id: '3', type: TransactionType.ADD, newValue: 3 };
trans.add(transaction);
expect(trans.getTransactionLog('3').pop()).toEqual(transaction);
transaction = { id: '1', type: TransactionType.UPDATE, newValue: 4 };
trans.add(transaction);
expect(trans.getTransactionLog('1').pop()).toEqual(transaction);
transaction = { id: '5', type: TransactionType.ADD, newValue: 5 };
trans.add(transaction);
expect(trans.getTransactionLog('5').pop()).toEqual(transaction);
transaction = { id: '6', type: TransactionType.ADD, newValue: 6 };
trans.add(transaction);
expect(trans.getTransactionLog('6').pop()).toEqual(transaction);
transaction = { id: '2', type: TransactionType.DELETE, newValue: 7 };
trans.add(transaction);
expect(trans.getTransactionLog('2').pop()).toEqual(transaction);
transaction = { id: '8', type: TransactionType.ADD, newValue: 8 };
trans.add(transaction);
expect(trans.getTransactionLog('8').pop()).toEqual(transaction);
transaction = { id: '9', type: TransactionType.ADD, newValue: 9 };
trans.add(transaction);
expect(trans.getTransactionLog('9').pop()).toEqual(transaction);
transaction = { id: '8', type: TransactionType.UPDATE, newValue: 10 };
trans.add(transaction);
expect(trans.getTransactionLog('8').pop()).toEqual(transaction);
// Get nonexisting transaction
expect(trans.getTransactionLog('100').pop()).toEqual(undefined);
});
it('Should add ADD type transaction - all feasible paths, and correctly fires onStateUpdate', () => {
const trans = new IgxTransactionService();
spyOn(trans.onStateUpdate, 'emit').and.callThrough();
expect(trans).toBeDefined();
// ADD
const addTransaction: Transaction = { id: 0, type: TransactionType.ADD, newValue: 1 };
trans.add(addTransaction);
expect(trans.getAggregatedValue(0, true)).toEqual(1);
expect(trans.getTransactionLog(0).pop()).toEqual(addTransaction);
expect(trans.getTransactionLog()).toEqual([addTransaction]);
expect(trans.getState(addTransaction.id)).toEqual({
value: addTransaction.newValue,
recordRef: undefined,
type: addTransaction.type
});
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(1);
trans.clear();
expect(trans.getState(0)).toBeUndefined();
expect(trans.getAggregatedValue(0, true)).toBeNull();
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(2);
// ADD -> Undo
trans.add(addTransaction);
trans.undo();
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(4);
trans.clear();
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(5);
// ADD -> Undo -> Redo
trans.add(addTransaction);
trans.undo();
trans.redo();
expect(trans.getTransactionLog()).toEqual([addTransaction]);
expect(trans.getState(addTransaction.id)).toEqual({
value: addTransaction.newValue,
recordRef: undefined,
type: addTransaction.type
});
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(8);
trans.clear();
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(9);
// ADD -> DELETE
trans.add(addTransaction);
const deleteTransaction: Transaction = { id: 0, type: TransactionType.DELETE, newValue: 1 };
trans.add(deleteTransaction);
expect(trans.getTransactionLog()).toEqual([addTransaction, deleteTransaction]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(11);
trans.clear();
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(12);
// ADD -> DELETE -> Undo
trans.add(addTransaction);
trans.add(deleteTransaction);
trans.undo();
expect(trans.getTransactionLog()).toEqual([addTransaction]);
expect(trans.getState(addTransaction.id)).toEqual({
value: addTransaction.newValue,
recordRef: undefined,
type: addTransaction.type
});
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(15);
trans.clear();
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(16);
// ADD -> DELETE -> Undo -> Redo
trans.add(addTransaction);
trans.add(deleteTransaction);
trans.undo();
trans.redo();
expect(trans.getTransactionLog()).toEqual([addTransaction, deleteTransaction]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(20);
trans.clear();
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(21);
// ADD -> DELETE -> Undo -> Undo
trans.add(addTransaction);
trans.add(deleteTransaction);
trans.undo();
trans.undo();
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(25);
trans.clear();
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(26);
// ADD -> UPDATE
trans.add(addTransaction);
const updateTransaction: Transaction = { id: 0, type: TransactionType.UPDATE, newValue: 2 };
trans.add(updateTransaction);
expect(trans.getTransactionLog()).toEqual([addTransaction, updateTransaction]);
expect(trans.getState(addTransaction.id)).toEqual({
value: updateTransaction.newValue,
recordRef: undefined,
type: addTransaction.type
});
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(28);
trans.clear();
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(29);
// ADD -> UPDATE -> Undo
trans.add(addTransaction);
trans.add(updateTransaction);
trans.undo();
expect(trans.getTransactionLog()).toEqual([addTransaction]);
expect(trans.getState(addTransaction.id)).toEqual({
value: addTransaction.newValue,
recordRef: undefined,
type: addTransaction.type
});
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(32);
trans.clear();
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(33);
// ADD -> UPDATE -> Undo -> Redo
trans.add(addTransaction);
trans.add(updateTransaction);
trans.undo();
trans.redo();
expect(trans.getTransactionLog()).toEqual([addTransaction, updateTransaction]);
expect(trans.getState(addTransaction.id)).toEqual({
value: updateTransaction.newValue,
recordRef: undefined,
type: addTransaction.type
});
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(37);
trans.clear();
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(38);
});
it('Should add DELETE type transaction - all feasible paths', () => {
const trans = new IgxTransactionService();
expect(trans).toBeDefined();
// DELETE
const recordRef = { key: 'Key1', value: 1 };
const deleteTransaction: Transaction = { id: 'Key1', type: TransactionType.DELETE, newValue: null };
trans.add(deleteTransaction, recordRef);
expect(trans.getTransactionLog('Key1').pop()).toEqual(deleteTransaction);
expect(trans.getTransactionLog()).toEqual([deleteTransaction]);
expect(trans.getState(deleteTransaction.id)).toEqual({
value: null,
recordRef,
type: deleteTransaction.type
});
trans.clear();
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
// DELETE -> Undo
trans.add(deleteTransaction, recordRef);
trans.undo();
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
trans.clear();
// DELETE -> Undo -> Redo
trans.add(deleteTransaction, recordRef);
trans.undo();
trans.redo();
expect(trans.getTransactionLog('Key1').pop()).toEqual(deleteTransaction);
expect(trans.getTransactionLog()).toEqual([deleteTransaction]);
expect(trans.getState(deleteTransaction.id)).toEqual({
value: null,
recordRef,
type: deleteTransaction.type
});
trans.clear();
});
it('Should add UPDATE type transaction - all feasible paths', () => {
const trans = new IgxTransactionService();
expect(trans).toBeDefined();
// UPDATE
const recordRef = { key: 'Key1', value: 1 };
const newValue = { key: 'Key1', value: 2 };
const updateTransaction: Transaction = { id: 'Key1', type: TransactionType.UPDATE, newValue };
trans.add(updateTransaction, recordRef);
expect(trans.getState('Key1')).toBeTruthy();
expect(trans.getAggregatedValue('Key1', true)).toEqual(newValue);
expect(trans.getTransactionLog('Key1').pop()).toEqual(updateTransaction);
expect(trans.getTransactionLog()).toEqual([updateTransaction]);
expect(trans.getState(updateTransaction.id)).toEqual({
value: { value: 2 },
recordRef,
type: updateTransaction.type
});
trans.clear();
expect(trans.getState('Key1')).toBeFalsy();
expect(trans.getAggregatedValue('Key1', true)).toBeNull();
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
// UPDATE -> Undo
trans.add(updateTransaction, recordRef);
trans.undo();
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
trans.clear();
// UPDATE -> Undo -> Redo
trans.add(updateTransaction, recordRef);
trans.undo();
trans.redo();
expect(trans.getTransactionLog('Key1').pop()).toEqual(updateTransaction);
expect(trans.getTransactionLog()).toEqual([updateTransaction]);
expect(trans.getState(updateTransaction.id)).toEqual({
value: { value: 2 },
recordRef,
type: updateTransaction.type
});
trans.clear();
// UPDATE -> UPDATE
trans.add(updateTransaction, recordRef);
const newValue2 = { key: 'Key1', value: 3 };
const updateTransaction2: Transaction = { id: 'Key1', type: TransactionType.UPDATE, newValue: newValue2 };
trans.add(updateTransaction2, recordRef);
expect(trans.getTransactionLog('Key1').pop()).toEqual(updateTransaction2);
expect(trans.getTransactionLog()).toEqual([updateTransaction, updateTransaction2]);
expect(trans.getState(updateTransaction.id)).toEqual({
value: { value: 3 },
recordRef,
type: updateTransaction2.type
});
trans.clear();
// UPDATE -> UPDATE (to initial recordRef)
trans.add(updateTransaction, recordRef);
const asRecordRefTransaction: Transaction = { id: 'Key1', type: TransactionType.UPDATE, newValue: recordRef };
trans.add(asRecordRefTransaction, recordRef);
expect(trans.getTransactionLog('Key1').pop()).toEqual(asRecordRefTransaction);
expect(trans.getTransactionLog()).toEqual([updateTransaction, asRecordRefTransaction]);
expect(trans.getState(updateTransaction.id)).toBeUndefined();
expect(trans.getAggregatedChanges(false)).toEqual([]);
trans.clear();
// UPDATE -> UPDATE -> Undo
trans.add(updateTransaction, recordRef);
trans.add(updateTransaction2, recordRef);
trans.undo();
expect(trans.getTransactionLog('Key1').pop()).toEqual(updateTransaction);
expect(trans.getTransactionLog()).toEqual([updateTransaction]);
expect(trans.getState(updateTransaction.id)).toEqual({
value: { value: 2 },
recordRef,
type: updateTransaction.type
});
trans.clear();
// UPDATE -> UPDATE -> Undo -> Redo
trans.add(updateTransaction, recordRef);
trans.add(updateTransaction2, recordRef);
trans.undo();
trans.redo();
expect(trans.getTransactionLog('Key1').pop()).toEqual(updateTransaction2);
expect(trans.getTransactionLog()).toEqual([updateTransaction, updateTransaction2]);
expect(trans.getState(updateTransaction.id)).toEqual({
value: { value: 3 },
recordRef,
type: updateTransaction2.type
});
trans.clear();
// UPDATE -> DELETE
trans.add(updateTransaction, recordRef);
const deleteTransaction: Transaction = { id: 'Key1', type: TransactionType.DELETE, newValue: null };
trans.add(deleteTransaction);
expect(trans.getTransactionLog('Key1').pop()).toEqual(deleteTransaction);
expect(trans.getTransactionLog()).toEqual([updateTransaction, deleteTransaction]);
expect(trans.getState(deleteTransaction.id)).toEqual({
value: deleteTransaction.newValue,
recordRef,
type: deleteTransaction.type
});
trans.clear();
// UPDATE -> DELETE -> Undo
trans.add(updateTransaction, recordRef);
trans.add(deleteTransaction);
trans.undo();
expect(trans.getTransactionLog('Key1').pop()).toEqual(updateTransaction);
expect(trans.getTransactionLog()).toEqual([updateTransaction]);
expect(trans.getState(updateTransaction.id)).toEqual({
value: { value: 2 },
recordRef,
type: updateTransaction.type
});
trans.clear();
// UPDATE -> DELETE -> Undo -> Redo
trans.add(updateTransaction, recordRef);
trans.add(deleteTransaction);
trans.undo();
trans.redo();
expect(trans.getTransactionLog('Key1').pop()).toEqual(deleteTransaction);
expect(trans.getTransactionLog()).toEqual([updateTransaction, deleteTransaction]);
expect(trans.getState(deleteTransaction.id)).toEqual({
value: deleteTransaction.newValue,
recordRef,
type: deleteTransaction.type
});
trans.clear();
});
it('Should properly confirm the length of the undo/redo stacks', () => {
const transaction = new IgxTransactionService();
expect(transaction).toBeDefined();
// Stacks are clear by default
expect(transaction.canRedo).toBeFalsy();
expect(transaction.canUndo).toBeFalsy();
let addItem: Transaction = { id: 1, type: TransactionType.ADD, newValue: { Category: 'Something' } };
transaction.add(addItem);
expect(transaction.canRedo).toBeFalsy();
expect(transaction.canUndo).toBeTruthy();
addItem = { id: 2, type: TransactionType.ADD, newValue: { Category: 'Something 2' } };
transaction.add(addItem);
expect(transaction.canRedo).toBeFalsy();
expect(transaction.canUndo).toBeTruthy();
transaction.undo();
expect(transaction.canRedo).toBeTruthy();
expect(transaction.canUndo).toBeTruthy();
transaction.undo();
expect(transaction.canRedo).toBeTruthy();
expect(transaction.canUndo).toBeFalsy();
transaction.redo();
expect(transaction.canRedo).toBeTruthy();
expect(transaction.canUndo).toBeTruthy();
transaction.redo();
expect(transaction.canRedo).toBeFalsy();
expect(transaction.canUndo).toBeTruthy();
});
it('Should update data when data is list of objects', () => {
const originalData = SampleTestData.generateProductData(50);
const trans = new IgxTransactionService();
expect(trans).toBeDefined();
const item0Update1: Transaction = { id: 1, type: TransactionType.UPDATE, newValue: { Category: 'Some new value' } };
trans.add(item0Update1, originalData[1]);
const item10Delete: Transaction = { id: 10, type: TransactionType.DELETE, newValue: null };
trans.add(item10Delete, originalData[10]);
const newItem1: Transaction = {
id: 'add1', type: TransactionType.ADD, newValue: {
ID: undefined,
Category: 'Category Added',
Downloads: 100,
Items: 'Items Added',
ProductName: 'ProductName Added',
ReleaseDate: new Date(),
Released: true,
Test: 'test Added'
}
};
trans.add(newItem1, undefined);
trans.commit(originalData);
expect(originalData.find(i => i.ID === 1).Category).toBe('Some new value');
expect(originalData.find(i => i.ID === 10)).toBeUndefined();
expect(originalData.length).toBe(50);
expect(originalData[49]).toEqual(newItem1.newValue);
});
it('Should update data for provided id when data is list of objects', () => {
const originalData = SampleTestData.generateProductData(50);
const trans = new IgxTransactionService();
expect(trans).toBeDefined();
const item0Update1: Transaction = { id: 0, type: TransactionType.UPDATE, newValue: { Category: 'Some new value' } };
trans.add(item0Update1, originalData[1]);
const item10Delete: Transaction = { id: 10, type: TransactionType.DELETE, newValue: null };
trans.add(item10Delete, originalData[10]);
const newItem1: Transaction = {
id: 'add1', type: TransactionType.ADD, newValue: {
ID: undefined,
Category: 'Category Added',
Downloads: 100,
Items: 'Items Added',
ProductName: 'ProductName Added',
ReleaseDate: new Date(),
Released: true,
Test: 'test Added'
}
};
trans.add(newItem1, undefined);
trans.commit(originalData, 10);
expect(originalData.find(i => i.ID === 1).Category).toBe('Category1');
expect(originalData.find(i => i.ID === 10)).toBeUndefined();
expect(originalData.length).toBe(49);
trans.commit(originalData, 'FAKE ID');
expect(originalData.find(i => i.ID === 1).Category).toBe('Category1');
expect(originalData.find(i => i.ID === 10)).toBeUndefined();
expect(originalData.length).toBe(49);
trans.commit(originalData, 20);
expect(originalData.find(i => i.ID === 1).Category).toBe('Category1');
expect(originalData.find(i => i.ID === 10)).toBeUndefined();
expect(originalData.length).toBe(49);
trans.commit(originalData, 0);
expect(originalData.find(i => i.ID === 1).Category).toBe('Some new value');
expect(originalData.find(i => i.ID === 10)).toBeUndefined();
expect(originalData.length).toBe(49);
trans.commit(originalData, 'add1');
expect(originalData.find(i => i.ID === 1).Category).toBe('Some new value');
expect(originalData.find(i => i.ID === 10)).toBeUndefined();
expect(originalData.length).toBe(50);
expect(originalData[49]).toEqual(newItem1.newValue);
});
it('Should update data when data is list of primitives', () => {
const originalData = SampleTestData.generateListOfPrimitiveValues(50, 'String');
const trans = new IgxTransactionService();
expect(trans).toBeDefined();
const item0Update1: Transaction = { id: 1, type: TransactionType.UPDATE, newValue: 'Updated Row' };
trans.add(item0Update1, originalData[1]);
const item10Delete: Transaction = { id: 10, type: TransactionType.DELETE, newValue: null };
trans.add(item10Delete, originalData[10]);
const newItem1: Transaction = {
id: 'add1', type: TransactionType.ADD, newValue: 'Added Row'
};
trans.add(newItem1, undefined);
trans.commit(originalData);
expect(originalData[1]).toBe('Updated Row');
expect(originalData.find(i => i === 'Row 10')).toBeUndefined();
expect(originalData.length).toBe(50);
expect(originalData[49]).toEqual('Added Row');
});
it('Should update data for provided id when data is list of primitives', () => {
const originalData = SampleTestData.generateListOfPrimitiveValues(50, 'String');
const trans = new IgxTransactionService();
expect(trans).toBeDefined();
const item0Update1: Transaction = { id: 1, type: TransactionType.UPDATE, newValue: 'Updated Row' };
trans.add(item0Update1, originalData[1]);
const item10Delete: Transaction = { id: 10, type: TransactionType.DELETE, newValue: null };
trans.add(item10Delete, originalData[10]);
const newItem1: Transaction = {
id: 'add1', type: TransactionType.ADD, newValue: 'Added Row'
};
trans.add(newItem1, undefined);
trans.commit(originalData, 10);
expect(originalData[1]).toBe('Row 1');
expect(originalData.find(i => i.id === 'Row 10')).toBeUndefined();
expect(originalData.length).toBe(49);
trans.commit(originalData, 'FAKE ID');
expect(originalData[1]).toBe('Row 1');
expect(originalData.find(i => i.id === 'Row 10')).toBeUndefined();
expect(originalData.length).toBe(49);
trans.commit(originalData, 20);
expect(originalData[1]).toBe('Row 1');
expect(originalData.find(i => i.id === 'Row 10')).toBeUndefined();
expect(originalData.length).toBe(49);
trans.commit(originalData, 1);
expect(originalData[1]).toBe('Updated Row');
expect(originalData.find(i => i.id === 'Row 10')).toBeUndefined();
expect(originalData.length).toBe(49);
trans.commit(originalData, 'add1');
expect(originalData[1]).toBe('Updated Row');
expect(originalData.find(i => i.id === 'Row 10')).toBeUndefined();
expect(originalData.length).toBe(50);
expect(originalData[49]).toEqual(newItem1.newValue);
});
it('Should add pending transaction and push it to transaction log, and correctly fires onStateUpdate', () => {
const trans = new IgxTransactionService();
spyOn(trans.onStateUpdate, 'emit').and.callThrough();
expect(trans).toBeDefined();
const recordRef = { key: 'Key1', value1: 1, value2: 2, value3: 3 };
let newValue: any = { key: 'Key1', value1: 10 };
let updateTransaction: Transaction = { id: 'Key1', type: TransactionType.UPDATE, newValue };
trans.startPending();
trans.add(updateTransaction, recordRef);
expect(trans.getState('Key1')).toBeUndefined();
expect(trans.getAggregatedValue('Key1', true)).toEqual({ key: 'Key1', value1: 10, value2: 2, value3: 3 });
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
newValue = { key: 'Key1', value3: 30 };
updateTransaction = { id: 'Key1', type: TransactionType.UPDATE, newValue };
trans.add(updateTransaction, recordRef);
expect(trans.getState('Key1')).toBeUndefined();
expect(trans.getAggregatedValue('Key1', true)).toEqual({ key: 'Key1', value1: 10, value2: 2, value3: 30 });
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
trans.endPending(true);
expect(trans.getState('Key1')).toBeTruthy();
expect(trans.getAggregatedValue('Key1', true)).toEqual({ key: 'Key1', value1: 10, value2: 2, value3: 30 });
expect(trans.getTransactionLog() as any).toEqual(
[
{
id: 'Key1',
newValue: { key: 'Key1', value1: 10 },
type: 'update'
}, {
id: 'Key1',
newValue: { key: 'Key1', value3: 30 },
type: 'update'
}
]);
expect(trans.getState(updateTransaction.id)).toEqual({
value: { value1: 10, value3: 30 },
recordRef,
type: updateTransaction.type
});
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(1);
});
it('Should not add pending transaction and push it to transaction log, and correctly fires onStateUpdate', () => {
const trans = new IgxTransactionService();
spyOn(trans.onStateUpdate, 'emit').and.callThrough();
expect(trans).toBeDefined();
const recordRef = { key: 'Key1', value1: 1, value2: 2, value3: 3 };
let newValue: any = { key: 'Key1', value1: 10 };
let updateTransaction: Transaction = { id: 'Key1', type: TransactionType.UPDATE, newValue };
trans.startPending();
trans.add(updateTransaction, recordRef);
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
newValue = { key: 'Key1', value3: 30 };
updateTransaction = { id: 'Key1', type: TransactionType.UPDATE, newValue };
trans.add(updateTransaction, recordRef);
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
trans.endPending(false);
expect(trans.getTransactionLog()).toEqual([]);
expect(trans.getAggregatedChanges(true)).toEqual([]);
expect(trans.onStateUpdate.emit).toHaveBeenCalledTimes(0);
});
it('Should not generate changes when updating a value to the original one', () => {
const originalData = SampleTestData.generateProductData(50);
const transaction = new IgxTransactionService();
expect(transaction).toBeDefined();
transaction.startPending();
const itemUpdate1: Transaction = { id: 1, type: TransactionType.UPDATE, newValue: { Category: 'Some new value' } };
transaction.add(itemUpdate1, originalData[1]);
expect(transaction.getState(1, true)).toBeTruthy();
expect(transaction.getAggregatedValue(1, false)).toEqual({ Category: 'Some new value' });
// update to original value
const itemUpdate2: Transaction = { id: 1, type: TransactionType.UPDATE, newValue: { Category: originalData[1].Category } };
transaction.add(itemUpdate2, originalData[1]);
expect(transaction.getState(1, true)).toBeUndefined();
expect(transaction.getAggregatedValue(1, false)).toBeNull();
transaction.endPending(false);
expect(transaction.getTransactionLog()).toEqual([]);
expect(transaction.getAggregatedChanges(true)).toEqual([]);
});
it('Should clear transactions for provided id', () => {
const originalData = SampleTestData.generateProductData(50);
const trans = new IgxTransactionService();
expect(trans).toBeDefined();
let transaction: Transaction = { id: 1, type: TransactionType.UPDATE, newValue: { Category: 'Some new value' } };
trans.add(transaction, originalData[1]);
transaction = { id: 2, type: TransactionType.UPDATE, newValue: { Category: 'Some new value' } };
trans.add(transaction, originalData[2]);
transaction = { id: 2, type: TransactionType.UPDATE, newValue: { Items: 'Some new value' } };
trans.add(transaction, originalData[2]);
transaction = { id: 1, type: TransactionType.UPDATE, newValue: { Category: 'Some very new value' } };
trans.add(transaction, originalData[1]);
transaction = { id: 10, type: TransactionType.UPDATE, newValue: { Category: 'Some new value' } };
trans.add(transaction, originalData[10]);
expect(trans.getTransactionLog().length).toBe(5);
expect(trans.getAggregatedChanges(true).length).toBe(3);
expect(trans.canUndo).toBeTruthy();
expect(trans.canRedo).toBeFalsy();
trans.clear(1);
expect(trans.getTransactionLog().length).toBe(3);
expect(trans.getAggregatedChanges(true).length).toBe(2);
expect(trans.canUndo).toBeTruthy();
expect(trans.canRedo).toBeFalsy();
trans.clear('FAKE ID');
expect(trans.getTransactionLog().length).toBe(3);
expect(trans.getAggregatedChanges(true).length).toBe(2);
expect(trans.canUndo).toBeTruthy();
expect(trans.canRedo).toBeFalsy();
trans.clear(20);
expect(trans.getTransactionLog().length).toBe(3);
expect(trans.getAggregatedChanges(true).length).toBe(2);
expect(trans.canUndo).toBeTruthy();
expect(trans.canRedo).toBeFalsy();
trans.clear(10);
expect(trans.getTransactionLog().length).toBe(2);
expect(trans.getAggregatedChanges(true).length).toBe(1);
expect(trans.canUndo).toBeTruthy();
expect(trans.canRedo).toBeFalsy();
});
});
describe('IgxHierarchicalTransaction UNIT Test', () => {
it('Should set path for each state when transaction is added in Hierarchical data source', () => {
const transaction = new IgxHierarchicalTransactionService();
expect(transaction).toBeDefined();
const path: any[] = ['P1', 'P2'];
const addTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.ADD, newValue: 'Add row', path };
transaction.add(addTransaction);
expect(transaction.getState(1).path).toBeDefined();
expect(transaction.getState(1).path.length).toBe(2);
expect(transaction.getState(1).path).toEqual(path);
path.push('P3');
const updateTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.UPDATE, newValue: 'Updated row', path };
transaction.add(updateTransaction, 'Update row');
expect(transaction.getState(1).path.length).toBe(3);
expect(transaction.getState(1).path).toEqual(path);
});
it('Should remove added transaction from states when deleted in Hierarchical data source', () => {
const transaction = new IgxHierarchicalTransactionService();
expect(transaction).toBeDefined();
const path: any[] = [];
let addTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.ADD, newValue: 'Parent row', path };
transaction.add(addTransaction);
expect(transaction.getState(1).path).toBeDefined();
expect(transaction.getState(1).path.length).toBe(0);
expect(transaction.getState(1).path).toEqual(path);
path.push(addTransaction.id);
addTransaction = { id: 2, type: TransactionType.ADD, newValue: 'Child row', path };
transaction.add(addTransaction);
expect(transaction.getState(2).path).toBeDefined();
expect(transaction.getState(2).path.length).toBe(1);
expect(transaction.getState(2).path).toEqual(path);
const deleteTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.DELETE, newValue: null, path: [] };
transaction.add(deleteTransaction);
expect(transaction.getState(1)).toBeUndefined();
expect(transaction.getState(2)).toBeUndefined();
});
it('Should mark update transactions state as deleted type when deleted in Hierarchical data source', () => {
const transaction = new IgxHierarchicalTransactionService();
expect(transaction).toBeDefined();
const path: any[] = [];
let updateTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.UPDATE, newValue: 'Parent row', path };
transaction.add(updateTransaction, 'Original value');
expect(transaction.getState(1).path).toBeDefined();
expect(transaction.getState(1).path.length).toBe(0);
expect(transaction.getState(1).path).toEqual(path);
path.push(updateTransaction.id);
updateTransaction = { id: 2, type: TransactionType.UPDATE, newValue: 'Child row', path };
transaction.add(updateTransaction, 'Original Value');
expect(transaction.getState(2).path).toBeDefined();
expect(transaction.getState(2).path.length).toBe(1);
expect(transaction.getState(2).path).toEqual(path);
const deleteTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.DELETE, newValue: null, path: [] };
transaction.add(deleteTransaction);
expect(transaction.getState(1)).toBeDefined();
expect(transaction.getState(1).type).toBe(TransactionType.DELETE);
expect(transaction.getState(2)).toBeDefined();
expect(transaction.getState(2).type).toBe(TransactionType.DELETE);
});
it('Should correctly call getAggregatedChanges without commit when recordRef is null', () => {
const transaction = new IgxHierarchicalTransactionService();
expect(transaction).toBeDefined();
const deleteTransaction: HierarchicalTransaction = { id: 0, type: TransactionType.DELETE, newValue: null, path: [] };
transaction.add(deleteTransaction, 'Deleted row');
expect(transaction.getAggregatedChanges(false)).toEqual([deleteTransaction]);
});
it('Should update data for provided id', () => {
const data = SampleTestData.employeeTreeData();
const transaction = new IgxHierarchicalTransactionService();
expect(transaction).toBeDefined();
const addTransaction: HierarchicalTransaction = {
id: 0,
type: TransactionType.ADD,
newValue: {
ID: 999,
Name: 'Root Add Transaction',
HireDate: new Date(2018, 3, 20),
Age: 45,
OnPTO: false,
Employees: []
},
path: null
};
transaction.add(addTransaction);
const updateTransaction: HierarchicalTransaction = {
id: 475,
type: TransactionType.UPDATE,
newValue: {
Age: 60
},
path: [data[0].ID]
};
transaction.add(updateTransaction, data[0].Employees[0]);
const deleteTransaction: HierarchicalTransaction = {
id: 711,
type: TransactionType.DELETE,
newValue: {},
path: [data[0].ID, data[0].Employees[2].ID]
};
transaction.add(deleteTransaction, data[0].Employees[2].Employees[0]);
updateTransaction.newValue = { Name: 'New Name'};
transaction.add(updateTransaction, data[0].Employees[0]);
expect(data.find(i => i.ID === 999)).toBeUndefined();
expect(data.length).toBe(4);
transaction.commit(data, 'ID', 'Employees', 0);
expect(data.find(i => i.ID === 999)).toBeDefined();
expect(data.find(i => i.ID === 999).Name).toBe('Root Add Transaction');
expect(data.length).toBe(5);
expect(transaction.canUndo).toBeTruthy();
expect(transaction.getAggregatedChanges(false).length).toBe(2);
expect(data[0].Employees[0].Age).toBe(43);
expect(data[0].Employees[0].Name).toBe('Michael Langdon');
transaction.commit(data, 'ID', 'Employees', 475);
expect(data[0].Employees[0].Age).toBe(60);
expect(data[0].Employees[0].Name).toBe('New Name');
expect(transaction.canUndo).toBeTruthy();
expect(transaction.getAggregatedChanges(false).length).toBe(1);
expect(data[0].Employees[2].Employees.length).toBe(2);
transaction.commit(data, 'ID', 'Employees', 711);
expect(data[0].Employees[2].Employees.length).toBe(1);
expect(transaction.canUndo).toBeFalsy();
expect(transaction.getAggregatedChanges(false).length).toBe(0);
});
it('Should not generate changes when updating a value to the original one', () => {
const originalData = SampleTestData.employeeTreeData();
const transaction = new IgxHierarchicalTransactionService();
expect(transaction).toBeDefined();
transaction.startPending();
// root record update
const rootUpdate1: HierarchicalTransaction = {
id: 147,
type: TransactionType.UPDATE,
newValue: {
Name: 'New Name'
},
path: null
};
transaction.add(rootUpdate1, originalData[0]);
expect(transaction.getState(147, true)).toBeTruthy();
expect(transaction.getAggregatedValue(147, false)).toEqual({ Name: 'New Name' });
// update to original value
const rootUpdate2: HierarchicalTransaction = {
id: 147,
type: TransactionType.UPDATE,
newValue: {
Name: originalData[0].Name
},
path: null
};
transaction.add(rootUpdate2, originalData[0]);
expect(transaction.getState(147, true)).toBeUndefined();
expect(transaction.getAggregatedValue(147, false)).toBeNull();
// child record update
const childUpdate1: HierarchicalTransaction = {
id: 475,
type: TransactionType.UPDATE,
newValue: {
Age: 60
},
path: [originalData[0].ID]
};
transaction.add(childUpdate1, originalData[0].Employees[0]);
expect(transaction.getState(475, true)).toBeTruthy();
expect(transaction.getAggregatedValue(475, false)).toEqual({ Age: 60 });
// update to original value
const childUpdate2: HierarchicalTransaction = {
id: 475,
type: TransactionType.UPDATE,
newValue: {
Age: originalData[0].Employees[0].Age
},
path: [originalData[0].ID]
};
transaction.add(childUpdate2, originalData[0].Employees[0]);
expect(transaction.getState(475, true)).toBeUndefined();
expect(transaction.getAggregatedValue(475, false)).toBeNull();
transaction.endPending(false);
expect(transaction.getTransactionLog()).toEqual([]);
expect(transaction.getAggregatedChanges(true)).toEqual([]);
});
it('Should emit onStateUpdate once when commiting a hierarchical transaction', () => {
const data = SampleTestData.employeeTreeData();
const transaction = new IgxHierarchicalTransactionService();
spyOn(transaction.onStateUpdate, 'emit').and.callThrough();
expect(transaction).toBeDefined();
const updateTransaction: HierarchicalTransaction = {
id: 475,
type: TransactionType.UPDATE,
newValue: {
Age: 60
},
path: [data[0].ID]
};
transaction.add(updateTransaction, data[0].Employees[0]);
expect(transaction.onStateUpdate.emit).toHaveBeenCalledTimes(1);
transaction.commit(data, 'ID');
expect(transaction.onStateUpdate.emit).toHaveBeenCalledTimes(2);
});
});
});