cojson 0.18.4 → 0.18.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +13 -0
- package/dist/coValueCore/branching.d.ts +5 -5
- package/dist/coValueCore/branching.d.ts.map +1 -1
- package/dist/coValueCore/branching.js +72 -4
- package/dist/coValueCore/branching.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +4 -0
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +37 -16
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValues/coList.d.ts +8 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +86 -58
- package/dist/coValues/coList.js.map +1 -1
- package/dist/exports.d.ts +2 -2
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +1 -1
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +1 -0
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js.map +1 -1
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +7 -4
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +7 -4
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/tests/branching.test.js +65 -1
- package/dist/tests/branching.test.js.map +1 -1
- package/dist/tests/coList.test.js +367 -1
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +1 -1
- package/dist/tests/sync.load.test.js +2 -2
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +6 -10
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +2 -2
- package/package.json +2 -2
- package/src/coValueCore/branching.ts +109 -9
- package/src/coValueCore/coValueCore.ts +45 -17
- package/src/coValues/coList.ts +118 -78
- package/src/exports.ts +6 -1
- package/src/ids.ts +1 -0
- package/src/storage/storageAsync.ts +8 -4
- package/src/storage/storageSync.ts +8 -4
- package/src/tests/branching.test.ts +103 -1
- package/src/tests/coList.test.ts +529 -1
- package/src/tests/sync.load.test.ts +2 -2
- package/src/tests/sync.storageAsync.test.ts +9 -12
package/src/coValues/coList.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { CoID, RawCoValue } from "../coValue.js";
|
|
2
|
-
import {
|
|
3
|
-
AvailableCoValueCore,
|
|
4
|
-
CoValueCore,
|
|
5
|
-
} from "../coValueCore/coValueCore.js";
|
|
2
|
+
import { AvailableCoValueCore } from "../coValueCore/coValueCore.js";
|
|
6
3
|
import { AgentID, SessionID, TransactionID } from "../ids.js";
|
|
7
4
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
8
|
-
import { CoValueKnownState } from "../sync.js";
|
|
9
5
|
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
10
6
|
import { isCoValue } from "../typeUtils/isCoValue.js";
|
|
11
7
|
import { RawAccountID } from "./account.js";
|
|
@@ -39,12 +35,14 @@ type InsertionEntry<T extends JsonValue> = {
|
|
|
39
35
|
madeAt: number;
|
|
40
36
|
predecessors: OpID[];
|
|
41
37
|
successors: OpID[];
|
|
42
|
-
|
|
38
|
+
change: InsertionOpPayload<T>;
|
|
39
|
+
};
|
|
43
40
|
|
|
44
41
|
type DeletionEntry = {
|
|
45
42
|
madeAt: number;
|
|
46
43
|
deletionID: OpID;
|
|
47
|
-
|
|
44
|
+
change: DeletionOpPayload;
|
|
45
|
+
};
|
|
48
46
|
|
|
49
47
|
export class RawCoList<
|
|
50
48
|
Item extends JsonValue = JsonValue,
|
|
@@ -109,6 +107,83 @@ export class RawCoList<
|
|
|
109
107
|
this.processNewTransactions();
|
|
110
108
|
}
|
|
111
109
|
|
|
110
|
+
private getInsertionsEntry(opID: OpID) {
|
|
111
|
+
const index = getSessionIndex(opID);
|
|
112
|
+
|
|
113
|
+
const sessionEntry = this.insertions[index];
|
|
114
|
+
if (!sessionEntry) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const txEntry = sessionEntry[opID.txIndex];
|
|
119
|
+
if (!txEntry) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return txEntry[opID.changeIdx];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private createInsertionsEntry(opID: OpID, value: InsertionEntry<Item>) {
|
|
127
|
+
const index = getSessionIndex(opID);
|
|
128
|
+
|
|
129
|
+
let sessionEntry = this.insertions[index];
|
|
130
|
+
if (!sessionEntry) {
|
|
131
|
+
sessionEntry = {};
|
|
132
|
+
this.insertions[index] = sessionEntry;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let txEntry = sessionEntry[opID.txIndex];
|
|
136
|
+
if (!txEntry) {
|
|
137
|
+
txEntry = {};
|
|
138
|
+
sessionEntry[opID.txIndex] = txEntry;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
txEntry[opID.changeIdx] = value;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private isDeleted(opID: OpID) {
|
|
145
|
+
const index = getSessionIndex(opID);
|
|
146
|
+
|
|
147
|
+
const sessionEntry = this.deletionsByInsertion[index];
|
|
148
|
+
|
|
149
|
+
if (!sessionEntry) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const txEntry = sessionEntry[opID.txIndex];
|
|
154
|
+
|
|
155
|
+
if (!txEntry) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return Boolean(txEntry[opID.changeIdx]?.length);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private pushDeletionsByInsertionEntry(opID: OpID, value: DeletionEntry) {
|
|
163
|
+
const index = getSessionIndex(opID);
|
|
164
|
+
|
|
165
|
+
let sessionEntry = this.deletionsByInsertion[index];
|
|
166
|
+
if (!sessionEntry) {
|
|
167
|
+
sessionEntry = {};
|
|
168
|
+
this.deletionsByInsertion[index] = sessionEntry;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let txEntry = sessionEntry[opID.txIndex];
|
|
172
|
+
if (!txEntry) {
|
|
173
|
+
txEntry = {};
|
|
174
|
+
sessionEntry[opID.txIndex] = txEntry;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let list = txEntry[opID.changeIdx];
|
|
178
|
+
|
|
179
|
+
if (!list) {
|
|
180
|
+
list = [];
|
|
181
|
+
txEntry[opID.changeIdx] = list;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
list.push(value);
|
|
185
|
+
}
|
|
186
|
+
|
|
112
187
|
processNewTransactions() {
|
|
113
188
|
const transactions = this.core.getValidSortedTransactions({
|
|
114
189
|
ignorePrivateTransactions: false,
|
|
@@ -133,87 +208,51 @@ export class RawCoList<
|
|
|
133
208
|
for (const [changeIdx, changeUntyped] of changes.entries()) {
|
|
134
209
|
const change = changeUntyped as ListOpPayload<Item>;
|
|
135
210
|
|
|
211
|
+
const opID = {
|
|
212
|
+
sessionID: txID.sessionID,
|
|
213
|
+
txIndex: txID.txIndex,
|
|
214
|
+
branch: txID.branch,
|
|
215
|
+
changeIdx,
|
|
216
|
+
};
|
|
217
|
+
|
|
136
218
|
if (change.op === "pre" || change.op === "app") {
|
|
137
|
-
|
|
138
|
-
if (!sessionEntry) {
|
|
139
|
-
sessionEntry = {};
|
|
140
|
-
this.insertions[txID.sessionID] = sessionEntry;
|
|
141
|
-
}
|
|
142
|
-
let txEntry = sessionEntry[txID.txIndex];
|
|
143
|
-
if (!txEntry) {
|
|
144
|
-
txEntry = {};
|
|
145
|
-
sessionEntry[txID.txIndex] = txEntry;
|
|
146
|
-
}
|
|
147
|
-
txEntry[changeIdx] = {
|
|
219
|
+
this.createInsertionsEntry(opID, {
|
|
148
220
|
madeAt,
|
|
149
221
|
predecessors: [],
|
|
150
222
|
successors: [],
|
|
151
|
-
|
|
152
|
-
};
|
|
223
|
+
change,
|
|
224
|
+
});
|
|
225
|
+
|
|
153
226
|
if (change.op === "pre") {
|
|
154
227
|
if (change.before === "end") {
|
|
155
|
-
this.beforeEnd.push(
|
|
156
|
-
...txID,
|
|
157
|
-
changeIdx,
|
|
158
|
-
});
|
|
228
|
+
this.beforeEnd.push(opID);
|
|
159
229
|
} else {
|
|
160
|
-
const beforeEntry =
|
|
161
|
-
|
|
162
|
-
change.before.txIndex
|
|
163
|
-
]?.[change.before.changeIdx];
|
|
230
|
+
const beforeEntry = this.getInsertionsEntry(change.before);
|
|
231
|
+
|
|
164
232
|
if (!beforeEntry) {
|
|
165
233
|
continue;
|
|
166
234
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
changeIdx,
|
|
170
|
-
});
|
|
235
|
+
|
|
236
|
+
beforeEntry.predecessors.push(opID);
|
|
171
237
|
}
|
|
172
238
|
} else {
|
|
173
239
|
if (change.after === "start") {
|
|
174
|
-
this.afterStart.push(
|
|
175
|
-
...txID,
|
|
176
|
-
changeIdx,
|
|
177
|
-
});
|
|
240
|
+
this.afterStart.push(opID);
|
|
178
241
|
} else {
|
|
179
|
-
const afterEntry =
|
|
180
|
-
|
|
181
|
-
change.after.txIndex
|
|
182
|
-
]?.[change.after.changeIdx];
|
|
242
|
+
const afterEntry = this.getInsertionsEntry(change.after);
|
|
243
|
+
|
|
183
244
|
if (!afterEntry) {
|
|
184
245
|
continue;
|
|
185
246
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
changeIdx,
|
|
189
|
-
});
|
|
247
|
+
|
|
248
|
+
afterEntry.successors.push(opID);
|
|
190
249
|
}
|
|
191
250
|
}
|
|
192
251
|
} else if (change.op === "del") {
|
|
193
|
-
|
|
194
|
-
this.deletionsByInsertion[change.insertion.sessionID];
|
|
195
|
-
if (!sessionEntry) {
|
|
196
|
-
sessionEntry = {};
|
|
197
|
-
this.deletionsByInsertion[change.insertion.sessionID] =
|
|
198
|
-
sessionEntry;
|
|
199
|
-
}
|
|
200
|
-
let txEntry = sessionEntry[change.insertion.txIndex];
|
|
201
|
-
if (!txEntry) {
|
|
202
|
-
txEntry = {};
|
|
203
|
-
sessionEntry[change.insertion.txIndex] = txEntry;
|
|
204
|
-
}
|
|
205
|
-
let changeEntry = txEntry[change.insertion.changeIdx];
|
|
206
|
-
if (!changeEntry) {
|
|
207
|
-
changeEntry = [];
|
|
208
|
-
txEntry[change.insertion.changeIdx] = changeEntry;
|
|
209
|
-
}
|
|
210
|
-
changeEntry.push({
|
|
252
|
+
this.pushDeletionsByInsertionEntry(change.insertion, {
|
|
211
253
|
madeAt,
|
|
212
|
-
deletionID:
|
|
213
|
-
|
|
214
|
-
changeIdx,
|
|
215
|
-
},
|
|
216
|
-
...change,
|
|
254
|
+
deletionID: opID,
|
|
255
|
+
change,
|
|
217
256
|
});
|
|
218
257
|
} else {
|
|
219
258
|
throw new Error(
|
|
@@ -324,10 +363,7 @@ export class RawCoList<
|
|
|
324
363
|
while (todo.length > 0) {
|
|
325
364
|
const currentOpID = todo[todo.length - 1]!;
|
|
326
365
|
|
|
327
|
-
const entry =
|
|
328
|
-
this.insertions[currentOpID.sessionID]?.[currentOpID.txIndex]?.[
|
|
329
|
-
currentOpID.changeIdx
|
|
330
|
-
];
|
|
366
|
+
const entry = this.getInsertionsEntry(currentOpID);
|
|
331
367
|
|
|
332
368
|
if (!entry) {
|
|
333
369
|
throw new Error("Missing op " + currentOpID);
|
|
@@ -338,22 +374,19 @@ export class RawCoList<
|
|
|
338
374
|
|
|
339
375
|
// We navigate the predecessors before processing the current opID in the list
|
|
340
376
|
if (shouldTraversePredecessors) {
|
|
341
|
-
for (
|
|
342
|
-
todo.push(
|
|
377
|
+
for (const predecessor of entry.predecessors) {
|
|
378
|
+
todo.push(predecessor);
|
|
343
379
|
}
|
|
344
380
|
predecessorsVisited.add(currentOpID);
|
|
345
381
|
} else {
|
|
346
382
|
// Remove the current opID from the todo stack to consider it processed.
|
|
347
383
|
todo.pop();
|
|
348
384
|
|
|
349
|
-
const deleted =
|
|
350
|
-
(this.deletionsByInsertion[currentOpID.sessionID]?.[
|
|
351
|
-
currentOpID.txIndex
|
|
352
|
-
]?.[currentOpID.changeIdx]?.length || 0) > 0;
|
|
385
|
+
const deleted = this.isDeleted(currentOpID);
|
|
353
386
|
|
|
354
387
|
if (!deleted) {
|
|
355
388
|
arr.push({
|
|
356
|
-
value: entry.value,
|
|
389
|
+
value: entry.change.value,
|
|
357
390
|
madeAt: entry.madeAt,
|
|
358
391
|
opID: currentOpID,
|
|
359
392
|
});
|
|
@@ -629,3 +662,10 @@ export class RawCoList<
|
|
|
629
662
|
this._cachedEntries = undefined;
|
|
630
663
|
}
|
|
631
664
|
}
|
|
665
|
+
|
|
666
|
+
function getSessionIndex(txID: TransactionID): SessionID {
|
|
667
|
+
if (txID.branch) {
|
|
668
|
+
return `${txID.sessionID}_branch_${txID.branch}`;
|
|
669
|
+
}
|
|
670
|
+
return txID.sessionID;
|
|
671
|
+
}
|
package/src/exports.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
|
|
2
2
|
import { type RawCoValue } from "./coValue.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CoValueCore,
|
|
5
|
+
idforHeader,
|
|
6
|
+
type AvailableCoValueCore,
|
|
7
|
+
} from "./coValueCore/coValueCore.js";
|
|
4
8
|
import { CoValueUniqueness } from "./coValueCore/verifiedState.js";
|
|
5
9
|
import {
|
|
6
10
|
ControlledAccount,
|
|
@@ -171,6 +175,7 @@ export type {
|
|
|
171
175
|
BinaryStreamStart,
|
|
172
176
|
OpID,
|
|
173
177
|
AccountRole,
|
|
178
|
+
AvailableCoValueCore,
|
|
174
179
|
};
|
|
175
180
|
|
|
176
181
|
export * from "./storage/index.js";
|
package/src/ids.ts
CHANGED
|
@@ -110,10 +110,14 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
110
110
|
|
|
111
111
|
let idx = 0;
|
|
112
112
|
|
|
113
|
-
signatures.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
const lastSignature = signatures[signatures.length - 1];
|
|
114
|
+
|
|
115
|
+
if (lastSignature?.signature !== sessionRow.lastSignature) {
|
|
116
|
+
signatures.push({
|
|
117
|
+
idx: sessionRow.lastIdx,
|
|
118
|
+
signature: sessionRow.lastSignature,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
117
121
|
|
|
118
122
|
for (const signature of signatures) {
|
|
119
123
|
const newTxsInSession = await this.dbClient.getNewTransactionInSession(
|
|
@@ -113,10 +113,14 @@ export class StorageApiSync implements StorageAPI {
|
|
|
113
113
|
|
|
114
114
|
let idx = 0;
|
|
115
115
|
|
|
116
|
-
signatures.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
const lastSignature = signatures[signatures.length - 1];
|
|
117
|
+
|
|
118
|
+
if (lastSignature?.signature !== sessionRow.lastSignature) {
|
|
119
|
+
signatures.push({
|
|
120
|
+
idx: sessionRow.lastIdx,
|
|
121
|
+
signature: sessionRow.lastSignature,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
120
124
|
|
|
121
125
|
for (const signature of signatures) {
|
|
122
126
|
const newTxsInSession = this.dbClient.getNewTransactionInSession(
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
setupTestNode,
|
|
5
5
|
loadCoValueOrFail,
|
|
6
6
|
} from "./testUtils.js";
|
|
7
|
-
import { expectMap } from "../coValue.js";
|
|
7
|
+
import { expectList, expectMap, expectPlainText } from "../coValue.js";
|
|
8
8
|
|
|
9
9
|
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
10
10
|
|
|
@@ -160,6 +160,108 @@ describe("Branching Logic", () => {
|
|
|
160
160
|
expect(originalMap.get("key1")).toBe("branchValue1");
|
|
161
161
|
expect(originalMap.get("key2")).toBe("branchValue2");
|
|
162
162
|
});
|
|
163
|
+
|
|
164
|
+
test("should work with co.list", () => {
|
|
165
|
+
const node = createTestNode();
|
|
166
|
+
const group = node.createGroup();
|
|
167
|
+
const list = group.createList();
|
|
168
|
+
|
|
169
|
+
// Create a shopping list with grocery items
|
|
170
|
+
list.appendItems(["bread", "milk", "eggs"]);
|
|
171
|
+
|
|
172
|
+
// Remove milk from the list
|
|
173
|
+
list.delete(list.asArray().indexOf("milk"));
|
|
174
|
+
|
|
175
|
+
const branch = expectList(
|
|
176
|
+
list.core.createBranch("feature-branch", group.id).getCurrentContent(),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Add more items to the branch
|
|
180
|
+
branch.appendItems(["cheese", "yogurt", "bananas"]);
|
|
181
|
+
|
|
182
|
+
// Remove yogurt from the branch
|
|
183
|
+
branch.delete(branch.asArray().indexOf("yogurt"));
|
|
184
|
+
|
|
185
|
+
const result = expectList(branch.core.mergeBranch().getCurrentContent());
|
|
186
|
+
|
|
187
|
+
expect(result.toJSON()).toEqual(["bread", "eggs", "cheese", "bananas"]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("should work with co.list when branching from different session", async () => {
|
|
191
|
+
const client = setupTestNode({
|
|
192
|
+
connected: true,
|
|
193
|
+
});
|
|
194
|
+
const group = client.node.createGroup();
|
|
195
|
+
const list = group.createList();
|
|
196
|
+
|
|
197
|
+
// Create a grocery list with initial items
|
|
198
|
+
list.appendItems(["bread", "milk"]);
|
|
199
|
+
|
|
200
|
+
const branch1 = expectList(
|
|
201
|
+
list.core.createBranch("feature-branch", group.id).getCurrentContent(),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Add new items to first branch
|
|
205
|
+
branch1.appendItems(["cheese"]);
|
|
206
|
+
|
|
207
|
+
const branch2 = expectList(
|
|
208
|
+
list.core
|
|
209
|
+
.createBranch("feature-branch-2", group.id)
|
|
210
|
+
.getCurrentContent(),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Add different items to second branch
|
|
214
|
+
branch2.appendItems(["apples", "oranges", "carrots"]);
|
|
215
|
+
|
|
216
|
+
const anotherSession = client.spawnNewSession();
|
|
217
|
+
|
|
218
|
+
const loadedBranch2 = await loadCoValueOrFail(
|
|
219
|
+
anotherSession.node,
|
|
220
|
+
branch2.id,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Add more items and remove some existing ones
|
|
224
|
+
loadedBranch2.appendItems(["tomatoes", "lettuce", "cucumber"]);
|
|
225
|
+
loadedBranch2.delete(loadedBranch2.asArray().indexOf("lettuce"));
|
|
226
|
+
loadedBranch2.delete(loadedBranch2.asArray().indexOf("milk"));
|
|
227
|
+
|
|
228
|
+
loadedBranch2.core.mergeBranch();
|
|
229
|
+
|
|
230
|
+
await loadedBranch2.core.waitForSync();
|
|
231
|
+
|
|
232
|
+
branch1.core.mergeBranch();
|
|
233
|
+
|
|
234
|
+
expect(list.toJSON()).toEqual([
|
|
235
|
+
"bread",
|
|
236
|
+
"cheese",
|
|
237
|
+
"apples",
|
|
238
|
+
"oranges",
|
|
239
|
+
"carrots",
|
|
240
|
+
"tomatoes",
|
|
241
|
+
"cucumber",
|
|
242
|
+
]);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("should work with co.plainText when branching from different session", async () => {
|
|
246
|
+
const node = createTestNode();
|
|
247
|
+
const group = node.createGroup();
|
|
248
|
+
const plainText = group.createPlainText();
|
|
249
|
+
|
|
250
|
+
plainText.insertAfter(0, "hello");
|
|
251
|
+
|
|
252
|
+
const branch = expectPlainText(
|
|
253
|
+
plainText.core
|
|
254
|
+
.createBranch("feature-branch", group.id)
|
|
255
|
+
.getCurrentContent(),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Add more items to the branch
|
|
259
|
+
branch.insertAfter("hello".length, "world");
|
|
260
|
+
|
|
261
|
+
branch.core.mergeBranch();
|
|
262
|
+
|
|
263
|
+
expect(plainText.toString()).toEqual("helloworld");
|
|
264
|
+
});
|
|
163
265
|
});
|
|
164
266
|
|
|
165
267
|
describe("Branch Loading and Checkout", () => {
|