• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM UTC coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts
1
import { Transaction, State, TransactionType, TransactionEventOrigin, Action } from './transaction';
2
import { IgxBaseTransactionService } from './base-transaction';
3
import { isObject, mergeObjects } from '../../core/utils';
4

5
export class IgxTransactionService<T extends Transaction, S extends State> extends IgxBaseTransactionService<T, S> {
6

UNCOV
7
    protected _transactions: T[] = [];
×
UNCOV
8
    protected _redoStack: Action<T>[][] = [];
×
UNCOV
9
    protected _undoStack: Action<T>[][] = [];
×
UNCOV
10
    protected _states: Map<any, S> = new Map();
×
11

12
    /**
13
     * @returns if there are any transactions in the Undo stack
14
     */
15
    public override get canUndo(): boolean {
UNCOV
16
        return this._undoStack.length > 0;
×
17
    }
18

19
    /**
20
     * @returns if there are any transactions in the Redo stack
21
     */
22
    public override get canRedo(): boolean {
UNCOV
23
        return this._redoStack.length > 0;
×
24
    }
25

26
    /**
27
     * Adds provided  transaction with recordRef if any
28
     *
29
     * @param transaction Transaction to be added
30
     * @param recordRef Reference to the value of the record in the data source related to the changed item
31
     */
32
    public override add(transaction: T, recordRef?: any): void {
UNCOV
33
        const states = this._isPending ? this._pendingStates : this._states;
×
UNCOV
34
        this.verifyAddedTransaction(states, transaction, recordRef);
×
UNCOV
35
        this.addTransaction(transaction, states, recordRef);
×
36
    }
37

38
    /**
39
     * Returns all recorded transactions in chronological order
40
     *
41
     * @param id Optional record id to get transactions for
42
     * @returns All transaction in the service or for the specified record
43
     */
44
    public override getTransactionLog(id?: any): T[] {
UNCOV
45
        if (id !== undefined) {
×
UNCOV
46
            return this._transactions.filter(t => t.id === id);
×
47
        }
UNCOV
48
        return [...this._transactions];
×
49
    }
50

51
    /**
52
     * Returns aggregated changes from all transactions
53
     *
54
     * @param mergeChanges If set to true will merge each state's value over relate recordRef
55
     * and will record resulting value in the related transaction
56
     * @returns Collection of aggregated transactions for each changed record
57
     */
58
    public override getAggregatedChanges(mergeChanges: boolean): T[] {
UNCOV
59
        const result: T[] = [];
×
UNCOV
60
        this._states.forEach((state: S, key: any) => {
×
UNCOV
61
            const value = mergeChanges ? this.mergeValues(state.recordRef, state.value) : state.value;
×
UNCOV
62
            result.push({ id: key, newValue: value, type: state.type } as T);
×
63
        });
UNCOV
64
        return result;
×
65
    }
66

67
    /**
68
     * Returns the state of the record with provided id
69
     *
70
     * @param id The id of the record
71
     * @param pending Should get pending state
72
     * @returns State of the record if any
73
     */
74
    public override getState(id: any, pending = false): S {
×
UNCOV
75
        return pending ? this._pendingStates.get(id) : this._states.get(id);
×
76
    }
77

78
    /**
79
     * Returns whether transaction is enabled for this service
80
     */
81
    public override get enabled(): boolean {
UNCOV
82
        return true;
×
83
    }
84

85
    /**
86
     * Returns value of the required id including all uncommitted changes
87
     *
88
     * @param id The id of the record to return value for
89
     * @param mergeChanges If set to true will merge state's value over relate recordRef
90
     * and will return merged value
91
     * @returns Value with changes or **null**
92
     */
93
    public override getAggregatedValue(id: any, mergeChanges: boolean): any {
UNCOV
94
        const state = this._states.get(id);
×
UNCOV
95
        const pendingState = super.getState(id);
×
96

97
        //  if there is no state and there is no pending state return null
UNCOV
98
        if (!state && !pendingState) {
×
UNCOV
99
            return null;
×
100
        }
101

UNCOV
102
        const pendingChange = super.getAggregatedValue(id, false);
×
UNCOV
103
        const change = state && state.value;
×
UNCOV
104
        let aggregatedValue = this.mergeValues(change, pendingChange);
×
UNCOV
105
        if (mergeChanges) {
×
UNCOV
106
            const originalValue = state ? state.recordRef : pendingState.recordRef;
×
UNCOV
107
            aggregatedValue = this.mergeValues(originalValue, aggregatedValue);
×
108
        }
UNCOV
109
        return aggregatedValue;
×
110
    }
111

112
    /**
113
     * Clears all pending transactions and aggregated pending state. If commit is set to true
114
     * commits pending states as single transaction
115
     *
116
     * @param commit Should commit the pending states
117
     */
118
    public override endPending(commit: boolean): void {
UNCOV
119
        this._isPending = false;
×
UNCOV
120
        if (commit) {
×
UNCOV
121
            const actions: Action<T>[] = [];
×
122
            // don't use addTransaction due to custom undo handling
UNCOV
123
            for (const transaction of this._pendingTransactions) {
×
UNCOV
124
                const pendingState = this._pendingStates.get(transaction.id);
×
UNCOV
125
                this._transactions.push(transaction);
×
UNCOV
126
                this.updateState(this._states, transaction, pendingState.recordRef);
×
UNCOV
127
                actions.push({ transaction, recordRef: pendingState.recordRef });
×
128
            }
129

UNCOV
130
            this._undoStack.push(actions);
×
UNCOV
131
            this._redoStack = [];
×
132

UNCOV
133
            this.onStateUpdate.emit({ origin: TransactionEventOrigin.END, actions });
×
134
        }
UNCOV
135
        super.endPending(commit);
×
136
    }
137

138
    /**
139
     * Applies all transactions over the provided data
140
     *
141
     * @param data Data source to update
142
     * @param id Optional record id to commit transactions for
143
     */
144
    public override commit(data: any[], id?: any): void {
UNCOV
145
        if (id !== undefined) {
×
UNCOV
146
            const state = this.getState(id);
×
UNCOV
147
            if (state) {
×
UNCOV
148
                this.updateRecord(data, state);
×
149
            }
150
        } else {
UNCOV
151
            this._states.forEach((s: S) => {
×
UNCOV
152
                this.updateRecord(data, s);
×
153
            });
154
        }
UNCOV
155
        this.clear(id);
×
156
    }
157

158
    /**
159
     * Clears all transactions
160
     *
161
     * @param id Optional record id to clear transactions for
162
     */
163
    public override clear(id?: any): void {
UNCOV
164
        if (id !== undefined) {
×
UNCOV
165
            this._transactions = this._transactions.filter(t => t.id !== id);
×
UNCOV
166
            this._states.delete(id);
×
167
            //  Undo stack is an array of actions. Each action is array of transaction like objects
168
            //  We are going trough all the actions. For each action we are filtering out transactions
169
            //  with provided id. Finally if any action ends up as empty array we are removing it from
170
            //  undo stack
UNCOV
171
            this._undoStack = this._undoStack.map(a => a.filter(t => t.transaction.id !== id)).filter(a => a.length > 0);
×
172
        } else {
UNCOV
173
            this._transactions = [];
×
UNCOV
174
            this._states.clear();
×
UNCOV
175
            this._undoStack = [];
×
176
        }
UNCOV
177
        this._redoStack = [];
×
UNCOV
178
        this.onStateUpdate.emit({ origin: TransactionEventOrigin.CLEAR, actions: [] });
×
179
    }
180

181
    /**
182
     * Remove the last transaction if any
183
     */
184
    public override undo(): void {
UNCOV
185
        if (this._undoStack.length <= 0) {
×
186
            return;
×
187
        }
188

UNCOV
189
        const lastActions: Action<T>[] = this._undoStack.pop();
×
UNCOV
190
        this._transactions.splice(this._transactions.length - lastActions.length);
×
UNCOV
191
        this._redoStack.push(lastActions);
×
192

UNCOV
193
        this._states.clear();
×
UNCOV
194
        for (const currentActions of this._undoStack) {
×
UNCOV
195
            for (const transaction of currentActions) {
×
UNCOV
196
                this.updateState(this._states, transaction.transaction, transaction.recordRef);
×
197
            }
198
        }
199

UNCOV
200
        this.onStateUpdate.emit({ origin: TransactionEventOrigin.UNDO, actions: lastActions });
×
201
    }
202

203
    /**
204
     * Applies the last undone transaction if any
205
     */
206
    public override redo(): void {
UNCOV
207
        if (this._redoStack.length > 0) {
×
UNCOV
208
            const actions: Action<T>[] = this._redoStack.pop();
×
UNCOV
209
            for (const action of actions) {
×
UNCOV
210
                this.updateState(this._states, action.transaction, action.recordRef);
×
UNCOV
211
                this._transactions.push(action.transaction);
×
212
            }
213

UNCOV
214
            this._undoStack.push(actions);
×
UNCOV
215
            this.onStateUpdate.emit({ origin: TransactionEventOrigin.REDO, actions });
×
216
        }
217
    }
218

219
    protected addTransaction(transaction: T, states: Map<any, S>, recordRef?: any) {
UNCOV
220
        this.updateState(states, transaction, recordRef);
×
221

UNCOV
222
        const transactions = this._isPending ? this._pendingTransactions : this._transactions;
×
UNCOV
223
        transactions.push(transaction);
×
224

UNCOV
225
        if (!this._isPending) {
×
UNCOV
226
            const actions = [{ transaction, recordRef }];
×
UNCOV
227
            this._undoStack.push(actions);
×
UNCOV
228
            this._redoStack = [];
×
UNCOV
229
            this.onStateUpdate.emit({ origin: TransactionEventOrigin.ADD, actions });
×
230
        }
231
    }
232

233
    /**
234
     * Verifies if the passed transaction is correct. If not throws an exception.
235
     *
236
     * @param transaction Transaction to be verified
237
     */
238
    protected verifyAddedTransaction(states: Map<any, S>, transaction: T, recordRef?: any): void {
UNCOV
239
        const state = states.get(transaction.id);
×
UNCOV
240
        switch (transaction.type) {
×
241
            case TransactionType.ADD:
UNCOV
242
                if (state) {
×
243
                    //  cannot add same item twice
UNCOV
244
                    throw new Error(`Cannot add this transaction. Transaction with id: ${transaction.id} has been already added.`);
×
245
                }
UNCOV
246
                break;
×
247
            case TransactionType.DELETE:
248
            case TransactionType.UPDATE:
UNCOV
249
                if (state && state.type === TransactionType.DELETE) {
×
250
                    //  cannot delete or update deleted items
UNCOV
251
                    throw new Error(`Cannot add this transaction. Transaction with id: ${transaction.id} has been already deleted.`);
×
252
                }
UNCOV
253
                if (!state && !recordRef && !this._isPending) {
×
254
                    //  cannot initially add transaction or delete item with no recordRef
UNCOV
255
                    throw new Error(`Cannot add this transaction. This is first transaction of type ${transaction.type} ` +
×
256
                        `for id ${transaction.id}. For first transaction of this type recordRef is mandatory.`);
257
                }
UNCOV
258
                break;
×
259
        }
260
    }
261

262
    /**
263
     * Updates the provided states collection according to passed transaction and recordRef
264
     *
265
     * @param states States collection to apply the update to
266
     * @param transaction Transaction to apply to the current state
267
     * @param recordRef Reference to the value of the record in data source, if any, where transaction should be applied
268
     */
269
    protected override updateState(states: Map<any, S>, transaction: T, recordRef?: any): void {
UNCOV
270
        let state = states.get(transaction.id);
×
271
        //  if TransactionType is ADD simply add transaction to states;
272
        //  if TransactionType is DELETE:
273
        //    - if there is state with this id of type ADD remove it from the states;
274
        //    - if there is state with this id of type UPDATE change its type to DELETE;
275
        //    - if there is no state with this id add transaction to states;
276
        //  if TransactionType is UPDATE:
277
        //    - if there is state with this id of type ADD merge new value and state recordRef into state new value
278
        //    - if there is state with this id of type UPDATE merge new value into state new value
279
        //    - if there is state with this id and state type is DELETE change its type to UPDATE
280
        //    - if there is no state with this id add transaction to states;
UNCOV
281
        if (state) {
×
UNCOV
282
            switch (transaction.type) {
×
283
                case TransactionType.DELETE:
UNCOV
284
                    if (state.type === TransactionType.ADD) {
×
UNCOV
285
                        states.delete(transaction.id);
×
UNCOV
286
                    } else if (state.type === TransactionType.UPDATE) {
×
UNCOV
287
                        state.value = transaction.newValue;
×
UNCOV
288
                        state.type = TransactionType.DELETE;
×
289
                    }
UNCOV
290
                    break;
×
291
                case TransactionType.UPDATE:
UNCOV
292
                    if (isObject(state.value)) {
×
UNCOV
293
                        if (state.type === TransactionType.ADD) {
×
UNCOV
294
                            state.value = this.mergeValues(state.value, transaction.newValue);
×
295
                        }
UNCOV
296
                        if (state.type === TransactionType.UPDATE) {
×
UNCOV
297
                            mergeObjects(state.value, transaction.newValue);
×
298
                        }
299
                    } else {
UNCOV
300
                        state.value = transaction.newValue;
×
301
                    }
302
            }
303
        } else {
UNCOV
304
            state = { value: this.cloneStrategy.clone(transaction.newValue), recordRef, type: transaction.type } as S;
×
UNCOV
305
            states.set(transaction.id, state);
×
306
        }
307

UNCOV
308
        this.cleanState(transaction.id, states);
×
309
    }
310

311
    /**
312
     * Updates state related record in the provided data
313
     *
314
     * @param data Data source to update
315
     * @param state State to update data from
316
     */
317
    protected updateRecord(data: any[], state: S) {
UNCOV
318
        const index = data.findIndex(i => JSON.stringify(i) === JSON.stringify(state.recordRef || {}));
×
UNCOV
319
        switch (state.type) {
×
320
            case TransactionType.ADD:
UNCOV
321
                data.push(state.value);
×
UNCOV
322
                break;
×
323
            case TransactionType.DELETE:
UNCOV
324
                if (0 <= index && index < data.length) {
×
UNCOV
325
                    data.splice(index, 1);
×
326
                }
UNCOV
327
                break;
×
328
            case TransactionType.UPDATE:
UNCOV
329
                if (0 <= index && index < data.length) {
×
UNCOV
330
                    data[index] = this.updateValue(state);
×
331
                }
UNCOV
332
                break;
×
333
        }
334
    }
335
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc