struct.spec.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. import { describe, expect, it, jest } from '@jest/globals';
  2. import { StructAsyncDeserializeStream, StructDefaultOptions, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from './basic/index.js';
  3. import { BigIntFieldDefinition, BigIntFieldType, BufferFieldSubType, FixedLengthBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, VariableLengthBufferLikeFieldDefinition } from "./index.js";
  4. import { Struct } from './struct.js';
  5. import type { ValueOrPromise } from './utils.js';
  6. class MockDeserializationStream implements StructDeserializeStream {
  7. public buffer = new Uint8Array(0);
  8. public read = jest.fn((length: number) => this.buffer);
  9. }
  10. describe('Struct', () => {
  11. describe('.constructor', () => {
  12. it('should initialize fields', () => {
  13. const struct = new Struct();
  14. expect(struct).toHaveProperty('options', StructDefaultOptions);
  15. expect(struct).toHaveProperty('size', 0);
  16. });
  17. });
  18. describe('#field', () => {
  19. class MockFieldDefinition extends StructFieldDefinition<number>{
  20. public constructor(size: number) {
  21. super(size);
  22. }
  23. public getSize = jest.fn(() => {
  24. return this.options;
  25. });
  26. public create(options: Readonly<StructOptions>, struct: StructValue, value: unknown): StructFieldValue<this> {
  27. throw new Error('Method not implemented.');
  28. }
  29. public override deserialize(
  30. options: Readonly<StructOptions>,
  31. stream: StructDeserializeStream,
  32. struct: StructValue,
  33. ): StructFieldValue<this>;
  34. public override deserialize(
  35. options: Readonly<StructOptions>,
  36. stream: StructAsyncDeserializeStream,
  37. struct: StructValue,
  38. ): Promise<StructFieldValue<this>>;
  39. public override deserialize(
  40. options: Readonly<StructOptions>,
  41. stream: StructDeserializeStream | StructAsyncDeserializeStream,
  42. struct: StructValue
  43. ): ValueOrPromise<StructFieldValue<this>> {
  44. throw new Error('Method not implemented.');
  45. }
  46. }
  47. it('should push a field and update size', () => {
  48. const struct = new Struct();
  49. const field1 = 'foo';
  50. const fieldDefinition1 = new MockFieldDefinition(4);
  51. struct.field(field1, fieldDefinition1);
  52. expect(struct).toHaveProperty('size', 4);
  53. expect(fieldDefinition1.getSize).toBeCalledTimes(1);
  54. expect(struct['_fields']).toEqual([[field1, fieldDefinition1]]);
  55. const field2 = 'bar';
  56. const fieldDefinition2 = new MockFieldDefinition(8);
  57. struct.field(field2, fieldDefinition2);
  58. expect(struct).toHaveProperty('size', 12);
  59. expect(fieldDefinition2.getSize).toBeCalledTimes(1);
  60. expect(struct['_fields']).toEqual([
  61. [field1, fieldDefinition1],
  62. [field2, fieldDefinition2],
  63. ]);
  64. });
  65. it('should throw an error if field name already exists', () => {
  66. const struct = new Struct();
  67. const fieldName = 'foo';
  68. struct.field(fieldName, new MockFieldDefinition(4));
  69. expect(() => struct.field(fieldName, new MockFieldDefinition(4))).toThrowError();
  70. });
  71. });
  72. describe('#number', () => {
  73. it('`int8` should append an `int8` field', () => {
  74. const struct = new Struct();
  75. struct.int8('foo');
  76. expect(struct).toHaveProperty('size', 1);
  77. const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
  78. expect(definition).toBeInstanceOf(NumberFieldDefinition);
  79. expect(definition.type).toBe(NumberFieldType.Int8);
  80. });
  81. it('`uint8` should append an `uint8` field', () => {
  82. const struct = new Struct();
  83. struct.uint8('foo');
  84. expect(struct).toHaveProperty('size', 1);
  85. const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
  86. expect(definition).toBeInstanceOf(NumberFieldDefinition);
  87. expect(definition.type).toBe(NumberFieldType.Uint8);
  88. });
  89. it('`int16` should append an `int16` field', () => {
  90. const struct = new Struct();
  91. struct.int16('foo');
  92. expect(struct).toHaveProperty('size', 2);
  93. const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
  94. expect(definition).toBeInstanceOf(NumberFieldDefinition);
  95. expect(definition.type).toBe(NumberFieldType.Int16);
  96. });
  97. it('`uint16` should append an `uint16` field', () => {
  98. const struct = new Struct();
  99. struct.uint16('foo');
  100. expect(struct).toHaveProperty('size', 2);
  101. const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
  102. expect(definition).toBeInstanceOf(NumberFieldDefinition);
  103. expect(definition.type).toBe(NumberFieldType.Uint16);
  104. });
  105. it('`int32` should append an `int32` field', () => {
  106. const struct = new Struct();
  107. struct.int32('foo');
  108. expect(struct).toHaveProperty('size', 4);
  109. const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
  110. expect(definition).toBeInstanceOf(NumberFieldDefinition);
  111. expect(definition.type).toBe(NumberFieldType.Int32);
  112. });
  113. it('`uint32` should append an `uint32` field', () => {
  114. const struct = new Struct();
  115. struct.uint32('foo');
  116. expect(struct).toHaveProperty('size', 4);
  117. const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
  118. expect(definition).toBeInstanceOf(NumberFieldDefinition);
  119. expect(definition.type).toBe(NumberFieldType.Uint32);
  120. });
  121. it('`int64` should append an `int64` field', () => {
  122. const struct = new Struct();
  123. struct.int64('foo');
  124. expect(struct).toHaveProperty('size', 8);
  125. const definition = struct['_fields'][0]![1] as BigIntFieldDefinition;
  126. expect(definition).toBeInstanceOf(BigIntFieldDefinition);
  127. expect(definition.type).toBe(BigIntFieldType.Int64);
  128. });
  129. it('`uint64` should append an `uint64` field', () => {
  130. const struct = new Struct();
  131. struct.uint64('foo');
  132. expect(struct).toHaveProperty('size', 8);
  133. const definition = struct['_fields'][0]![1] as BigIntFieldDefinition;
  134. expect(definition).toBeInstanceOf(BigIntFieldDefinition);
  135. expect(definition.type).toBe(BigIntFieldType.Uint64);
  136. });
  137. describe('#uint8ArrayLike', () => {
  138. describe('FixedLengthBufferLikeFieldDefinition', () => {
  139. it('`#uint8Array` with fixed length', () => {
  140. let struct = new Struct();
  141. struct.uint8Array('foo', { length: 10 });
  142. expect(struct).toHaveProperty('size', 10);
  143. const definition = struct['_fields'][0]![1] as FixedLengthBufferLikeFieldDefinition;
  144. expect(definition).toBeInstanceOf(FixedLengthBufferLikeFieldDefinition);
  145. expect(definition.type).toBeInstanceOf(BufferFieldSubType);
  146. expect(definition.options.length).toBe(10);
  147. });
  148. it('`#string` with fixed length', () => {
  149. let struct = new Struct();
  150. struct.string('foo', { length: 10 });
  151. expect(struct).toHaveProperty('size', 10);
  152. const definition = struct['_fields'][0]![1] as FixedLengthBufferLikeFieldDefinition;
  153. expect(definition).toBeInstanceOf(FixedLengthBufferLikeFieldDefinition);
  154. expect(definition.type).toBeInstanceOf(BufferFieldSubType);
  155. expect(definition.options.length).toBe(10);
  156. });
  157. });
  158. describe('VariableLengthBufferLikeFieldDefinition', () => {
  159. it('`#uint8Array` with variable length', () => {
  160. const struct = new Struct().int8('barLength');
  161. expect(struct).toHaveProperty('size', 1);
  162. struct.uint8Array('bar', { lengthField: 'barLength' });
  163. expect(struct).toHaveProperty('size', 1);
  164. const definition = struct['_fields'][1]![1] as VariableLengthBufferLikeFieldDefinition;
  165. expect(definition).toBeInstanceOf(VariableLengthBufferLikeFieldDefinition);
  166. expect(definition.type).toBeInstanceOf(BufferFieldSubType);
  167. expect(definition.options.lengthField).toBe('barLength');
  168. });
  169. it('`#string` with variable length', () => {
  170. const struct = new Struct().int8('barLength');
  171. expect(struct).toHaveProperty('size', 1);
  172. struct.string('bar', { lengthField: 'barLength' });
  173. expect(struct).toHaveProperty('size', 1);
  174. const definition = struct['_fields'][1]![1] as VariableLengthBufferLikeFieldDefinition;
  175. expect(definition).toBeInstanceOf(VariableLengthBufferLikeFieldDefinition);
  176. expect(definition.type).toBeInstanceOf(BufferFieldSubType);
  177. expect(definition.options.lengthField).toBe('barLength');
  178. });
  179. });
  180. });
  181. describe('#fields', () => {
  182. it('should append all fields from other struct', async () => {
  183. const sub = new Struct()
  184. .int16('int16')
  185. .int32('int32');
  186. const struct = new Struct()
  187. .int8('int8')
  188. .fields(sub)
  189. .int64('int64');
  190. const field0 = struct['_fields'][0]!;
  191. expect(field0).toHaveProperty('0', 'int8');
  192. expect(field0[1]).toHaveProperty('type', NumberFieldType.Int8);
  193. const field1 = struct['_fields'][1]!;
  194. expect(field1).toHaveProperty('0', 'int16');
  195. expect(field1[1]).toHaveProperty('type', NumberFieldType.Int16);
  196. const field2 = struct['_fields'][2]!;
  197. expect(field2).toHaveProperty('0', 'int32');
  198. expect(field2[1]).toHaveProperty('type', NumberFieldType.Int32);
  199. const field3 = struct['_fields'][3]!;
  200. expect(field3).toHaveProperty('0', 'int64');
  201. expect(field3[1]).toHaveProperty('type', BigIntFieldType.Int64);
  202. });
  203. });
  204. describe('deserialize', () => {
  205. it('should deserialize without dynamic size fields', async () => {
  206. const struct = new Struct()
  207. .int8('foo')
  208. .int16('bar');
  209. const stream = new MockDeserializationStream();
  210. stream.read
  211. .mockReturnValueOnce(new Uint8Array([2]))
  212. .mockReturnValueOnce(new Uint8Array([0, 16]));
  213. const result = await struct.deserialize(stream);
  214. expect(result).toEqual({ foo: 2, bar: 16 });
  215. expect(stream.read).toBeCalledTimes(2);
  216. expect(stream.read).nthCalledWith(1, 1);
  217. expect(stream.read).nthCalledWith(2, 2);
  218. });
  219. it('should deserialize with dynamic size fields', async () => {
  220. const struct = new Struct()
  221. .int8('fooLength')
  222. .uint8Array('foo', { lengthField: 'fooLength' });
  223. const stream = new MockDeserializationStream();
  224. stream.read
  225. .mockReturnValueOnce(new Uint8Array([2]))
  226. .mockReturnValueOnce(new Uint8Array([3, 4]));
  227. const result = await struct.deserialize(stream);
  228. expect(result).toEqual({ fooLength: 2, foo: new Uint8Array([3, 4]) });
  229. expect(stream.read).toBeCalledTimes(2);
  230. expect(stream.read).nthCalledWith(1, 1);
  231. expect(stream.read).nthCalledWith(2, 2);
  232. });
  233. });
  234. describe('#extra', () => {
  235. it('should accept plain field', async () => {
  236. const struct = new Struct()
  237. .extra({ foo: 42, bar: true });
  238. const stream = new MockDeserializationStream();
  239. const result = await struct.deserialize(stream);
  240. expect(Object.entries(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(result)))).toEqual([
  241. ['foo', { configurable: true, enumerable: true, writable: true, value: 42 }],
  242. ['bar', { configurable: true, enumerable: true, writable: true, value: true }],
  243. ]);
  244. });
  245. it('should accept accessors', async () => {
  246. const struct = new Struct()
  247. .extra({
  248. get foo() { return 42; },
  249. get bar() { return true; },
  250. set bar(value) { },
  251. });
  252. const stream = new MockDeserializationStream();
  253. const result = await struct.deserialize(stream);
  254. expect(Object.entries(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(result)))).toEqual([
  255. ['foo', { configurable: true, enumerable: true, get: expect.any(Function) }],
  256. ['bar', { configurable: true, enumerable: true, get: expect.any(Function), set: expect.any(Function) }],
  257. ]);
  258. });
  259. });
  260. describe('#postDeserialize', () => {
  261. it('can throw errors', () => {
  262. const struct = new Struct();
  263. const callback = jest.fn(() => { throw new Error('mock'); });
  264. struct.postDeserialize(callback);
  265. const stream = new MockDeserializationStream();
  266. expect(() => struct.deserialize(stream)).toThrowError('mock');
  267. expect(callback).toBeCalledTimes(1);
  268. });
  269. it('can replace return value', () => {
  270. const struct = new Struct();
  271. const callback = jest.fn(() => 'mock');
  272. struct.postDeserialize(callback);
  273. const stream = new MockDeserializationStream();
  274. expect(struct.deserialize(stream)).toBe('mock');
  275. expect(callback).toBeCalledTimes(1);
  276. expect(callback).toBeCalledWith({});
  277. });
  278. it('can return nothing', () => {
  279. const struct = new Struct();
  280. const callback = jest.fn();
  281. struct.postDeserialize(callback);
  282. const stream = new MockDeserializationStream();
  283. const result = struct.deserialize(stream);
  284. expect(callback).toBeCalledTimes(1);
  285. expect(callback).toBeCalledWith(result);
  286. });
  287. it('should overwrite callback', () => {
  288. const struct = new Struct();
  289. const callback1 = jest.fn();
  290. struct.postDeserialize(callback1);
  291. const callback2 = jest.fn();
  292. struct.postDeserialize(callback2);
  293. const stream = new MockDeserializationStream();
  294. struct.deserialize(stream);
  295. expect(callback1).toBeCalledTimes(0);
  296. expect(callback2).toBeCalledTimes(1);
  297. expect(callback2).toBeCalledWith({});
  298. });
  299. });
  300. describe('#serialize', () => {
  301. it('should serialize without dynamic size fields', () => {
  302. const struct = new Struct()
  303. .int8('foo')
  304. .int16('bar');
  305. const result = new Uint8Array(struct.serialize({ foo: 0x42, bar: 0x1024 }));
  306. expect(result).toEqual(new Uint8Array([0x42, 0x10, 0x24]));
  307. });
  308. it('should serialize with dynamic size fields', () => {
  309. const struct = new Struct()
  310. .int8('fooLength')
  311. .uint8Array('foo', { lengthField: 'fooLength' });
  312. const result = new Uint8Array(struct.serialize({ foo: new Uint8Array([0x03, 0x04, 0x05]) }));
  313. expect(result).toEqual(new Uint8Array([0x03, 0x03, 0x04, 0x05]));
  314. });
  315. });
  316. });
  317. });