@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
788 lines (606 loc) • 25.8 kB
text/typescript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { Address, AddressMap } from '../src/index.js';
describe('AddressMap', () => {
// Store original fromBigInt to restore later
const originalFromBigInt = Address.fromBigInt.bind(Address);
// Helper to create mock addresses with predictable bigint values
const createMockAddress = (value: bigint): Address => {
return originalFromBigInt(value);
};
beforeEach(() => {
// Mock Address.fromBigInt to return a mock address
Address.fromBigInt = vi.fn((value: bigint) => createMockAddress(value));
});
afterEach(() => {
Address.fromBigInt = originalFromBigInt;
});
describe('constructor', () => {
it('should create an empty map with no arguments', () => {
const map = new AddressMap();
expect(map.size).toBe(0);
});
it('should create an empty map with null', () => {
const map = new AddressMap(null);
expect(map.size).toBe(0);
});
it('should create a map from iterable', () => {
const addr1 = createMockAddress(1n);
const addr2 = createMockAddress(2n);
const map = new AddressMap([
[addr1, 'value1'],
[addr2, 'value2'],
]);
expect(map.size).toBe(2);
expect(map.get(addr1)).toBe('value1');
expect(map.get(addr2)).toBe('value2');
});
it('should handle empty iterable', () => {
const map = new AddressMap([]);
expect(map.size).toBe(0);
});
});
describe('size', () => {
it('should return 0 for empty map', () => {
const map = new AddressMap();
expect(map.size).toBe(0);
});
it('should return correct size after additions', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
expect(map.size).toBe(1);
map.set(createMockAddress(2n), 'b');
expect(map.size).toBe(2);
map.set(createMockAddress(3n), 'c');
expect(map.size).toBe(3);
});
it('should not increase size when setting existing key', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'a');
expect(map.size).toBe(1);
map.set(addr, 'b');
expect(map.size).toBe(1);
});
it('should decrease size after deletion', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'a');
expect(map.size).toBe(1);
map.delete(addr);
expect(map.size).toBe(0);
});
});
describe('set', () => {
it('should add new key-value pair', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'test');
expect(map.has(addr)).toBe(true);
expect(map.get(addr)).toBe('test');
});
it('should update existing key', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'first');
map.set(addr, 'second');
expect(map.get(addr)).toBe('second');
expect(map.size).toBe(1);
});
it('should return this for chaining', () => {
const map = new AddressMap<string>();
const addr1 = createMockAddress(1n);
const addr2 = createMockAddress(2n);
const result = map.set(addr1, 'a').set(addr2, 'b');
expect(result).toBe(map);
expect(map.size).toBe(2);
});
it('should handle various value types', () => {
const map = new AddressMap<unknown>();
const addr1 = createMockAddress(1n);
const addr2 = createMockAddress(2n);
const addr3 = createMockAddress(3n);
const addr4 = createMockAddress(4n);
const addr5 = createMockAddress(5n);
map.set(addr1, null);
map.set(addr2, undefined);
map.set(addr3, { nested: 'object' });
map.set(addr4, [1, 2, 3]);
map.set(addr5, () => 'function');
expect(map.get(addr1)).toBe(null);
expect(map.get(addr2)).toBe(undefined);
expect(map.get(addr3)).toEqual({ nested: 'object' });
expect(map.get(addr4)).toEqual([1, 2, 3]);
expect(typeof map.get(addr5)).toBe('function');
});
});
describe('get', () => {
it('should return value for existing key', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'test');
expect(map.get(addr)).toBe('test');
});
it('should return undefined for non-existing key', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
expect(map.get(addr)).toBeUndefined();
});
it('should return undefined after key is deleted', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'test');
map.delete(addr);
expect(map.get(addr)).toBeUndefined();
});
it('should distinguish between undefined value and missing key', () => {
const map = new AddressMap<string | undefined>();
const addr1 = createMockAddress(1n);
const addr2 = createMockAddress(2n);
map.set(addr1, undefined);
expect(map.get(addr1)).toBeUndefined();
expect(map.get(addr2)).toBeUndefined();
expect(map.has(addr1)).toBe(true);
expect(map.has(addr2)).toBe(false);
});
});
describe('has', () => {
it('should return true for existing key', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'test');
expect(map.has(addr)).toBe(true);
});
it('should return false for non-existing key', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
expect(map.has(addr)).toBe(false);
});
it('should return false after key is deleted', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'test');
map.delete(addr);
expect(map.has(addr)).toBe(false);
});
it('should return true for key with undefined value', () => {
const map = new AddressMap<string | undefined>();
const addr = createMockAddress(1n);
map.set(addr, undefined);
expect(map.has(addr)).toBe(true);
});
it('should return true for key with null value', () => {
const map = new AddressMap<string | null>();
const addr = createMockAddress(1n);
map.set(addr, null);
expect(map.has(addr)).toBe(true);
});
});
describe('delete', () => {
it('should delete existing key and return true', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'test');
const result = map.delete(addr);
expect(result).toBe(true);
expect(map.has(addr)).toBe(false);
expect(map.size).toBe(0);
});
it('should return false for non-existing key', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
const result = map.delete(addr);
expect(result).toBe(false);
});
it('should return false when deleting same key twice', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'test');
expect(map.delete(addr)).toBe(true);
expect(map.delete(addr)).toBe(false);
});
it('should not affect other keys', () => {
const map = new AddressMap<string>();
const addr1 = createMockAddress(1n);
const addr2 = createMockAddress(2n);
const addr3 = createMockAddress(3n);
map.set(addr1, 'a');
map.set(addr2, 'b');
map.set(addr3, 'c');
map.delete(addr2);
expect(map.has(addr1)).toBe(true);
expect(map.has(addr2)).toBe(false);
expect(map.has(addr3)).toBe(true);
expect(map.size).toBe(2);
});
});
describe('clear', () => {
it('should remove all entries', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
map.set(createMockAddress(2n), 'b');
map.set(createMockAddress(3n), 'c');
map.clear();
expect(map.size).toBe(0);
});
it('should work on empty map', () => {
const map = new AddressMap<string>();
map.clear();
expect(map.size).toBe(0);
});
it('should allow adding after clear', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'before');
map.clear();
map.set(addr, 'after');
expect(map.get(addr)).toBe('after');
expect(map.size).toBe(1);
});
});
describe('indexOf', () => {
it('should return index of existing key', () => {
const map = new AddressMap<string>();
const addr1 = createMockAddress(1n);
const addr2 = createMockAddress(2n);
const addr3 = createMockAddress(3n);
map.set(addr1, 'a');
map.set(addr2, 'b');
map.set(addr3, 'c');
expect(map.indexOf(addr1)).toBe(0);
expect(map.indexOf(addr2)).toBe(1);
expect(map.indexOf(addr3)).toBe(2);
});
it('should return -1 for non-existing key', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
expect(map.indexOf(addr)).toBe(-1);
});
it('should return -1 after key is deleted', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'test');
map.delete(addr);
expect(map.indexOf(addr)).toBe(-1);
});
it('should update indices after deletion', () => {
const map = new AddressMap<string>();
const addr1 = createMockAddress(1n);
const addr2 = createMockAddress(2n);
const addr3 = createMockAddress(3n);
map.set(addr1, 'a');
map.set(addr2, 'b');
map.set(addr3, 'c');
map.delete(addr1);
expect(map.indexOf(addr2)).toBe(0);
expect(map.indexOf(addr3)).toBe(1);
});
});
describe('entries', () => {
it('should yield all entries', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
map.set(createMockAddress(2n), 'b');
map.set(createMockAddress(3n), 'c');
const entries = [...map.entries()];
expect(entries.length).toBe(3);
expect((entries[0] as [Address, string])[0].toBigInt()).toBe(1n);
expect((entries[0] as [Address, string])[1]).toBe('a');
expect((entries[1] as [Address, string])[0].toBigInt()).toBe(2n);
expect((entries[1] as [Address, string])[1]).toBe('b');
expect((entries[2] as [Address, string])[0].toBigInt()).toBe(3n);
expect((entries[2] as [Address, string])[1]).toBe('c');
});
it('should yield nothing for empty map', () => {
const map = new AddressMap<string>();
const entries = [...map.entries()];
expect(entries).toEqual([]);
});
it('should maintain insertion order', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(3n), 'c');
map.set(createMockAddress(1n), 'a');
map.set(createMockAddress(2n), 'b');
const entries = [...map.entries()];
expect((entries[0] as [Address, string])[0].toBigInt()).toBe(3n);
expect((entries[1] as [Address, string])[0].toBigInt()).toBe(1n);
expect((entries[2] as [Address, string])[0].toBigInt()).toBe(2n);
});
it('should create new Address instances (not references)', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
void [...map.entries()];
void [...map.entries()];
// Address.fromBigInt should be called for each iteration
expect(vi.mocked(Address.fromBigInt)).toHaveBeenCalledWith(1n);
});
});
describe('keys', () => {
it('should yield all keys', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
map.set(createMockAddress(2n), 'b');
map.set(createMockAddress(3n), 'c');
const keys = [...map.keys()];
expect(keys.length).toBe(3);
expect((keys[0] as Address).toBigInt()).toBe(1n);
expect((keys[1] as Address).toBigInt()).toBe(2n);
expect((keys[2] as Address).toBigInt()).toBe(3n);
});
it('should yield nothing for empty map', () => {
const map = new AddressMap<string>();
const keys = [...map.keys()];
expect(keys).toEqual([]);
});
it('should maintain insertion order', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(30n), 'c');
map.set(createMockAddress(10n), 'a');
map.set(createMockAddress(20n), 'b');
const keys = [...map.keys()];
expect((keys[0] as Address).toBigInt()).toBe(30n);
expect((keys[1] as Address).toBigInt()).toBe(10n);
expect((keys[2] as Address).toBigInt()).toBe(20n);
});
});
describe('values', () => {
it('should yield all values', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
map.set(createMockAddress(2n), 'b');
map.set(createMockAddress(3n), 'c');
const values = [...map.values()];
expect(values).toEqual(['a', 'b', 'c']);
});
it('should yield nothing for empty map', () => {
const map = new AddressMap<string>();
const values = [...map.values()];
expect(values).toEqual([]);
});
it('should maintain insertion order', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(3n), 'third');
map.set(createMockAddress(1n), 'first');
map.set(createMockAddress(2n), 'second');
const values = [...map.values()];
expect(values).toEqual(['third', 'first', 'second']);
});
it('should yield updated values', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(1n);
map.set(addr, 'original');
map.set(addr, 'updated');
const values = [...map.values()];
expect(values).toEqual(['updated']);
});
});
describe('forEach', () => {
it('should call callback for each entry', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
map.set(createMockAddress(2n), 'b');
map.set(createMockAddress(3n), 'c');
const callback = vi.fn();
map.forEach(callback);
expect(callback).toHaveBeenCalledTimes(3);
});
/*it('should pass value, key, and map to callback', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'test');
const callback = vi.fn();
map.forEach(callback);
expect(callback).toHaveBeenCalledWith(
'test',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
expect.objectContaining({ toBigInt: expect.any(Function) }),
map,
);
});*/
it('should iterate in insertion order', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(3n), 'c');
map.set(createMockAddress(1n), 'a');
map.set(createMockAddress(2n), 'b');
const values: string[] = [];
map.forEach((value: string) => values.push(value));
expect(values).toEqual(['c', 'a', 'b']);
});
it('should not call callback for empty map', () => {
const map = new AddressMap<string>();
const callback = vi.fn();
map.forEach(callback);
expect(callback).not.toHaveBeenCalled();
});
it('should use thisArg as context', () => {
const map = new AddressMap<number>();
map.set(createMockAddress(1n), 10);
map.set(createMockAddress(2n), 20);
map.set(createMockAddress(3n), 30);
const collector = {
sum: 0,
add(value: number) {
this.sum += value;
},
};
map.forEach(function (this: typeof collector, value: number) {
this.add(value);
}, collector);
expect(collector.sum).toBe(60);
});
it('should work without thisArg', () => {
const map = new AddressMap<number>();
map.set(createMockAddress(1n), 1);
let sum = 0;
map.forEach((value: number) => {
sum += value;
});
expect(sum).toBe(1);
});
});
describe('Symbol.iterator', () => {
it('should be iterable with for...of', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
map.set(createMockAddress(2n), 'b');
const entries: [Address, string][] = [];
for (const entry of map) {
entries.push(entry);
}
expect(entries.length).toBe(2);
expect((entries[0] as [Address, string])[0].toBigInt()).toBe(1n);
expect((entries[0] as [Address, string])[1]).toBe('a');
expect((entries[1] as [Address, string])[0].toBigInt()).toBe(2n);
expect((entries[1] as [Address, string])[1]).toBe('b');
});
it('should work with spread operator', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
map.set(createMockAddress(2n), 'b');
const entries = [...map];
expect(entries.length).toBe(2);
});
it('should work with Array.from', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
const entries = Array.from(map);
expect(entries.length).toBe(1);
});
it('should yield same as entries()', () => {
const map = new AddressMap<string>();
map.set(createMockAddress(1n), 'a');
map.set(createMockAddress(2n), 'b');
const fromIterator = [...map];
const fromEntries = [...map.entries()];
expect(fromIterator.length).toBe(fromEntries.length);
for (let i = 0; i < fromIterator.length; i++) {
expect((fromIterator[i] as [Address, string])[0].toBigInt()).toBe(
(fromEntries[i] as [Address, string])[0].toBigInt(),
);
expect((fromIterator[i] as [Address, string])[1]).toBe(
(fromEntries[i] as [Address, string])[1],
);
}
});
});
describe('edge cases', () => {
it('should handle large number of entries', () => {
const map = new AddressMap<number>();
const count = 10000;
for (let i = 0; i < count; i++) {
map.set(createMockAddress(BigInt(i)), i);
}
expect(map.size).toBe(count);
expect(map.get(createMockAddress(0n))).toBe(0);
expect(map.get(createMockAddress(BigInt(count - 1)))).toBe(count - 1);
});
it('should handle addresses with very large bigint values', () => {
const map = new AddressMap<string>();
const largeBigInt = 2n ** 256n - 1n;
const addr = createMockAddress(largeBigInt);
map.set(addr, 'large');
expect(map.get(addr)).toBe('large');
expect(map.has(addr)).toBe(true);
});
it('should handle zero bigint value', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(0n);
map.set(addr, 'zero');
expect(map.get(addr)).toBe('zero');
expect(map.has(addr)).toBe(true);
});
it('should handle negative bigint values', () => {
const map = new AddressMap<string>();
const addr = createMockAddress(-1n);
map.set(addr, 'negative');
expect(map.get(addr)).toBe('negative');
expect(map.has(addr)).toBe(true);
});
it('should handle rapid set/delete cycles', () => {
const map = new AddressMap<number>();
const addr = createMockAddress(1n);
for (let i = 0; i < 1000; i++) {
map.set(addr, i);
if (i % 2 === 0) {
map.delete(addr);
}
}
// Last iteration: i=999, set to 999, 999 % 2 !== 0, so not deleted
expect(map.get(addr)).toBe(999);
});
it('should handle interleaved operations', () => {
const map = new AddressMap<string>();
const addr1 = createMockAddress(1n);
const addr2 = createMockAddress(2n);
const addr3 = createMockAddress(3n);
map.set(addr1, 'a');
map.set(addr2, 'b');
map.delete(addr1);
map.set(addr3, 'c');
map.set(addr1, 'a2');
map.delete(addr2);
expect(map.size).toBe(2);
expect(map.get(addr1)).toBe('a2');
expect(map.has(addr2)).toBe(false);
expect(map.get(addr3)).toBe('c');
// Check order: addr3 was added before addr1 was re-added
const keys = [...map.keys()];
expect((keys[0] as Address).toBigInt()).toBe(3n);
expect((keys[1] as Address).toBigInt()).toBe(1n);
});
});
describe('performance', () => {
it('should handle bulk insertions efficiently', () => {
const map = new AddressMap<number>();
const start = performance.now();
for (let i = 0; i < 50000; i++) {
map.set(createMockAddress(BigInt(i)), i);
}
const duration = performance.now() - start;
expect(map.size).toBe(50000);
// Should complete in reasonable time (adjust threshold as needed)
expect(duration).toBeLessThan(5000); // 5 seconds max
});
it('should handle bulk lookups efficiently', () => {
const map = new AddressMap<number>();
const count = 50000;
for (let i = 0; i < count; i++) {
map.set(createMockAddress(BigInt(i)), i);
}
const start = performance.now();
for (let i = 0; i < count; i++) {
map.get(createMockAddress(BigInt(i)));
}
const duration = performance.now() - start;
// Should complete in reasonable time
expect(duration).toBeLessThan(5000);
});
it('should handle bulk deletions efficiently', () => {
const map = new AddressMap<number>();
const count = 10000;
for (let i = 0; i < count; i++) {
map.set(createMockAddress(BigInt(i)), i);
}
const start = performance.now();
for (let i = 0; i < count; i++) {
map.delete(createMockAddress(BigInt(i)));
}
const duration = performance.now() - start;
expect(map.size).toBe(0);
expect(duration).toBeLessThan(5000);
});
it('should iterate efficiently over large map', () => {
const map = new AddressMap<number>();
const count = 50000;
for (let i = 0; i < count; i++) {
map.set(createMockAddress(BigInt(i)), i);
}
const start = performance.now();
let sum = 0;
for (const [, value] of map) {
sum += value;
}
const duration = performance.now() - start;
expect(sum).toBe((count * (count - 1)) / 2);
expect(duration).toBeLessThan(5000);
});
});
});