mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-04 18:29:23 +02:00
test(struct): 100% code coverage
This commit is contained in:
parent
b71f9714b2
commit
83910f1a35
12 changed files with 985 additions and 466 deletions
|
@ -1,8 +1,8 @@
|
|||
import { ValueOrPromise } from '../utils';
|
||||
import { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
||||
import type { ValueOrPromise } from '../utils';
|
||||
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
||||
import { StructFieldDefinition } from './definition';
|
||||
import { StructFieldValue } from './field-value';
|
||||
import { StructValue } from './struct-value';
|
||||
import type { StructFieldValue } from './field-value';
|
||||
import type { StructValue } from './struct-value';
|
||||
|
||||
describe('StructFieldDefinition', () => {
|
||||
describe('.constructor', () => {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ValueOrPromise } from '../utils';
|
||||
import { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
||||
import type { ValueOrPromise } from '../utils';
|
||||
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
||||
import { StructFieldDefinition } from './definition';
|
||||
import { StructFieldValue } from './field-value';
|
||||
import { StructValue } from './struct-value';
|
||||
import type { StructValue } from './struct-value';
|
||||
|
||||
describe('StructFieldValue', () => {
|
||||
describe('.constructor', () => {
|
||||
|
|
|
@ -7,8 +7,11 @@ describe('StructValue', () => {
|
|||
const bar = new StructValue();
|
||||
|
||||
expect(foo).toHaveProperty('fieldValues', {});
|
||||
expect(foo).toHaveProperty('value', {});
|
||||
expect(bar).toHaveProperty('fieldValues', {});
|
||||
expect(bar).toHaveProperty('value', {});
|
||||
expect(foo.fieldValues).not.toBe(bar.fieldValues);
|
||||
expect(foo.value).not.toBe(bar.fieldValues);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
407
packages/struct/src/struct.spec.ts
Normal file
407
packages/struct/src/struct.spec.ts
Normal file
|
@ -0,0 +1,407 @@
|
|||
import { StructDefaultOptions, StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from './basic';
|
||||
import { Struct } from './struct';
|
||||
import { ArrayBufferFieldType, FixedLengthArrayBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition } from './types';
|
||||
import { ValueOrPromise } from './utils';
|
||||
|
||||
class MockDeserializationContext implements StructDeserializationContext {
|
||||
public buffer = new ArrayBuffer(0);
|
||||
|
||||
public read = jest.fn((length: number) => this.buffer);
|
||||
|
||||
public encodeUtf8 = jest.fn((input: string) => Buffer.from(input, 'utf-8'));
|
||||
|
||||
public decodeUtf8 = jest.fn((buffer: ArrayBuffer) => Buffer.from(buffer).toString('utf-8'));
|
||||
}
|
||||
|
||||
describe('Struct', () => {
|
||||
describe('.constructor', () => {
|
||||
it('should initialize fields', () => {
|
||||
const struct = new Struct();
|
||||
expect(struct).toHaveProperty('options', StructDefaultOptions);
|
||||
expect(struct).toHaveProperty('size', 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#field', () => {
|
||||
class MockFieldDefinition extends StructFieldDefinition<number>{
|
||||
public constructor(size: number) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
public getSize = jest.fn(() => {
|
||||
return this.options;
|
||||
});
|
||||
|
||||
public create(options: Readonly<StructOptions>, context: StructSerializationContext, object: StructValue, struct: unknown): StructFieldValue<this> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, struct: StructValue): ValueOrPromise<StructFieldValue<this>> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
it('should push a field and update size', () => {
|
||||
const struct = new Struct();
|
||||
|
||||
const field1 = 'foo';
|
||||
const fieldDefinition1 = new MockFieldDefinition(4);
|
||||
|
||||
struct.field(field1, fieldDefinition1);
|
||||
expect(struct).toHaveProperty('size', 4);
|
||||
expect(fieldDefinition1.getSize).toBeCalledTimes(1);
|
||||
expect(struct['_fields']).toEqual([[field1, fieldDefinition1]]);
|
||||
|
||||
const field2 = 'bar';
|
||||
const fieldDefinition2 = new MockFieldDefinition(8);
|
||||
struct.field(field2, fieldDefinition2);
|
||||
expect(struct).toHaveProperty('size', 12);
|
||||
expect(fieldDefinition2.getSize).toBeCalledTimes(1);
|
||||
expect(struct['_fields']).toEqual([
|
||||
[field1, fieldDefinition1],
|
||||
[field2, fieldDefinition2],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should throw an error if field name already exists', () => {
|
||||
const struct = new Struct();
|
||||
const fieldName = 'foo';
|
||||
struct.field(fieldName, new MockFieldDefinition(4));
|
||||
expect(() => struct.field(fieldName, new MockFieldDefinition(4))).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#number', () => {
|
||||
it('`int8` should append an `int8` field', () => {
|
||||
const struct = new Struct();
|
||||
struct.int8('foo');
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
const definition = struct['_fields'][0][1] as NumberFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||
expect(definition.type).toBe(NumberFieldType.Int8);
|
||||
});
|
||||
|
||||
it('`uint8` should append an `uint8` field', () => {
|
||||
const struct = new Struct();
|
||||
struct.uint8('foo');
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
const definition = struct['_fields'][0][1] as NumberFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||
expect(definition.type).toBe(NumberFieldType.Uint8);
|
||||
});
|
||||
|
||||
it('`int16` should append an `int16` field', () => {
|
||||
const struct = new Struct();
|
||||
struct.int16('foo');
|
||||
expect(struct).toHaveProperty('size', 2);
|
||||
|
||||
const definition = struct['_fields'][0][1] as NumberFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||
expect(definition.type).toBe(NumberFieldType.Int16);
|
||||
});
|
||||
|
||||
it('`uint16` should append an `uint16` field', () => {
|
||||
const struct = new Struct();
|
||||
struct.uint16('foo');
|
||||
expect(struct).toHaveProperty('size', 2);
|
||||
|
||||
const definition = struct['_fields'][0][1] as NumberFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||
expect(definition.type).toBe(NumberFieldType.Uint16);
|
||||
});
|
||||
|
||||
it('`int32` should append an `int32` field', () => {
|
||||
const struct = new Struct();
|
||||
struct.int32('foo');
|
||||
expect(struct).toHaveProperty('size', 4);
|
||||
|
||||
const definition = struct['_fields'][0][1] as NumberFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||
expect(definition.type).toBe(NumberFieldType.Int32);
|
||||
});
|
||||
|
||||
it('`uint32` should append an `uint32` field', () => {
|
||||
const struct = new Struct();
|
||||
struct.uint32('foo');
|
||||
expect(struct).toHaveProperty('size', 4);
|
||||
|
||||
const definition = struct['_fields'][0][1] as NumberFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||
expect(definition.type).toBe(NumberFieldType.Uint32);
|
||||
});
|
||||
|
||||
it('`int64` should append an `int64` field', () => {
|
||||
const struct = new Struct();
|
||||
struct.int64('foo');
|
||||
expect(struct).toHaveProperty('size', 8);
|
||||
|
||||
const definition = struct['_fields'][0][1] as NumberFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||
expect(definition.type).toBe(NumberFieldType.Int64);
|
||||
});
|
||||
|
||||
it('`uint64` should append an `uint64` field', () => {
|
||||
const struct = new Struct();
|
||||
struct.uint64('foo');
|
||||
expect(struct).toHaveProperty('size', 8);
|
||||
|
||||
const definition = struct['_fields'][0][1] as NumberFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||
expect(definition.type).toBe(NumberFieldType.Uint64);
|
||||
});
|
||||
|
||||
describe('#arrayBufferLike', () => {
|
||||
describe('FixedLengthArrayBufferLikeFieldDefinition', () => {
|
||||
it('`#arrayBuffer` with fixed length', () => {
|
||||
let struct = new Struct();
|
||||
struct.arrayBuffer('foo', { length: 10 });
|
||||
expect(struct).toHaveProperty('size', 10);
|
||||
|
||||
const definition = struct['_fields'][0][1] as FixedLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(ArrayBufferFieldType);
|
||||
expect(definition.options.length).toBe(10);
|
||||
});
|
||||
|
||||
it('`#uint8ClampedArray` with fixed length', () => {
|
||||
let struct = new Struct();
|
||||
struct.uint8ClampedArray('foo', { length: 10 });
|
||||
expect(struct).toHaveProperty('size', 10);
|
||||
|
||||
const definition = struct['_fields'][0][1] as FixedLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(Uint8ClampedArrayFieldType);
|
||||
expect(definition.options.length).toBe(10);
|
||||
});
|
||||
|
||||
it('`#string` with fixed length', () => {
|
||||
let struct = new Struct();
|
||||
struct.string('foo', { length: 10 });
|
||||
expect(struct).toHaveProperty('size', 10);
|
||||
|
||||
const definition = struct['_fields'][0][1] as FixedLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(StringFieldType);
|
||||
expect(definition.options.length).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
|
||||
it('`#arrayBuffer` with variable length', () => {
|
||||
const struct = new Struct().int8('barLength');
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
struct.arrayBuffer('bar', { lengthField: 'barLength' });
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
const definition = struct['_fields'][1][1] as VariableLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(ArrayBufferFieldType);
|
||||
expect(definition.options.lengthField).toBe('barLength');
|
||||
});
|
||||
|
||||
it('`#uint8ClampedArray` with variable length', () => {
|
||||
const struct = new Struct().int8('barLength');
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
struct.uint8ClampedArray('bar', { lengthField: 'barLength' });
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
const definition = struct['_fields'][1][1] as VariableLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(Uint8ClampedArrayFieldType);
|
||||
expect(definition.options.lengthField).toBe('barLength');
|
||||
});
|
||||
|
||||
|
||||
it('`#string` with variable length', () => {
|
||||
const struct = new Struct().int8('barLength');
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
struct.string('bar', { lengthField: 'barLength' });
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
const definition = struct['_fields'][1][1] as VariableLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(StringFieldType);
|
||||
expect(definition.options.lengthField).toBe('barLength');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#fields', () => {
|
||||
it('should append all fields from other struct', async () => {
|
||||
const sub = new Struct()
|
||||
.int16('int16')
|
||||
.int32('int32');
|
||||
|
||||
const struct = new Struct()
|
||||
.int8('int8')
|
||||
.fields(sub)
|
||||
.int64('int64');
|
||||
|
||||
const field0 = struct['_fields'][0];
|
||||
expect(field0).toHaveProperty('0', 'int8');
|
||||
expect(field0[1]).toHaveProperty('type', NumberFieldType.Int8);
|
||||
|
||||
const field1 = struct['_fields'][1];
|
||||
expect(field1).toHaveProperty('0', 'int16');
|
||||
expect(field1[1]).toHaveProperty('type', NumberFieldType.Int16);
|
||||
|
||||
const field2 = struct['_fields'][2];
|
||||
expect(field2).toHaveProperty('0', 'int32');
|
||||
expect(field2[1]).toHaveProperty('type', NumberFieldType.Int32);
|
||||
|
||||
const field3 = struct['_fields'][3];
|
||||
expect(field3).toHaveProperty('0', 'int64');
|
||||
expect(field3[1]).toHaveProperty('type', NumberFieldType.Int64);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deserialize', () => {
|
||||
it('should deserialize without dynamic size fields', async () => {
|
||||
const struct = new Struct()
|
||||
.int8('foo')
|
||||
.int16('bar');
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
context.read
|
||||
.mockReturnValueOnce(new Uint8Array([2]).buffer)
|
||||
.mockReturnValueOnce(new Uint8Array([0, 16]).buffer);
|
||||
|
||||
const result = await struct.deserialize(context);
|
||||
expect(result).toEqual({ foo: 2, bar: 16 });
|
||||
|
||||
expect(context.read).toBeCalledTimes(2);
|
||||
expect(context.read).nthCalledWith(1, 1);
|
||||
expect(context.read).nthCalledWith(2, 2);
|
||||
});
|
||||
|
||||
it('should deserialize with dynamic size fields', async () => {
|
||||
const struct = new Struct()
|
||||
.int8('fooLength')
|
||||
.uint8ClampedArray('foo', { lengthField: 'fooLength' });
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
context.read
|
||||
.mockReturnValueOnce(new Uint8Array([2]).buffer)
|
||||
.mockReturnValueOnce(new Uint8Array([3, 4]).buffer);
|
||||
|
||||
const result = await struct.deserialize(context);
|
||||
expect(result).toEqual({ fooLength: 2, foo: new Uint8ClampedArray([3, 4]) });
|
||||
expect(context.read).toBeCalledTimes(2);
|
||||
expect(context.read).nthCalledWith(1, 1);
|
||||
expect(context.read).nthCalledWith(2, 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#extra', () => {
|
||||
it('should accept plain field', async () => {
|
||||
const struct = new Struct()
|
||||
.extra({ foo: 42, bar: true });
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const result = await struct.deserialize(context);
|
||||
|
||||
expect(Object.entries(Object.getOwnPropertyDescriptors(result))).toEqual([
|
||||
['foo', { configurable: true, enumerable: true, writable: true, value: 42 }],
|
||||
['bar', { configurable: true, enumerable: true, writable: true, value: true }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should accept accessors', async () => {
|
||||
const struct = new Struct()
|
||||
.extra({
|
||||
get foo() { return 42; },
|
||||
get bar() { return true; },
|
||||
set bar(value) { },
|
||||
});
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const result = await struct.deserialize(context);
|
||||
|
||||
expect(Object.entries(Object.getOwnPropertyDescriptors(result))).toEqual([
|
||||
['foo', { configurable: true, enumerable: true, get: expect.any(Function) }],
|
||||
['bar', { configurable: true, enumerable: true, get: expect.any(Function), set: expect.any(Function) }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#postDeserialize', () => {
|
||||
it('can throw errors', async () => {
|
||||
const struct = new Struct();
|
||||
const callback = jest.fn(() => { throw new Error('mock'); });
|
||||
struct.postDeserialize(callback);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
expect(struct.deserialize(context)).rejects.toThrowError('mock');
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('can replace return value', async () => {
|
||||
const struct = new Struct();
|
||||
const callback = jest.fn(() => 'mock');
|
||||
struct.postDeserialize(callback);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
expect(struct.deserialize(context)).resolves.toBe('mock');
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
expect(callback).toBeCalledWith({});
|
||||
});
|
||||
|
||||
it('can return nothing', async () => {
|
||||
const struct = new Struct();
|
||||
const callback = jest.fn();
|
||||
struct.postDeserialize(callback);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const result = await struct.deserialize(context);
|
||||
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
expect(callback).toBeCalledWith(result);
|
||||
});
|
||||
|
||||
it('should overwrite callback', async () => {
|
||||
const struct = new Struct();
|
||||
|
||||
const callback1 = jest.fn();
|
||||
struct.postDeserialize(callback1);
|
||||
|
||||
const callback2 = jest.fn();
|
||||
struct.postDeserialize(callback2);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
await struct.deserialize(context);
|
||||
|
||||
expect(callback1).toBeCalledTimes(0);
|
||||
expect(callback2).toBeCalledTimes(1);
|
||||
expect(callback2).toBeCalledWith({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#serialize', () => {
|
||||
it('should serialize without dynamic size fields', () => {
|
||||
const struct = new Struct()
|
||||
.int8('foo')
|
||||
.int16('bar');
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const result = new Uint8Array(struct.serialize({ foo: 0x42, bar: 0x1024 }, context));
|
||||
|
||||
expect(result).toEqual(new Uint8Array([0x42, 0x10, 0x24]));
|
||||
});
|
||||
|
||||
it('should serialize with dynamic size fields', () => {
|
||||
const struct = new Struct()
|
||||
.int8('fooLength')
|
||||
.arrayBuffer('foo', { lengthField: 'fooLength' });
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const result = new Uint8Array(struct.serialize({ foo: new Uint8Array([0x03, 0x04, 0x05]).buffer }, context));
|
||||
|
||||
expect(result).toEqual(new Uint8Array([0x03, 0x03, 0x04, 0x05]));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,13 +1,11 @@
|
|||
import { StructDefaultOptions, StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from './basic';
|
||||
import { ArrayBufferFieldType, ArrayBufferLikeFieldType, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types';
|
||||
import { Evaluate, Identity, KeysOfType, Overwrite } from './utils';
|
||||
import { Awaited, Evaluate, Identity, KeysOfType, Overwrite } from './utils';
|
||||
|
||||
export interface StructLike<TValue> {
|
||||
deserialize(context: StructDeserializationContext): Promise<TValue>;
|
||||
}
|
||||
|
||||
export type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T;
|
||||
|
||||
/**
|
||||
* Extract the value type of the specified `Struct`
|
||||
*
|
||||
|
@ -160,17 +158,17 @@ export type StructDeserializedType<TFields extends object, TExtra extends object
|
|||
|
||||
export class Struct<
|
||||
TFields extends object = {},
|
||||
TOmitInit extends string = never,
|
||||
TOmitInitKey extends string = never,
|
||||
TExtra extends object = {},
|
||||
TPostDeserialized = undefined,
|
||||
> implements StructLike<StructDeserializedType<TFields, TExtra, TPostDeserialized>>{
|
||||
public readonly fieldsType!: TFields;
|
||||
|
||||
public readonly omitInitType!: TOmitInit;
|
||||
public readonly omitInitType!: TOmitInitKey;
|
||||
|
||||
public readonly extraType!: TExtra;
|
||||
|
||||
public readonly initType!: Evaluate<Omit<TFields, TOmitInit>>;
|
||||
public readonly initType!: Evaluate<Omit<TFields, TOmitInitKey>>;
|
||||
|
||||
public readonly deserializedType!: StructDeserializedType<TFields, TExtra, TPostDeserialized>;
|
||||
|
||||
|
@ -188,7 +186,7 @@ export class Struct<
|
|||
|
||||
private _postDeserialized?: StructPostDeserialized<any, any>;
|
||||
|
||||
public constructor(options?: Partial<StructOptions>) {
|
||||
public constructor(options?: Partial<Readonly<StructOptions>>) {
|
||||
this.options = { ...StructDefaultOptions, ...options };
|
||||
}
|
||||
|
||||
|
@ -203,12 +201,18 @@ export class Struct<
|
|||
definition: TDefinition,
|
||||
): AddFieldDescriptor<
|
||||
TFields,
|
||||
TOmitInit,
|
||||
TOmitInitKey,
|
||||
TExtra,
|
||||
TPostDeserialized,
|
||||
TName,
|
||||
TDefinition
|
||||
> {
|
||||
for (const field of this._fields) {
|
||||
if (field[0] === name) {
|
||||
throw new Error(`This struct already have a field with name '${name}'`);
|
||||
}
|
||||
}
|
||||
|
||||
this._fields.push([name, definition]);
|
||||
|
||||
const size = definition.getSize();
|
||||
|
@ -225,7 +229,7 @@ export class Struct<
|
|||
other: TOther
|
||||
): Struct<
|
||||
TFields & TOther['fieldsType'],
|
||||
TOmitInit | TOther['omitInitType'],
|
||||
TOmitInitKey | TOther['omitInitType'],
|
||||
TExtra & TOther['extraType'],
|
||||
TPostDeserialized
|
||||
> {
|
||||
|
@ -394,7 +398,7 @@ export class Struct<
|
|||
|
||||
private arrayBufferLike: ArrayBufferLikeFieldCreator<
|
||||
TFields,
|
||||
TOmitInit,
|
||||
TOmitInitKey,
|
||||
TExtra,
|
||||
TPostDeserialized
|
||||
> = (
|
||||
|
@ -417,7 +421,7 @@ export class Struct<
|
|||
|
||||
public arrayBuffer: ArrayBufferTypeFieldDefinitionCreator<
|
||||
TFields,
|
||||
TOmitInit,
|
||||
TOmitInitKey,
|
||||
TExtra,
|
||||
TPostDeserialized,
|
||||
ArrayBufferFieldType
|
||||
|
@ -430,7 +434,7 @@ export class Struct<
|
|||
|
||||
public uint8ClampedArray: ArrayBufferTypeFieldDefinitionCreator<
|
||||
TFields,
|
||||
TOmitInit,
|
||||
TOmitInitKey,
|
||||
TExtra,
|
||||
TPostDeserialized,
|
||||
Uint8ClampedArrayFieldType
|
||||
|
@ -443,7 +447,7 @@ export class Struct<
|
|||
|
||||
public string: ArrayBufferTypeFieldDefinitionCreator<
|
||||
TFields,
|
||||
TOmitInit,
|
||||
TOmitInitKey,
|
||||
TExtra,
|
||||
TPostDeserialized,
|
||||
StringFieldType
|
||||
|
@ -475,7 +479,7 @@ export class Struct<
|
|||
value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>>
|
||||
): Struct<
|
||||
TFields,
|
||||
TOmitInit,
|
||||
TOmitInitKey,
|
||||
Overwrite<TExtra, T>,
|
||||
TPostDeserialized
|
||||
> {
|
||||
|
@ -491,7 +495,7 @@ export class Struct<
|
|||
*/
|
||||
public postDeserialize(
|
||||
callback: StructPostDeserialized<TFields, never>
|
||||
): Struct<TFields, TOmitInit, TExtra, never>;
|
||||
): Struct<TFields, TOmitInitKey, TExtra, never>;
|
||||
/**
|
||||
* Registers (or replaces) a custom callback to be run after deserialized.
|
||||
*
|
||||
|
@ -500,7 +504,7 @@ export class Struct<
|
|||
*/
|
||||
public postDeserialize(
|
||||
callback?: StructPostDeserialized<TFields, void>
|
||||
): Struct<TFields, TOmitInit, TExtra, undefined>;
|
||||
): Struct<TFields, TOmitInitKey, TExtra, undefined>;
|
||||
/**
|
||||
* Registers (or replaces) a custom callback to be run after deserialized.
|
||||
*
|
||||
|
@ -509,7 +513,7 @@ export class Struct<
|
|||
*/
|
||||
public postDeserialize<TPostSerialize>(
|
||||
callback?: StructPostDeserialized<TFields, TPostSerialize>
|
||||
): Struct<TFields, TOmitInit, TExtra, TPostSerialize>;
|
||||
): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;
|
||||
public postDeserialize(
|
||||
callback?: StructPostDeserialized<TFields, any>
|
||||
) {
|
||||
|
@ -517,16 +521,11 @@ export class Struct<
|
|||
return this as any;
|
||||
}
|
||||
|
||||
private initializeStructValue() {
|
||||
const value = new StructValue();
|
||||
Object.defineProperties(value.value, this._extra);
|
||||
return value;
|
||||
}
|
||||
|
||||
public async deserialize(
|
||||
context: StructDeserializationContext
|
||||
): Promise<StructDeserializedType<TFields, TExtra, TPostDeserialized>> {
|
||||
const value = this.initializeStructValue();
|
||||
const value = new StructValue();
|
||||
Object.defineProperties(value.value, this._extra);
|
||||
|
||||
for (const [name, definition] of this._fields) {
|
||||
const fieldValue = await definition.deserialize(this.options, context, value);
|
||||
|
@ -534,7 +533,8 @@ export class Struct<
|
|||
}
|
||||
|
||||
if (this._postDeserialized) {
|
||||
const result = this._postDeserialized.call(value.value as TFields, value as TFields);
|
||||
const object = value.value as TFields;
|
||||
const result = this._postDeserialized.call(object, object);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
@ -543,8 +543,8 @@ export class Struct<
|
|||
return value.value as any;
|
||||
}
|
||||
|
||||
public serialize(init: Evaluate<Omit<TFields, TOmitInit>>, context: StructSerializationContext): ArrayBuffer {
|
||||
const value = this.initializeStructValue();
|
||||
public serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>, context: StructSerializationContext): ArrayBuffer {
|
||||
const value = new StructValue();
|
||||
|
||||
for (const [name, definition] of this._fields) {
|
||||
const fieldValue = definition.create(this.options, context, value, (init as any)[name]);
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
import { StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext, StructValue } from '../basic';
|
||||
import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer';
|
||||
import { StructDefaultOptions, StructDeserializationContext, StructValue } from '../basic';
|
||||
import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer';
|
||||
|
||||
class MockDeserializationContext implements StructDeserializationContext {
|
||||
public buffer = new ArrayBuffer(0);
|
||||
|
||||
public read = jest.fn((length: number) => this.buffer);
|
||||
|
||||
public encodeUtf8 = jest.fn((input: string) => Buffer.from(input, 'utf-8'));
|
||||
|
||||
public decodeUtf8 = jest.fn((buffer: ArrayBuffer) => Buffer.from(buffer).toString('utf-8'));
|
||||
}
|
||||
|
||||
describe('Types', () => {
|
||||
describe('ArrayBufferLike', () => {
|
||||
|
@ -57,12 +67,10 @@ describe('Types', () => {
|
|||
it('`#toArrayBuffer` should return the decoded string', () => {
|
||||
const text = 'foo';
|
||||
const arrayBuffer = Buffer.from(text, 'utf-8');
|
||||
const context: StructSerializationContext = {
|
||||
encodeUtf8(input) {
|
||||
return Buffer.from(input, 'utf-8');
|
||||
},
|
||||
};
|
||||
const context = new MockDeserializationContext();
|
||||
expect(StringFieldType.instance.toArrayBuffer(text, context)).toEqual(arrayBuffer);
|
||||
expect(context.encodeUtf8).toBeCalledTimes(1);
|
||||
expect(context.encodeUtf8).toBeCalledWith(text);
|
||||
});
|
||||
|
||||
it('`#fromArrayBuffer` should return the encoded ArrayBuffer', () => {
|
||||
|
@ -96,41 +104,34 @@ describe('Types', () => {
|
|||
|
||||
describe('ArrayBufferLikeFieldDefinition', () => {
|
||||
it('should work with `ArrayBufferFieldType`', async () => {
|
||||
const buffer = new ArrayBuffer(10);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const struct = new StructValue();
|
||||
|
||||
const size = 10;
|
||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const buffer = new ArrayBuffer(size);
|
||||
context.buffer = buffer;
|
||||
const struct = new StructValue();
|
||||
|
||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||
expect(read).toBeCalledTimes(1);
|
||||
expect(read).toBeCalledWith(size);
|
||||
expect(context.read).toBeCalledTimes(1);
|
||||
expect(context.read).toBeCalledWith(size);
|
||||
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
|
||||
|
||||
const value = fieldValue.get();
|
||||
expect(value).toBe(buffer);
|
||||
expect(fieldValue.get()).toBe(buffer);
|
||||
});
|
||||
|
||||
it('should work with `Uint8ClampedArrayFieldType`', async () => {
|
||||
const buffer = new ArrayBuffer(10);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const struct = new StructValue();
|
||||
|
||||
const size = 10;
|
||||
const definition = new MockArrayBufferFieldDefinition(Uint8ClampedArrayFieldType.instance, size);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const buffer = new ArrayBuffer(size);
|
||||
context.buffer = buffer;
|
||||
const struct = new StructValue();
|
||||
|
||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||
expect(read).toBeCalledTimes(1);
|
||||
expect(read).toBeCalledWith(10);
|
||||
expect(context.read).toBeCalledTimes(1);
|
||||
expect(context.read).toBeCalledWith(size);
|
||||
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
|
||||
|
||||
const value = fieldValue.get();
|
||||
|
@ -139,21 +140,18 @@ describe('Types', () => {
|
|||
});
|
||||
|
||||
it('should work when `#getSize` returns `0`', async () => {
|
||||
const buffer = new ArrayBuffer(10);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const struct = new StructValue();
|
||||
|
||||
const size = 0;
|
||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const buffer = new ArrayBuffer(size);
|
||||
context.buffer = buffer;
|
||||
const struct = new StructValue();
|
||||
|
||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||
expect(read).toBeCalledTimes(0);
|
||||
expect(fieldValue.arrayBuffer).toBeInstanceOf(ArrayBuffer);
|
||||
expect(fieldValue.arrayBuffer).toHaveProperty('byteLength', 0);
|
||||
expect(context.read).toBeCalledTimes(0);
|
||||
expect(fieldValue['arrayBuffer']).toBeInstanceOf(ArrayBuffer);
|
||||
expect(fieldValue['arrayBuffer']).toHaveProperty('byteLength', 0);
|
||||
|
||||
const value = fieldValue.get();
|
||||
expect(value).toBeInstanceOf(ArrayBuffer);
|
||||
|
@ -164,36 +162,34 @@ describe('Types', () => {
|
|||
describe('ArrayBufferLikeFieldValue', () => {
|
||||
describe('#set', () => {
|
||||
it('should clear `arrayBuffer` field', async () => {
|
||||
const size = 10;
|
||||
const size = 0;
|
||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
context.buffer = buffer;
|
||||
const struct = new StructValue();
|
||||
|
||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||
|
||||
fieldValue.set(new ArrayBuffer(20));
|
||||
const newValue = new ArrayBuffer(20);
|
||||
fieldValue.set(newValue);
|
||||
expect(fieldValue.get()).toBe(newValue);
|
||||
expect(fieldValue).toHaveProperty('arrayBuffer', undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#serialize', () => {
|
||||
it('should be able to serialize a deserialized value', async () => {
|
||||
const size = 10;
|
||||
it('should be able to serialize with cached `arrayBuffer`', async () => {
|
||||
const size = 0;
|
||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
|
||||
const sourceBuffer = sourceArray.buffer;
|
||||
const read = jest.fn((length: number) => sourceBuffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const buffer = sourceArray.buffer;
|
||||
context.buffer = buffer;
|
||||
const struct = new StructValue();
|
||||
|
||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||
|
||||
const targetArray = new Uint8Array(size);
|
||||
|
@ -204,18 +200,17 @@ describe('Types', () => {
|
|||
});
|
||||
|
||||
it('should be able to serialize a modified value', async () => {
|
||||
const size = 10;
|
||||
const size = 0;
|
||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
|
||||
const sourceBuffer = sourceArray.buffer;
|
||||
const read = jest.fn((length: number) => sourceBuffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const buffer = sourceArray.buffer;
|
||||
context.buffer = buffer;
|
||||
const struct = new StructValue();
|
||||
|
||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||
|
||||
fieldValue.set(sourceArray.buffer);
|
||||
|
||||
const targetArray = new Uint8Array(size);
|
||||
|
|
|
@ -127,8 +127,9 @@ export abstract class ArrayBufferLikeFieldDefinition<
|
|||
context: StructSerializationContext,
|
||||
struct: StructValue,
|
||||
value: TType['valueType'],
|
||||
arrayBuffer?: ArrayBuffer,
|
||||
): ArrayBufferLikeFieldValue<this> {
|
||||
return new ArrayBufferLikeFieldValue(this, options, context, struct, value);
|
||||
return new ArrayBufferLikeFieldValue(this, options, context, struct, value, arrayBuffer);
|
||||
}
|
||||
|
||||
public async deserialize(
|
||||
|
@ -146,16 +147,26 @@ export abstract class ArrayBufferLikeFieldDefinition<
|
|||
}
|
||||
|
||||
const value = this.type.fromArrayBuffer(arrayBuffer, context);
|
||||
const fieldValue = this.create(options, context, struct, value);
|
||||
fieldValue.arrayBuffer = arrayBuffer;
|
||||
return fieldValue;
|
||||
return this.create(options, context, struct, value, arrayBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrayBufferLikeFieldValue<
|
||||
TDefinition extends ArrayBufferLikeFieldDefinition<ArrayBufferLikeFieldType, any, any>,
|
||||
> extends StructFieldValue<TDefinition> {
|
||||
public arrayBuffer: ArrayBuffer | undefined;
|
||||
protected arrayBuffer: ArrayBuffer | undefined;
|
||||
|
||||
public constructor(
|
||||
definition: TDefinition,
|
||||
options: Readonly<StructOptions>,
|
||||
context: StructSerializationContext,
|
||||
struct: StructValue,
|
||||
value: TDefinition['valueType'],
|
||||
arrayBuffer?: ArrayBuffer,
|
||||
) {
|
||||
super(definition, options, context, struct, value);
|
||||
this.arrayBuffer = arrayBuffer;
|
||||
}
|
||||
|
||||
public set(value: TDefinition['valueType']): void {
|
||||
super.set(value);
|
||||
|
|
|
@ -5,7 +5,10 @@ describe('Types', () => {
|
|||
describe('FixedLengthArrayBufferLikeFieldDefinition', () => {
|
||||
describe('#getSize', () => {
|
||||
it('should return size in its options', () => {
|
||||
const definition = new FixedLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { length: 10 });
|
||||
const definition = new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ length: 10 },
|
||||
);
|
||||
expect(definition.getSize()).toBe(10);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,467 +1,556 @@
|
|||
import { StructDefaultOptions, StructDeserializationContext, StructValue } from '../basic';
|
||||
import { ArrayBufferFieldType, ArrayBufferLikeFieldValue, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer';
|
||||
import { FixedLengthArrayBufferLikeFieldDefinition } from './fixed-length-array-buffer';
|
||||
import { NumberFieldDefinition, NumberFieldType, NumberFieldValue } from './number';
|
||||
import { VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeLengthStructFieldValue, VariableLengthArrayBufferLikeStructFieldValue } from './variable-length-array-buffer';
|
||||
import { StructDefaultOptions, StructDeserializationContext, StructFieldValue, StructSerializationContext, StructValue } from '../basic';
|
||||
import { ArrayBufferFieldType, ArrayBufferLikeFieldType } from './array-buffer';
|
||||
import { VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldLengthValue, VariableLengthArrayBufferLikeStructFieldValue } from './variable-length-array-buffer';
|
||||
|
||||
class MockDeserializationContext implements StructDeserializationContext {
|
||||
public buffer = new ArrayBuffer(0);
|
||||
|
||||
public read = jest.fn((length: number) => this.buffer);
|
||||
|
||||
public encodeUtf8 = jest.fn((input: string) => Buffer.from(input, 'utf-8'));
|
||||
|
||||
public decodeUtf8 = jest.fn((buffer: ArrayBuffer) => Buffer.from(buffer).toString('utf-8'));
|
||||
}
|
||||
|
||||
class MockOriginalFieldValue extends StructFieldValue {
|
||||
public constructor() {
|
||||
super({} as any, {} as any, {} as any, {} as any, {});
|
||||
}
|
||||
|
||||
public value: string | number = 0;
|
||||
|
||||
public get = jest.fn((): string | number => this.value);
|
||||
|
||||
public size = 0;
|
||||
|
||||
public getSize = jest.fn((): number => this.size);
|
||||
|
||||
public set = jest.fn((value: string | number) => { });
|
||||
|
||||
public serialize = jest.fn((dataView: DataView, offset: number, context: StructSerializationContext): void => { });
|
||||
}
|
||||
|
||||
describe('Types', () => {
|
||||
describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
|
||||
describe('VariableLengthArrayBufferLikeFieldLengthValue', () => {
|
||||
class MockArrayBufferFieldValue extends StructFieldValue {
|
||||
public constructor() {
|
||||
super({} as any, {} as any, {} as any, {} as any, {});
|
||||
}
|
||||
|
||||
public size = 0;
|
||||
|
||||
public getSize = jest.fn(() => this.size);
|
||||
|
||||
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
describe('#getSize', () => {
|
||||
it('should always return `0`', () => {
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { lengthField: 'foo' });
|
||||
expect(definition.getSize()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getDeserializeSize', () => {
|
||||
it('should return value of its `lengthField`', async () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { lengthField });
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const struct = new StructValue();
|
||||
struct.set(
|
||||
lengthField,
|
||||
new NumberFieldValue(
|
||||
new NumberFieldDefinition(
|
||||
NumberFieldType.Int8
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size,
|
||||
),
|
||||
it('should return size of its original field value', () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||
expect(read).toBeCalledTimes(1);
|
||||
expect(read).toBeCalledWith(size);
|
||||
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
|
||||
mockOriginalFieldValue.size = 0;
|
||||
expect(lengthFieldValue.getSize()).toBe(0);
|
||||
expect(mockOriginalFieldValue.getSize).toBeCalledTimes(1);
|
||||
|
||||
const value = fieldValue.get();
|
||||
expect(value).toBe(buffer);
|
||||
mockOriginalFieldValue.getSize.mockClear();
|
||||
mockOriginalFieldValue.size = 100;
|
||||
expect(lengthFieldValue.getSize()).toBe(100);
|
||||
expect(mockOriginalFieldValue.getSize).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return value of its `lengthField` as number', async () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { lengthField });
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const struct = new StructValue();
|
||||
struct.set(
|
||||
lengthField,
|
||||
new ArrayBufferLikeFieldValue(
|
||||
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
StringFieldType.instance,
|
||||
{ length: 2 },
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size.toString()
|
||||
),
|
||||
describe('#get', () => {
|
||||
it('should return size of its `arrayBufferField`', async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||
expect(read).toBeCalledTimes(1);
|
||||
expect(read).toBeCalledWith(size);
|
||||
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
|
||||
mockOriginalFieldValue.value = 0;
|
||||
mockArrayBufferFieldValue.size = 0;
|
||||
expect(lengthFieldValue.get()).toBe(0);
|
||||
expect(mockArrayBufferFieldValue.getSize).toBeCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.get).toBeCalledTimes(1);
|
||||
|
||||
const value = fieldValue.get();
|
||||
expect(value).toBe(buffer);
|
||||
mockArrayBufferFieldValue.getSize.mockClear();
|
||||
mockOriginalFieldValue.get.mockClear();
|
||||
mockArrayBufferFieldValue.size = 100;
|
||||
expect(lengthFieldValue.get()).toBe(100);
|
||||
expect(mockArrayBufferFieldValue.getSize).toBeCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.get).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return size of its `arrayBufferField` as string', async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
||||
mockOriginalFieldValue.value = '0';
|
||||
mockArrayBufferFieldValue.size = 0;
|
||||
expect(lengthFieldValue.get()).toBe('0');
|
||||
expect(mockArrayBufferFieldValue.getSize).toBeCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.get).toBeCalledTimes(1);
|
||||
|
||||
mockArrayBufferFieldValue.getSize.mockClear();
|
||||
mockOriginalFieldValue.get.mockClear();
|
||||
mockArrayBufferFieldValue.size = 100;
|
||||
expect(lengthFieldValue.get()).toBe('100');
|
||||
expect(mockArrayBufferFieldValue.getSize).toBeCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.get).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#set', () => {
|
||||
it('should does nothing', async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
||||
mockOriginalFieldValue.value = 0;
|
||||
mockArrayBufferFieldValue.size = 0;
|
||||
expect(lengthFieldValue.get()).toBe(0);
|
||||
|
||||
(lengthFieldValue as StructFieldValue).set(100);
|
||||
expect(lengthFieldValue.get()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#serialize', () => {
|
||||
it('should call `serialize` of its `originalField`', async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
||||
let dataView = 0 as any;
|
||||
let offset = 1 as any;
|
||||
let context = 2 as any;
|
||||
|
||||
mockArrayBufferFieldValue.size = 0;
|
||||
lengthFieldValue.serialize(dataView, offset, context);
|
||||
expect(mockOriginalFieldValue.set).toBeCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.set).toBeCalledWith(0);
|
||||
expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context);
|
||||
|
||||
mockOriginalFieldValue.set.mockClear();
|
||||
mockOriginalFieldValue.serialize.mockClear();
|
||||
mockArrayBufferFieldValue.size = 100;
|
||||
lengthFieldValue.serialize(dataView, offset, context);
|
||||
expect(mockOriginalFieldValue.set).toBeCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.set).toBeCalledWith(100);
|
||||
expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('VariableLengthArrayBufferLikeStructFieldValue', () => {
|
||||
describe('.constructor', () => {
|
||||
it('should replace `lengthField` on `struct`', () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
it('should forward parameters', () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
StringFieldType.instance,
|
||||
{ length: 2 },
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size.toString()
|
||||
);
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
struct.set(lengthField, originalLengthFieldValue,);
|
||||
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ lengthField },
|
||||
);
|
||||
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
definition,
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const value = new ArrayBuffer(0);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
buffer,
|
||||
value,
|
||||
);
|
||||
|
||||
expect(fieldValue).toHaveProperty('definition', definition);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('definition', arrayBufferFieldDefinition);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('context', context);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('value', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('length', undefined);
|
||||
});
|
||||
|
||||
expect(struct.fieldValues[lengthField]).not.toBe(originalLengthFieldValue);
|
||||
it('should forward parameters with `arrayBuffer`', () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const value = new ArrayBuffer(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
value,
|
||||
value,
|
||||
);
|
||||
|
||||
expect(arrayBufferFieldValue).toHaveProperty('definition', arrayBufferFieldDefinition);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('context', context);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('value', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
||||
});
|
||||
|
||||
it('should replace `lengthField` on `struct`', () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const value = new ArrayBuffer(0);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
value,
|
||||
);
|
||||
|
||||
expect(arrayBufferFieldValue['lengthFieldValue']).toBeInstanceOf(StructFieldValue);
|
||||
expect(struct.fieldValues[lengthField]).toBe(arrayBufferFieldValue['lengthFieldValue']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getSize', () => {
|
||||
it('should return size of `arrayBuffer` if exist', async () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
class MockArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer> {
|
||||
public toArrayBuffer = jest.fn((value: ArrayBuffer, context: StructSerializationContext): ArrayBuffer => {
|
||||
return value;
|
||||
});
|
||||
|
||||
public fromArrayBuffer = jest.fn((arrayBuffer: ArrayBuffer, context: StructDeserializationContext): ArrayBuffer => {
|
||||
return arrayBuffer;
|
||||
});
|
||||
|
||||
public size = 0;
|
||||
|
||||
public getSize = jest.fn((value: ArrayBuffer): number => {
|
||||
return this.size;
|
||||
});
|
||||
}
|
||||
|
||||
it('should return cached size if exist', async () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
StringFieldType.instance,
|
||||
{ length: 2 },
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size.toString()
|
||||
);
|
||||
struct.set(lengthField, originalLengthFieldValue,);
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
arrayBufferFieldType,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||
expect(fieldValue.getSize()).toBe(size);
|
||||
const context = new MockDeserializationContext();
|
||||
const value = new ArrayBuffer(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
value,
|
||||
value,
|
||||
);
|
||||
|
||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should call `getSize` of its `type`', () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
const buffer = new ArrayBuffer(size);
|
||||
|
||||
const context: StructDeserializationContext = {
|
||||
read(length) { throw new Error(''); },
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const struct = new StructValue();
|
||||
|
||||
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
StringFieldType.instance,
|
||||
{ length: 2 },
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size.toString()
|
||||
);
|
||||
struct.set(lengthField, originalLengthFieldValue,);
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
arrayBufferFieldType,
|
||||
{ lengthField },
|
||||
),
|
||||
);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const value = new ArrayBuffer(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
buffer,
|
||||
value,
|
||||
);
|
||||
|
||||
expect(fieldValue.getSize()).toBe(size);
|
||||
arrayBufferFieldType.size = 100;
|
||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
||||
});
|
||||
|
||||
it('should call `toArrayBuffer` of its `type` if it does not support `getSize`', () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
const context: StructDeserializationContext = {
|
||||
read(length) { throw new Error(''); },
|
||||
encodeUtf8(input) {
|
||||
return Buffer.from(input, 'utf-8');
|
||||
},
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const struct = new StructValue();
|
||||
|
||||
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
StringFieldType.instance,
|
||||
{ length: 2 },
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size.toString()
|
||||
);
|
||||
struct.set(lengthField, originalLengthFieldValue,);
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
StringFieldType.instance,
|
||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
arrayBufferFieldType,
|
||||
{ lengthField },
|
||||
),
|
||||
);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const value = new ArrayBuffer(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
'test',
|
||||
value,
|
||||
);
|
||||
|
||||
expect(fieldValue.getSize()).toBe(4);
|
||||
arrayBufferFieldType.size = -1;
|
||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(1);
|
||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#set', () => {
|
||||
it('should store value', () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
|
||||
const array = new Uint8ClampedArray(size);
|
||||
const buffer = array.buffer;
|
||||
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
it('should call `ArrayBufferLikeFieldValue#set`', () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
StringFieldType.instance,
|
||||
{ length: 2 },
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size.toString()
|
||||
);
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
struct.set(lengthField, originalLengthFieldValue,);
|
||||
|
||||
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
Uint8ClampedArrayFieldType.instance,
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ lengthField },
|
||||
),
|
||||
);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const value = new ArrayBuffer(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
new Uint8ClampedArray(buffer),
|
||||
value,
|
||||
value,
|
||||
);
|
||||
|
||||
const newArray = new Uint8ClampedArray(size);
|
||||
fieldValue.set(newArray);
|
||||
expect(fieldValue.get()).toBe(newArray);
|
||||
const newValue = new ArrayBuffer(100);
|
||||
arrayBufferFieldValue.set(newValue);
|
||||
expect(arrayBufferFieldValue.get()).toBe(newValue);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
|
||||
});
|
||||
|
||||
it('should clear length', () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
|
||||
const array = new Uint8ClampedArray(size);
|
||||
const buffer = array.buffer;
|
||||
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const struct = new StructValue();
|
||||
|
||||
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
StringFieldType.instance,
|
||||
{ length: 2 },
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size.toString()
|
||||
);
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
struct.set(lengthField, originalLengthFieldValue,);
|
||||
|
||||
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
Uint8ClampedArrayFieldType.instance,
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ lengthField },
|
||||
),
|
||||
);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const value = new ArrayBuffer(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
new Uint8ClampedArray(buffer),
|
||||
value,
|
||||
value,
|
||||
);
|
||||
|
||||
const newArray = new Uint8ClampedArray(size);
|
||||
fieldValue.set(newArray);
|
||||
|
||||
expect(fieldValue['length']).toBeUndefined();
|
||||
const newValue = new ArrayBuffer(100);
|
||||
arrayBufferFieldValue.set(newValue);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('length', undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('VariableLengthArrayBufferLikeLengthStructFieldValue', () => {
|
||||
describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
|
||||
describe('#getSize', () => {
|
||||
it('should return size of its original field value', () => {
|
||||
it('should always return `0`', () => {
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ lengthField: 'foo' },
|
||||
);
|
||||
expect(definition.getSize()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getDeserializeSize', () => {
|
||||
it('should return value of its `lengthField`', async () => {
|
||||
const struct = new StructValue();
|
||||
const originalFieldValue = new NumberFieldValue(
|
||||
new NumberFieldDefinition(
|
||||
NumberFieldType.Int8
|
||||
),
|
||||
StructDefaultOptions,
|
||||
{} as any,
|
||||
struct,
|
||||
42,
|
||||
);
|
||||
const fieldValue = new VariableLengthArrayBufferLikeLengthStructFieldValue(
|
||||
originalFieldValue,
|
||||
{} as any,
|
||||
);
|
||||
|
||||
expect(fieldValue.getSize()).toBe(originalFieldValue.getSize());
|
||||
});
|
||||
});
|
||||
|
||||
describe('#get', () => {
|
||||
it('should return size of its `arrayBufferField`', async () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
const struct = new StructValue();
|
||||
|
||||
const originalLengthFieldValue = new NumberFieldValue(
|
||||
new NumberFieldDefinition(
|
||||
NumberFieldType.Int32
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size
|
||||
);
|
||||
struct.set(lengthField, originalLengthFieldValue,);
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const fieldValue = (await definition.deserialize(StructDefaultOptions, context, struct)) as any as VariableLengthArrayBufferLikeStructFieldValue;
|
||||
expect(fieldValue['lengthFieldValue'].get()).toBe(size);
|
||||
originalLengthFieldValue.value = 0;
|
||||
expect(definition['getDeserializeSize'](struct)).toBe(0);
|
||||
expect(originalLengthFieldValue.get).toBeCalledTimes(1);
|
||||
|
||||
originalLengthFieldValue.get.mockClear();
|
||||
originalLengthFieldValue.value = 100;
|
||||
expect(definition['getDeserializeSize'](struct)).toBe(100);
|
||||
expect(originalLengthFieldValue.get).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return size of its `arrayBufferField` as string', async () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) { throw new Error(''); },
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
it('should return value of its `lengthField` as number', async () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
StringFieldType.instance,
|
||||
{ length: 2 },
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size.toString()
|
||||
);
|
||||
struct.set(lengthField, originalLengthFieldValue,);
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const fieldValue = (await definition.deserialize(StructDefaultOptions, context, struct)) as any as VariableLengthArrayBufferLikeStructFieldValue;
|
||||
expect(fieldValue['lengthFieldValue'].get()).toBe(size.toString());
|
||||
originalLengthFieldValue.value = '0';
|
||||
expect(definition['getDeserializeSize'](struct)).toBe(0);
|
||||
expect(originalLengthFieldValue.get).toBeCalledTimes(1);
|
||||
|
||||
originalLengthFieldValue.get.mockClear();
|
||||
originalLengthFieldValue.value = '100';
|
||||
expect(definition['getDeserializeSize'](struct)).toBe(100);
|
||||
expect(originalLengthFieldValue.get).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#serialize', () => {
|
||||
it('should call `serialize` of its `originalField`', async () => {
|
||||
const lengthField = 'foo';
|
||||
const size = 10;
|
||||
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const read = jest.fn((length: number) => buffer);
|
||||
const context: StructDeserializationContext = {
|
||||
read,
|
||||
encodeUtf8(input) {
|
||||
return Buffer.from(input, 'utf-8');
|
||||
},
|
||||
decodeUtf8(buffer) { throw new Error(''); },
|
||||
};
|
||||
|
||||
describe('#create', () => {
|
||||
it('should create a `VariableLengthArrayBufferLikeFieldValue`', () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
StringFieldType.instance,
|
||||
{ length: 2 },
|
||||
),
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
size.toString()
|
||||
);
|
||||
struct.set(lengthField, originalLengthFieldValue,);
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const fieldValue = (await definition.deserialize(StructDefaultOptions, context, struct)) as any as VariableLengthArrayBufferLikeStructFieldValue;
|
||||
const context = new MockDeserializationContext();
|
||||
const value = new ArrayBuffer(100);
|
||||
const arrayBufferFieldValue = definition.create(
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
value,
|
||||
);
|
||||
|
||||
const targetArray = new Uint8Array(2);
|
||||
const targetView = new DataView(targetArray.buffer);
|
||||
fieldValue['lengthFieldValue'].serialize(targetView, 0, context);
|
||||
expect(targetArray).toEqual(new Uint8Array('10'.split('').map(c => c.charCodeAt(0))));
|
||||
expect(arrayBufferFieldValue).toHaveProperty('definition', definition);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('context', context);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('value', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('length', undefined);
|
||||
});
|
||||
|
||||
it('should create a `VariableLengthArrayBufferLikeFieldValue` with `arrayBuffer`', () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const lengthField = 'foo';
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const context = new MockDeserializationContext();
|
||||
const value = new ArrayBuffer(100);
|
||||
const arrayBufferFieldValue = definition.create(
|
||||
StructDefaultOptions,
|
||||
context,
|
||||
struct,
|
||||
value,
|
||||
value,
|
||||
);
|
||||
|
||||
expect(arrayBufferFieldValue).toHaveProperty('definition', definition);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('context', context);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('value', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic';
|
||||
import { KeysOfType } from '../utils';
|
||||
import type { KeysOfType } from '../utils';
|
||||
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer';
|
||||
|
||||
export interface VariableLengthArrayBufferLikeFieldOptions<
|
||||
|
@ -34,8 +34,16 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
|
|||
context: StructSerializationContext,
|
||||
struct: StructValue,
|
||||
value: TType['valueType'],
|
||||
arrayBuffer?: ArrayBuffer
|
||||
): VariableLengthArrayBufferLikeStructFieldValue<this> {
|
||||
return new VariableLengthArrayBufferLikeStructFieldValue(this, options, context, struct, value);
|
||||
return new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
this,
|
||||
options,
|
||||
context,
|
||||
struct,
|
||||
value,
|
||||
arrayBuffer,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +52,7 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
|
|||
> extends ArrayBufferLikeFieldValue<TDefinition> {
|
||||
protected length: number | undefined;
|
||||
|
||||
protected lengthFieldValue: VariableLengthArrayBufferLikeLengthStructFieldValue;
|
||||
protected lengthFieldValue: VariableLengthArrayBufferLikeFieldLengthValue;
|
||||
|
||||
public constructor(
|
||||
definition: TDefinition,
|
||||
|
@ -52,14 +60,19 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
|
|||
context: StructSerializationContext,
|
||||
struct: StructValue,
|
||||
value: TDefinition['valueType'],
|
||||
arrayBuffer?: ArrayBuffer,
|
||||
) {
|
||||
super(definition, options, context, struct, value);
|
||||
super(definition, options, context, struct, value, arrayBuffer);
|
||||
|
||||
if (arrayBuffer) {
|
||||
this.length = arrayBuffer.byteLength;
|
||||
}
|
||||
|
||||
// Patch the associated length field.
|
||||
const lengthField = this.definition.options.lengthField;
|
||||
|
||||
const originalValue = struct.get(lengthField);
|
||||
this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthStructFieldValue(
|
||||
this.lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
originalValue,
|
||||
this,
|
||||
);
|
||||
|
@ -68,35 +81,32 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
|
|||
|
||||
public getSize() {
|
||||
if (this.length === undefined) {
|
||||
if (this.arrayBuffer !== undefined) {
|
||||
this.length = this.arrayBuffer.byteLength;
|
||||
} else {
|
||||
this.length = this.definition.type.getSize(this.value);
|
||||
if (this.length === -1) {
|
||||
this.arrayBuffer = this.definition.type.toArrayBuffer(this.value, this.context);
|
||||
this.length = this.arrayBuffer.byteLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.length;
|
||||
}
|
||||
|
||||
public set(value: unknown) {
|
||||
super.set(value);
|
||||
this.arrayBuffer = undefined;
|
||||
this.length = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class VariableLengthArrayBufferLikeLengthStructFieldValue
|
||||
export class VariableLengthArrayBufferLikeFieldLengthValue
|
||||
extends StructFieldValue {
|
||||
protected originalField: StructFieldValue;
|
||||
|
||||
protected arrayBufferField: VariableLengthArrayBufferLikeStructFieldValue;
|
||||
protected arrayBufferField: StructFieldValue;
|
||||
|
||||
public constructor(
|
||||
originalField: StructFieldValue,
|
||||
arrayBufferField: VariableLengthArrayBufferLikeStructFieldValue,
|
||||
arrayBufferField: StructFieldValue,
|
||||
) {
|
||||
super(originalField.definition, originalField.options, originalField.context, originalField.struct, 0);
|
||||
this.originalField = originalField;
|
||||
|
|
|
@ -41,6 +41,7 @@ export type KeysOfType<T, TValue> =
|
|||
|
||||
export type ValueOrPromise<T> = T | Promise<T>;
|
||||
|
||||
export type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T;
|
||||
|
||||
/**
|
||||
* Returns a (fake) value of the given type.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"composite": true,
|
||||
/* Basic Options */
|
||||
"target": "ES5", // /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"target": "ES2015", // /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "ESNext", // /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"lib": [ // /* Specify library files to be included in the compilation. */
|
||||
"ESNext"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue