test(struct): 100% code coverage

This commit is contained in:
Simon Chan 2021-01-17 19:57:34 +08:00
parent b71f9714b2
commit 83910f1a35
12 changed files with 985 additions and 466 deletions

View file

@ -1,8 +1,8 @@
import { ValueOrPromise } from '../utils'; import type { ValueOrPromise } from '../utils';
import { StructDeserializationContext, StructOptions, StructSerializationContext } from './context'; import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
import { StructFieldDefinition } from './definition'; import { StructFieldDefinition } from './definition';
import { StructFieldValue } from './field-value'; import type { StructFieldValue } from './field-value';
import { StructValue } from './struct-value'; import type { StructValue } from './struct-value';
describe('StructFieldDefinition', () => { describe('StructFieldDefinition', () => {
describe('.constructor', () => { describe('.constructor', () => {

View file

@ -1,8 +1,8 @@
import { ValueOrPromise } from '../utils'; import type { ValueOrPromise } from '../utils';
import { StructDeserializationContext, StructOptions, StructSerializationContext } from './context'; import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
import { StructFieldDefinition } from './definition'; import { StructFieldDefinition } from './definition';
import { StructFieldValue } from './field-value'; import { StructFieldValue } from './field-value';
import { StructValue } from './struct-value'; import type { StructValue } from './struct-value';
describe('StructFieldValue', () => { describe('StructFieldValue', () => {
describe('.constructor', () => { describe('.constructor', () => {

View file

@ -7,8 +7,11 @@ describe('StructValue', () => {
const bar = new StructValue(); const bar = new StructValue();
expect(foo).toHaveProperty('fieldValues', {}); expect(foo).toHaveProperty('fieldValues', {});
expect(foo).toHaveProperty('value', {});
expect(bar).toHaveProperty('fieldValues', {}); expect(bar).toHaveProperty('fieldValues', {});
expect(bar).toHaveProperty('value', {});
expect(foo.fieldValues).not.toBe(bar.fieldValues); expect(foo.fieldValues).not.toBe(bar.fieldValues);
expect(foo.value).not.toBe(bar.fieldValues);
}); });
}); });

View 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]));
});
});
});
});

View file

@ -1,13 +1,11 @@
import { StructDefaultOptions, StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from './basic'; import { StructDefaultOptions, StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from './basic';
import { ArrayBufferFieldType, ArrayBufferLikeFieldType, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types'; 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> { export interface StructLike<TValue> {
deserialize(context: StructDeserializationContext): Promise<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` * Extract the value type of the specified `Struct`
* *
@ -160,17 +158,17 @@ export type StructDeserializedType<TFields extends object, TExtra extends object
export class Struct< export class Struct<
TFields extends object = {}, TFields extends object = {},
TOmitInit extends string = never, TOmitInitKey extends string = never,
TExtra extends object = {}, TExtra extends object = {},
TPostDeserialized = undefined, TPostDeserialized = undefined,
> implements StructLike<StructDeserializedType<TFields, TExtra, TPostDeserialized>>{ > implements StructLike<StructDeserializedType<TFields, TExtra, TPostDeserialized>>{
public readonly fieldsType!: TFields; public readonly fieldsType!: TFields;
public readonly omitInitType!: TOmitInit; public readonly omitInitType!: TOmitInitKey;
public readonly extraType!: TExtra; 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>; public readonly deserializedType!: StructDeserializedType<TFields, TExtra, TPostDeserialized>;
@ -188,7 +186,7 @@ export class Struct<
private _postDeserialized?: StructPostDeserialized<any, any>; private _postDeserialized?: StructPostDeserialized<any, any>;
public constructor(options?: Partial<StructOptions>) { public constructor(options?: Partial<Readonly<StructOptions>>) {
this.options = { ...StructDefaultOptions, ...options }; this.options = { ...StructDefaultOptions, ...options };
} }
@ -203,12 +201,18 @@ export class Struct<
definition: TDefinition, definition: TDefinition,
): AddFieldDescriptor< ): AddFieldDescriptor<
TFields, TFields,
TOmitInit, TOmitInitKey,
TExtra, TExtra,
TPostDeserialized, TPostDeserialized,
TName, TName,
TDefinition 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]); this._fields.push([name, definition]);
const size = definition.getSize(); const size = definition.getSize();
@ -225,7 +229,7 @@ export class Struct<
other: TOther other: TOther
): Struct< ): Struct<
TFields & TOther['fieldsType'], TFields & TOther['fieldsType'],
TOmitInit | TOther['omitInitType'], TOmitInitKey | TOther['omitInitType'],
TExtra & TOther['extraType'], TExtra & TOther['extraType'],
TPostDeserialized TPostDeserialized
> { > {
@ -394,7 +398,7 @@ export class Struct<
private arrayBufferLike: ArrayBufferLikeFieldCreator< private arrayBufferLike: ArrayBufferLikeFieldCreator<
TFields, TFields,
TOmitInit, TOmitInitKey,
TExtra, TExtra,
TPostDeserialized TPostDeserialized
> = ( > = (
@ -417,7 +421,7 @@ export class Struct<
public arrayBuffer: ArrayBufferTypeFieldDefinitionCreator< public arrayBuffer: ArrayBufferTypeFieldDefinitionCreator<
TFields, TFields,
TOmitInit, TOmitInitKey,
TExtra, TExtra,
TPostDeserialized, TPostDeserialized,
ArrayBufferFieldType ArrayBufferFieldType
@ -430,7 +434,7 @@ export class Struct<
public uint8ClampedArray: ArrayBufferTypeFieldDefinitionCreator< public uint8ClampedArray: ArrayBufferTypeFieldDefinitionCreator<
TFields, TFields,
TOmitInit, TOmitInitKey,
TExtra, TExtra,
TPostDeserialized, TPostDeserialized,
Uint8ClampedArrayFieldType Uint8ClampedArrayFieldType
@ -443,7 +447,7 @@ export class Struct<
public string: ArrayBufferTypeFieldDefinitionCreator< public string: ArrayBufferTypeFieldDefinitionCreator<
TFields, TFields,
TOmitInit, TOmitInitKey,
TExtra, TExtra,
TPostDeserialized, TPostDeserialized,
StringFieldType StringFieldType
@ -475,7 +479,7 @@ export class Struct<
value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>> value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>>
): Struct< ): Struct<
TFields, TFields,
TOmitInit, TOmitInitKey,
Overwrite<TExtra, T>, Overwrite<TExtra, T>,
TPostDeserialized TPostDeserialized
> { > {
@ -491,7 +495,7 @@ export class Struct<
*/ */
public postDeserialize( public postDeserialize(
callback: StructPostDeserialized<TFields, never> 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. * Registers (or replaces) a custom callback to be run after deserialized.
* *
@ -500,7 +504,7 @@ export class Struct<
*/ */
public postDeserialize( public postDeserialize(
callback?: StructPostDeserialized<TFields, void> 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. * Registers (or replaces) a custom callback to be run after deserialized.
* *
@ -509,7 +513,7 @@ export class Struct<
*/ */
public postDeserialize<TPostSerialize>( public postDeserialize<TPostSerialize>(
callback?: StructPostDeserialized<TFields, TPostSerialize> callback?: StructPostDeserialized<TFields, TPostSerialize>
): Struct<TFields, TOmitInit, TExtra, TPostSerialize>; ): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;
public postDeserialize( public postDeserialize(
callback?: StructPostDeserialized<TFields, any> callback?: StructPostDeserialized<TFields, any>
) { ) {
@ -517,16 +521,11 @@ export class Struct<
return this as any; return this as any;
} }
private initializeStructValue() {
const value = new StructValue();
Object.defineProperties(value.value, this._extra);
return value;
}
public async deserialize( public async deserialize(
context: StructDeserializationContext context: StructDeserializationContext
): Promise<StructDeserializedType<TFields, TExtra, TPostDeserialized>> { ): 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) { for (const [name, definition] of this._fields) {
const fieldValue = await definition.deserialize(this.options, context, value); const fieldValue = await definition.deserialize(this.options, context, value);
@ -534,7 +533,8 @@ export class Struct<
} }
if (this._postDeserialized) { 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) { if (result) {
return result; return result;
} }
@ -543,8 +543,8 @@ export class Struct<
return value.value as any; return value.value as any;
} }
public serialize(init: Evaluate<Omit<TFields, TOmitInit>>, context: StructSerializationContext): ArrayBuffer { public serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>, context: StructSerializationContext): ArrayBuffer {
const value = this.initializeStructValue(); const value = new StructValue();
for (const [name, definition] of this._fields) { for (const [name, definition] of this._fields) {
const fieldValue = definition.create(this.options, context, value, (init as any)[name]); const fieldValue = definition.create(this.options, context, value, (init as any)[name]);

View file

@ -1,5 +1,15 @@
import { StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext, StructValue } from '../basic'; import { StructDefaultOptions, StructDeserializationContext, StructValue } from '../basic';
import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer'; 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('Types', () => {
describe('ArrayBufferLike', () => { describe('ArrayBufferLike', () => {
@ -57,12 +67,10 @@ describe('Types', () => {
it('`#toArrayBuffer` should return the decoded string', () => { it('`#toArrayBuffer` should return the decoded string', () => {
const text = 'foo'; const text = 'foo';
const arrayBuffer = Buffer.from(text, 'utf-8'); const arrayBuffer = Buffer.from(text, 'utf-8');
const context: StructSerializationContext = { const context = new MockDeserializationContext();
encodeUtf8(input) {
return Buffer.from(input, 'utf-8');
},
};
expect(StringFieldType.instance.toArrayBuffer(text, context)).toEqual(arrayBuffer); 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', () => { it('`#fromArrayBuffer` should return the encoded ArrayBuffer', () => {
@ -96,41 +104,34 @@ describe('Types', () => {
describe('ArrayBufferLikeFieldDefinition', () => { describe('ArrayBufferLikeFieldDefinition', () => {
it('should work with `ArrayBufferFieldType`', async () => { 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 size = 10;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); 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); const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(read).toBeCalledTimes(1); expect(context.read).toBeCalledTimes(1);
expect(read).toBeCalledWith(size); expect(context.read).toBeCalledWith(size);
expect(fieldValue).toHaveProperty('arrayBuffer', buffer); expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
const value = fieldValue.get(); expect(fieldValue.get()).toBe(buffer);
expect(value).toBe(buffer);
}); });
it('should work with `Uint8ClampedArrayFieldType`', async () => { 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 size = 10;
const definition = new MockArrayBufferFieldDefinition(Uint8ClampedArrayFieldType.instance, size); 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); const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(read).toBeCalledTimes(1); expect(context.read).toBeCalledTimes(1);
expect(read).toBeCalledWith(10); expect(context.read).toBeCalledWith(size);
expect(fieldValue).toHaveProperty('arrayBuffer', buffer); expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
const value = fieldValue.get(); const value = fieldValue.get();
@ -139,21 +140,18 @@ describe('Types', () => {
}); });
it('should work when `#getSize` returns `0`', async () => { 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 size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); 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); const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(read).toBeCalledTimes(0); expect(context.read).toBeCalledTimes(0);
expect(fieldValue.arrayBuffer).toBeInstanceOf(ArrayBuffer); expect(fieldValue['arrayBuffer']).toBeInstanceOf(ArrayBuffer);
expect(fieldValue.arrayBuffer).toHaveProperty('byteLength', 0); expect(fieldValue['arrayBuffer']).toHaveProperty('byteLength', 0);
const value = fieldValue.get(); const value = fieldValue.get();
expect(value).toBeInstanceOf(ArrayBuffer); expect(value).toBeInstanceOf(ArrayBuffer);
@ -164,36 +162,34 @@ describe('Types', () => {
describe('ArrayBufferLikeFieldValue', () => { describe('ArrayBufferLikeFieldValue', () => {
describe('#set', () => { describe('#set', () => {
it('should clear `arrayBuffer` field', async () => { it('should clear `arrayBuffer` field', async () => {
const size = 10; const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationContext();
const buffer = new ArrayBuffer(size); const buffer = new ArrayBuffer(size);
const read = jest.fn((length: number) => buffer); context.buffer = buffer;
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue(); const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct); 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); expect(fieldValue).toHaveProperty('arrayBuffer', undefined);
}); });
}); });
describe('#serialize', () => { describe('#serialize', () => {
it('should be able to serialize a deserialized value', async () => { it('should be able to serialize with cached `arrayBuffer`', async () => {
const size = 10; const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationContext();
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i)); const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
const sourceBuffer = sourceArray.buffer; const buffer = sourceArray.buffer;
const read = jest.fn((length: number) => sourceBuffer); context.buffer = buffer;
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue(); const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct); const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
const targetArray = new Uint8Array(size); const targetArray = new Uint8Array(size);
@ -204,18 +200,17 @@ describe('Types', () => {
}); });
it('should be able to serialize a modified value', async () => { it('should be able to serialize a modified value', async () => {
const size = 10; const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationContext();
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i)); const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
const sourceBuffer = sourceArray.buffer; const buffer = sourceArray.buffer;
const read = jest.fn((length: number) => sourceBuffer); context.buffer = buffer;
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue(); const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct); const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
fieldValue.set(sourceArray.buffer); fieldValue.set(sourceArray.buffer);
const targetArray = new Uint8Array(size); const targetArray = new Uint8Array(size);

View file

@ -127,8 +127,9 @@ export abstract class ArrayBufferLikeFieldDefinition<
context: StructSerializationContext, context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TType['valueType'], value: TType['valueType'],
arrayBuffer?: ArrayBuffer,
): ArrayBufferLikeFieldValue<this> { ): ArrayBufferLikeFieldValue<this> {
return new ArrayBufferLikeFieldValue(this, options, context, struct, value); return new ArrayBufferLikeFieldValue(this, options, context, struct, value, arrayBuffer);
} }
public async deserialize( public async deserialize(
@ -146,16 +147,26 @@ export abstract class ArrayBufferLikeFieldDefinition<
} }
const value = this.type.fromArrayBuffer(arrayBuffer, context); const value = this.type.fromArrayBuffer(arrayBuffer, context);
const fieldValue = this.create(options, context, struct, value); return this.create(options, context, struct, value, arrayBuffer);
fieldValue.arrayBuffer = arrayBuffer;
return fieldValue;
} }
} }
export class ArrayBufferLikeFieldValue< export class ArrayBufferLikeFieldValue<
TDefinition extends ArrayBufferLikeFieldDefinition<ArrayBufferLikeFieldType, any, any>, TDefinition extends ArrayBufferLikeFieldDefinition<ArrayBufferLikeFieldType, any, any>,
> extends StructFieldValue<TDefinition> { > 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 { public set(value: TDefinition['valueType']): void {
super.set(value); super.set(value);

View file

@ -5,7 +5,10 @@ describe('Types', () => {
describe('FixedLengthArrayBufferLikeFieldDefinition', () => { describe('FixedLengthArrayBufferLikeFieldDefinition', () => {
describe('#getSize', () => { describe('#getSize', () => {
it('should return size in its options', () => { 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); expect(definition.getSize()).toBe(10);
}); });
}); });

View file

@ -1,467 +1,556 @@
import { StructDefaultOptions, StructDeserializationContext, StructValue } from '../basic'; import { StructDefaultOptions, StructDeserializationContext, StructFieldValue, StructSerializationContext, StructValue } from '../basic';
import { ArrayBufferFieldType, ArrayBufferLikeFieldValue, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer'; import { ArrayBufferFieldType, ArrayBufferLikeFieldType } from './array-buffer';
import { FixedLengthArrayBufferLikeFieldDefinition } from './fixed-length-array-buffer'; import { VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldLengthValue, VariableLengthArrayBufferLikeStructFieldValue } from './variable-length-array-buffer';
import { NumberFieldDefinition, NumberFieldType, NumberFieldValue } from './number';
import { VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeLengthStructFieldValue, 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('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', () => { describe('#getSize', () => {
it('should always return `0`', () => { it('should return size of its original field value', () => {
const definition = new VariableLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { lengthField: 'foo' }); const mockOriginalFieldValue = new MockOriginalFieldValue();
expect(definition.getSize()).toBe(0); const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
mockOriginalFieldValue,
mockArrayBufferFieldValue,
);
mockOriginalFieldValue.size = 0;
expect(lengthFieldValue.getSize()).toBe(0);
expect(mockOriginalFieldValue.getSize).toBeCalledTimes(1);
mockOriginalFieldValue.getSize.mockClear();
mockOriginalFieldValue.size = 100;
expect(lengthFieldValue.getSize()).toBe(100);
expect(mockOriginalFieldValue.getSize).toBeCalledTimes(1);
}); });
}); });
describe('#getDeserializeSize', () => { describe('#get', () => {
it('should return value of its `lengthField`', async () => { it('should return size of its `arrayBufferField`', async () => {
const lengthField = 'foo'; const mockOriginalFieldValue = new MockOriginalFieldValue();
const size = 10; const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
const definition = new VariableLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { lengthField }); const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
const buffer = new ArrayBuffer(size); mockOriginalFieldValue,
const read = jest.fn((length: number) => buffer); mockArrayBufferFieldValue,
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,
),
); );
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct); mockOriginalFieldValue.value = 0;
expect(read).toBeCalledTimes(1); mockArrayBufferFieldValue.size = 0;
expect(read).toBeCalledWith(size); expect(lengthFieldValue.get()).toBe(0);
expect(fieldValue).toHaveProperty('arrayBuffer', buffer); expect(mockArrayBufferFieldValue.getSize).toBeCalledTimes(1);
expect(mockOriginalFieldValue.get).toBeCalledTimes(1);
const value = fieldValue.get(); mockArrayBufferFieldValue.getSize.mockClear();
expect(value).toBe(buffer); mockOriginalFieldValue.get.mockClear();
mockArrayBufferFieldValue.size = 100;
expect(lengthFieldValue.get()).toBe(100);
expect(mockArrayBufferFieldValue.getSize).toBeCalledTimes(1);
expect(mockOriginalFieldValue.get).toBeCalledTimes(1);
}); });
it('should return value of its `lengthField` as number', async () => { it('should return size of its `arrayBufferField` as string', async () => {
const lengthField = 'foo'; const mockOriginalFieldValue = new MockOriginalFieldValue();
const size = 10; const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
const definition = new VariableLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { lengthField }); const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
const buffer = new ArrayBuffer(size); mockOriginalFieldValue,
const read = jest.fn((length: number) => buffer); mockArrayBufferFieldValue,
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()
),
); );
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct); mockOriginalFieldValue.value = '0';
expect(read).toBeCalledTimes(1); mockArrayBufferFieldValue.size = 0;
expect(read).toBeCalledWith(size); expect(lengthFieldValue.get()).toBe('0');
expect(fieldValue).toHaveProperty('arrayBuffer', buffer); expect(mockArrayBufferFieldValue.getSize).toBeCalledTimes(1);
expect(mockOriginalFieldValue.get).toBeCalledTimes(1);
const value = fieldValue.get(); mockArrayBufferFieldValue.getSize.mockClear();
expect(value).toBe(buffer); 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('VariableLengthArrayBufferLikeStructFieldValue', () => {
describe('.constructor', () => { describe('.constructor', () => {
it('should replace `lengthField` on `struct`', () => { it('should forward parameters', () => {
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 struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue( const lengthField = 'foo';
new FixedLengthArrayBufferLikeFieldDefinition( const originalLengthFieldValue = new MockOriginalFieldValue();
StringFieldType.instance, struct.set(lengthField, originalLengthFieldValue);
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,); const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, ArrayBufferFieldType.instance,
{ lengthField }, { lengthField },
); );
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
definition, const context = new MockDeserializationContext();
const value = new ArrayBuffer(0);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context, context,
struct, 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', () => { describe('#getSize', () => {
it('should return size of `arrayBuffer` if exist', async () => { class MockArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer> {
const lengthField = 'foo'; public toArrayBuffer = jest.fn((value: ArrayBuffer, context: StructSerializationContext): ArrayBuffer => {
const size = 10; return value;
const buffer = new ArrayBuffer(size); });
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = { public fromArrayBuffer = jest.fn((arrayBuffer: ArrayBuffer, context: StructDeserializationContext): ArrayBuffer => {
read, return arrayBuffer;
encodeUtf8(input) { throw new Error(''); }, });
decodeUtf8(buffer) { throw new Error(''); },
}; 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 struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue( const lengthField = 'foo';
new FixedLengthArrayBufferLikeFieldDefinition( const originalLengthFieldValue = new MockOriginalFieldValue();
StringFieldType.instance, struct.set(lengthField, originalLengthFieldValue);
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const definition = new VariableLengthArrayBufferLikeFieldDefinition( const arrayBufferFieldType = new MockArrayBufferFieldType();
ArrayBufferFieldType.instance, const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
arrayBufferFieldType,
{ lengthField }, { lengthField },
); );
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct); const context = new MockDeserializationContext();
expect(fieldValue.getSize()).toBe(size); 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`', () => { 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 struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue( const lengthField = 'foo';
new FixedLengthArrayBufferLikeFieldDefinition( const originalLengthFieldValue = new MockOriginalFieldValue();
StringFieldType.instance, struct.set(lengthField, originalLengthFieldValue);
{ length: 2 },
), 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, StructDefaultOptions,
context, context,
struct, struct,
size.toString() value,
);
struct.set(lengthField, originalLengthFieldValue,);
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance,
{ lengthField },
),
StructDefaultOptions,
context,
struct,
buffer,
); );
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`', () => { 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 struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue( const lengthField = 'foo';
new FixedLengthArrayBufferLikeFieldDefinition( const originalLengthFieldValue = new MockOriginalFieldValue();
StringFieldType.instance, struct.set(lengthField, originalLengthFieldValue);
{ length: 2 },
), 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, StructDefaultOptions,
context, context,
struct, struct,
size.toString() value,
);
struct.set(lengthField, originalLengthFieldValue,);
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
new VariableLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ lengthField },
),
StructDefaultOptions,
context,
struct,
'test',
); );
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', () => { describe('#set', () => {
it('should store value', () => { it('should call `ArrayBufferLikeFieldValue#set`', () => {
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 struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue( const lengthField = 'foo';
new FixedLengthArrayBufferLikeFieldDefinition( const originalLengthFieldValue = new MockOriginalFieldValue();
StringFieldType.instance, struct.set(lengthField, originalLengthFieldValue);
{ length: 2 },
), const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance,
{ lengthField },
);
const context = new MockDeserializationContext();
const value = new ArrayBuffer(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context, context,
struct, struct,
size.toString() value,
value,
); );
struct.set(lengthField, originalLengthFieldValue,); const newValue = new ArrayBuffer(100);
arrayBufferFieldValue.set(newValue);
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue( expect(arrayBufferFieldValue.get()).toBe(newValue);
new VariableLengthArrayBufferLikeFieldDefinition( expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
Uint8ClampedArrayFieldType.instance,
{ lengthField },
),
StructDefaultOptions,
context,
struct,
new Uint8ClampedArray(buffer),
);
const newArray = new Uint8ClampedArray(size);
fieldValue.set(newArray);
expect(fieldValue.get()).toBe(newArray);
}); });
it('should clear length', () => { 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 struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue( const lengthField = 'foo';
new FixedLengthArrayBufferLikeFieldDefinition( const originalLengthFieldValue = new MockOriginalFieldValue();
StringFieldType.instance, struct.set(lengthField, originalLengthFieldValue);
{ length: 2 },
), const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance,
{ lengthField },
);
const context = new MockDeserializationContext();
const value = new ArrayBuffer(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context, context,
struct, struct,
size.toString() value,
value,
); );
struct.set(lengthField, originalLengthFieldValue,); const newValue = new ArrayBuffer(100);
arrayBufferFieldValue.set(newValue);
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue( expect(arrayBufferFieldValue).toHaveProperty('length', undefined);
new VariableLengthArrayBufferLikeFieldDefinition(
Uint8ClampedArrayFieldType.instance,
{ lengthField },
),
StructDefaultOptions,
context,
struct,
new Uint8ClampedArray(buffer),
);
const newArray = new Uint8ClampedArray(size);
fieldValue.set(newArray);
expect(fieldValue['length']).toBeUndefined();
}); });
}); });
}); });
describe('VariableLengthArrayBufferLikeLengthStructFieldValue', () => { describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
describe('#getSize', () => { describe('#getSize', () => {
it('should return size of its original field value', () => { it('should always return `0`', () => {
const struct = new StructValue(); const definition = new VariableLengthArrayBufferLikeFieldDefinition(
const originalFieldValue = new NumberFieldValue( ArrayBufferFieldType.instance,
new NumberFieldDefinition( { lengthField: 'foo' },
NumberFieldType.Int8
),
StructDefaultOptions,
{} as any,
struct,
42,
); );
const fieldValue = new VariableLengthArrayBufferLikeLengthStructFieldValue( expect(definition.getSize()).toBe(0);
originalFieldValue,
{} as any,
);
expect(fieldValue.getSize()).toBe(originalFieldValue.getSize());
}); });
}); });
describe('#get', () => { describe('#getDeserializeSize', () => {
it('should return size of its `arrayBufferField`', async () => { it('should return value of its `lengthField`', 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 struct = new StructValue();
const originalLengthFieldValue = new NumberFieldValue( const lengthField = 'foo';
new NumberFieldDefinition( const originalLengthFieldValue = new MockOriginalFieldValue();
NumberFieldType.Int32 struct.set(lengthField, originalLengthFieldValue);
),
StructDefaultOptions,
context,
struct,
size
);
struct.set(lengthField, originalLengthFieldValue,);
const definition = new VariableLengthArrayBufferLikeFieldDefinition( const definition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, ArrayBufferFieldType.instance,
{ lengthField }, { lengthField },
); );
const fieldValue = (await definition.deserialize(StructDefaultOptions, context, struct)) as any as VariableLengthArrayBufferLikeStructFieldValue; originalLengthFieldValue.value = 0;
expect(fieldValue['lengthFieldValue'].get()).toBe(size); 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 () => { it('should return value of its `lengthField` as number', 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 struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue( const lengthField = 'foo';
new FixedLengthArrayBufferLikeFieldDefinition( const originalLengthFieldValue = new MockOriginalFieldValue();
StringFieldType.instance, struct.set(lengthField, originalLengthFieldValue);
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const definition = new VariableLengthArrayBufferLikeFieldDefinition( const definition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, ArrayBufferFieldType.instance,
{ lengthField }, { lengthField },
); );
const fieldValue = (await definition.deserialize(StructDefaultOptions, context, struct)) as any as VariableLengthArrayBufferLikeStructFieldValue; originalLengthFieldValue.value = '0';
expect(fieldValue['lengthFieldValue'].get()).toBe(size.toString()); 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', () => { describe('#create', () => {
it('should call `serialize` of its `originalField`', async () => { it('should create a `VariableLengthArrayBufferLikeFieldValue`', () => {
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(''); },
};
const struct = new StructValue(); const struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue( const lengthField = 'foo';
new FixedLengthArrayBufferLikeFieldDefinition( const originalLengthFieldValue = new MockOriginalFieldValue();
StringFieldType.instance, struct.set(lengthField, originalLengthFieldValue);
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const definition = new VariableLengthArrayBufferLikeFieldDefinition( const definition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, ArrayBufferFieldType.instance,
{ lengthField }, { 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); expect(arrayBufferFieldValue).toHaveProperty('definition', definition);
const targetView = new DataView(targetArray.buffer); expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
fieldValue['lengthFieldValue'].serialize(targetView, 0, context); expect(arrayBufferFieldValue).toHaveProperty('context', context);
expect(targetArray).toEqual(new Uint8Array('10'.split('').map(c => c.charCodeAt(0)))); 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);
}); });
}); });
}); });

View file

@ -1,5 +1,5 @@
import { StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic'; import { StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic';
import { KeysOfType } from '../utils'; import type { KeysOfType } from '../utils';
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer'; import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer';
export interface VariableLengthArrayBufferLikeFieldOptions< export interface VariableLengthArrayBufferLikeFieldOptions<
@ -34,8 +34,16 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
context: StructSerializationContext, context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TType['valueType'], value: TType['valueType'],
arrayBuffer?: ArrayBuffer
): VariableLengthArrayBufferLikeStructFieldValue<this> { ): 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> { > extends ArrayBufferLikeFieldValue<TDefinition> {
protected length: number | undefined; protected length: number | undefined;
protected lengthFieldValue: VariableLengthArrayBufferLikeLengthStructFieldValue; protected lengthFieldValue: VariableLengthArrayBufferLikeFieldLengthValue;
public constructor( public constructor(
definition: TDefinition, definition: TDefinition,
@ -52,14 +60,19 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
context: StructSerializationContext, context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TDefinition['valueType'], 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. // Patch the associated length field.
const lengthField = this.definition.options.lengthField; const lengthField = this.definition.options.lengthField;
const originalValue = struct.get(lengthField); const originalValue = struct.get(lengthField);
this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthStructFieldValue( this.lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
originalValue, originalValue,
this, this,
); );
@ -68,14 +81,10 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
public getSize() { public getSize() {
if (this.length === undefined) { if (this.length === undefined) {
if (this.arrayBuffer !== undefined) { 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; 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;
}
} }
} }
@ -84,19 +93,20 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
public set(value: unknown) { public set(value: unknown) {
super.set(value); super.set(value);
this.arrayBuffer = undefined;
this.length = undefined; this.length = undefined;
} }
} }
export class VariableLengthArrayBufferLikeLengthStructFieldValue export class VariableLengthArrayBufferLikeFieldLengthValue
extends StructFieldValue { extends StructFieldValue {
protected originalField: StructFieldValue; protected originalField: StructFieldValue;
protected arrayBufferField: VariableLengthArrayBufferLikeStructFieldValue; protected arrayBufferField: StructFieldValue;
public constructor( public constructor(
originalField: StructFieldValue, originalField: StructFieldValue,
arrayBufferField: VariableLengthArrayBufferLikeStructFieldValue, arrayBufferField: StructFieldValue,
) { ) {
super(originalField.definition, originalField.options, originalField.context, originalField.struct, 0); super(originalField.definition, originalField.options, originalField.context, originalField.struct, 0);
this.originalField = originalField; this.originalField = originalField;

View file

@ -41,6 +41,7 @@ export type KeysOfType<T, TValue> =
export type ValueOrPromise<T> = T | Promise<T>; 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. * Returns a (fake) value of the given type.

View file

@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
/* Basic Options */ /* 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'. */ "module": "ESNext", // /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": [ // /* Specify library files to be included in the compilation. */ "lib": [ // /* Specify library files to be included in the compilation. */
"ESNext" "ESNext"