mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 09:49:24 +02:00
test(struct): add more unit tests
This commit is contained in:
parent
547e9781e7
commit
b71f9714b2
26 changed files with 1151 additions and 473 deletions
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -59,5 +59,7 @@
|
||||||
"yume",
|
"yume",
|
||||||
"zstd"
|
"zstd"
|
||||||
],
|
],
|
||||||
"editor.tabSize": 4
|
"editor.tabSize": 4,
|
||||||
|
"jest.rootPath": "packages/struct",
|
||||||
|
"jest.showCoverageOnLoad": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,5 @@ export * from './device-view';
|
||||||
export * from './error-dialog';
|
export * from './error-dialog';
|
||||||
export * from './external-link';
|
export * from './external-link';
|
||||||
export * from './logger';
|
export * from './logger';
|
||||||
|
export * from './number-picker';
|
||||||
export * from './router';
|
export * from './router';
|
||||||
|
|
|
@ -55,11 +55,11 @@ const buffer = MyStruct.serialize({
|
||||||
- [`postDeserialize`](#postdeserialize)
|
- [`postDeserialize`](#postdeserialize)
|
||||||
- [Custom field type](#custom-field-type)
|
- [Custom field type](#custom-field-type)
|
||||||
- [`Struct#field` method](#structfield-method)
|
- [`Struct#field` method](#structfield-method)
|
||||||
- [`FieldDefinition`](#fielddefinition)
|
- [`StructFieldDefinition`](#fielddefinition)
|
||||||
- [`getSize`](#getsize)
|
- [`getSize`](#getsize)
|
||||||
- [`deserialize`](#deserialize-1)
|
- [`deserialize`](#deserialize-1)
|
||||||
- [`createValue`](#createvalue)
|
- [`createValue`](#createvalue)
|
||||||
- [`FieldRuntimeValue`](#fieldruntimevalue)
|
- [`StructFieldValue`](#StructFieldValue)
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
|
@ -562,7 +562,7 @@ This library has a plugin system to support adding fields with custom types.
|
||||||
```ts
|
```ts
|
||||||
field<
|
field<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TDefinition extends FieldDefinition<any, any, any>
|
TDefinition extends StructFieldDefinition<any, any, any>
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
definition: TDefinition
|
definition: TDefinition
|
||||||
|
@ -574,21 +574,21 @@ field<
|
||||||
>;
|
>;
|
||||||
```
|
```
|
||||||
|
|
||||||
Appends a `FieldDefinition` to the `Struct`.
|
Appends a `StructFieldDefinition` to the `Struct`.
|
||||||
|
|
||||||
All above built-in methods are alias of `field`. To add a field of a custom type, let users call `field` with your custom `FieldDefinition` implementation.
|
All above built-in methods are alias of `field`. To add a field of a custom type, let users call `field` with your custom `StructFieldDefinition` implementation.
|
||||||
|
|
||||||
### `FieldDefinition`
|
### `StructFieldDefinition`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract class FieldDefinition<TOptions = void, TValueType = unknown, TOmitInit = never> {
|
abstract class StructFieldDefinition<TOptions = void, TValueType = unknown, TOmitInit = never> {
|
||||||
readonly options: TOptions;
|
readonly options: TOptions;
|
||||||
|
|
||||||
constructor(options: TOptions);
|
constructor(options: TOptions);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
A `FieldDefinition` describes type, size and runtime semantics of a field.
|
A `StructFieldDefinition` describes type, size and runtime semantics of a field.
|
||||||
|
|
||||||
It's an `abstract` class, means it lacks some method implementations, so it shouldn't be constructed.
|
It's an `abstract` class, means it lacks some method implementations, so it shouldn't be constructed.
|
||||||
|
|
||||||
|
@ -600,7 +600,7 @@ abstract getSize(): number;
|
||||||
|
|
||||||
Returns the size (or minimal size if it's dynamic) of this field.
|
Returns the size (or minimal size if it's dynamic) of this field.
|
||||||
|
|
||||||
Actual size should been returned from `FieldRuntimeValue#getSize`
|
Actual size should been returned from `StructFieldValue#getSize`
|
||||||
|
|
||||||
#### `deserialize`
|
#### `deserialize`
|
||||||
|
|
||||||
|
@ -609,7 +609,7 @@ abstract deserialize(
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
context: StructDeserializationContext,
|
context: StructDeserializationContext,
|
||||||
object: any,
|
object: any,
|
||||||
): ValueOrPromise<FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>>;
|
): ValueOrPromise<StructFieldValue<StructFieldDefinition<TOptions, TValueType, TRemoveInitFields>>>;
|
||||||
```
|
```
|
||||||
|
|
||||||
Defines how to deserialize a value from `context`. Can also return a `Promise`.
|
Defines how to deserialize a value from `context`. Can also return a `Promise`.
|
||||||
|
@ -617,9 +617,9 @@ Defines how to deserialize a value from `context`. Can also return a `Promise`.
|
||||||
Usually implementations should be:
|
Usually implementations should be:
|
||||||
|
|
||||||
1. Somehow parse the value from `context`
|
1. Somehow parse the value from `context`
|
||||||
2. Pass the value into `FieldDefinition#createValue`
|
2. Pass the value into `StructFieldDefinition#createValue`
|
||||||
|
|
||||||
Sometimes, some metadata is present when deserializing, but need to be calculated when serializing, for example a UTF-8 encoded string may have different length between itself (character count) and serialized form (byte length). So `deserialize` can save those metadata on the `FieldRuntimeValue` instance for later use.
|
Sometimes, some metadata is present when deserializing, but need to be calculated when serializing, for example a UTF-8 encoded string may have different length between itself (character count) and serialized form (byte length). So `deserialize` can save those metadata on the `StructFieldValue` instance for later use.
|
||||||
|
|
||||||
#### `createValue`
|
#### `createValue`
|
||||||
|
|
||||||
|
@ -629,15 +629,15 @@ abstract createValue(
|
||||||
context: StructSerializationContext,
|
context: StructSerializationContext,
|
||||||
object: any,
|
object: any,
|
||||||
value: TValueType,
|
value: TValueType,
|
||||||
): FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>;
|
): StructFieldValue<StructFieldDefinition<TOptions, TValueType, TRemoveInitFields>>;
|
||||||
```
|
```
|
||||||
|
|
||||||
Similar to `deserialize`, creates a `FieldRuntimeValue` for this instance.
|
Similar to `deserialize`, creates a `StructFieldValue` for this instance.
|
||||||
|
|
||||||
The difference is `createValue` will be called when a init value was provided to create a Struct value.
|
The difference is `createValue` will be called when a init value was provided to create a Struct value.
|
||||||
|
|
||||||
### `FieldRuntimeValue`
|
### `StructFieldValue`
|
||||||
|
|
||||||
One `FieldDefinition` instance represents one field declaration, and one `FieldRuntimeValue` instance represents one value.
|
One `StructFieldDefinition` instance represents one field declaration, and one `StructFieldValue` instance represents one value.
|
||||||
|
|
||||||
It defines how to get, set, and serialize a value.
|
It defines how to get, set, and serialize a value.
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { StructDefaultOptions } from './context';
|
import { StructDefaultOptions } from './context';
|
||||||
|
|
||||||
describe('Runtime', () => {
|
|
||||||
describe('StructDefaultOptions', () => {
|
describe('StructDefaultOptions', () => {
|
||||||
it('should have `littleEndian` that equals to `false`', () => {
|
describe('.littleEndian', () => {
|
||||||
|
it('should be `false`', () => {
|
||||||
expect(StructDefaultOptions).toHaveProperty('littleEndian', false);
|
expect(StructDefaultOptions).toHaveProperty('littleEndian', false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { ValueOrPromise } from '../utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context with enough methods to serialize a struct
|
* Context with enough methods to serialize a struct
|
||||||
*/
|
*/
|
||||||
|
@ -23,7 +25,7 @@ export interface StructDeserializationContext extends StructSerializationContext
|
||||||
* Context should return exactly `length` bytes or data. If that's not possible
|
* Context should return exactly `length` bytes or data. If that's not possible
|
||||||
* (due to end of file or other error condition), it should throw an error.
|
* (due to end of file or other error condition), it should throw an error.
|
||||||
*/
|
*/
|
||||||
read(length: number): ArrayBuffer | Promise<ArrayBuffer>;
|
read(length: number): ValueOrPromise<ArrayBuffer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StructOptions {
|
export interface StructOptions {
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
import { StructOptions, StructDeserializationContext, StructSerializationContext } from './context';
|
import { ValueOrPromise } from '../utils';
|
||||||
import { FieldDefinition } from './definition';
|
import { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
||||||
import { FieldRuntimeValue } from './runtime-value';
|
import { StructFieldDefinition } from './definition';
|
||||||
|
import { StructFieldValue } from './field-value';
|
||||||
|
import { StructValue } from './struct-value';
|
||||||
|
|
||||||
describe('FieldDefinition', () => {
|
describe('StructFieldDefinition', () => {
|
||||||
describe('new', () => {
|
describe('.constructor', () => {
|
||||||
it('should save the `options` parameter', () => {
|
it('should save the `options` parameter', () => {
|
||||||
class MockFieldDefinition extends FieldDefinition<number>{
|
class MockFieldDefinition extends StructFieldDefinition<number>{
|
||||||
public constructor(options: number) {
|
public constructor(options: number) {
|
||||||
super(options);
|
super(options);
|
||||||
}
|
}
|
||||||
public getSize(): number {
|
public getSize(): number {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, object: any): FieldRuntimeValue<FieldDefinition<number, unknown, never>> | Promise<FieldRuntimeValue<FieldDefinition<number, unknown, never>>> {
|
public create(options: Readonly<StructOptions>, context: StructSerializationContext, object: StructValue, struct: unknown): StructFieldValue<this> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
public createValue(options: Readonly<StructOptions>, context: StructSerializationContext, object: any, value: unknown): FieldRuntimeValue<FieldDefinition<number, unknown, never>> {
|
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, struct: StructValue): ValueOrPromise<StructFieldValue<this>> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,41 @@
|
||||||
|
import type { ValueOrPromise } from '../utils';
|
||||||
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
||||||
import type { FieldRuntimeValue } from './runtime-value';
|
import type { StructFieldValue } from './field-value';
|
||||||
|
import type { StructValue } from './struct-value';
|
||||||
type ValueOrPromise<T> = T | Promise<T>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A field definition is a bridge between its type and its runtime value.
|
* A field definition is a bridge between its type and its runtime value.
|
||||||
*
|
*
|
||||||
* `Struct` record fields in a list of `FieldDefinition`s.
|
* `Struct` record fields in a list of `StructFieldDefinition`s.
|
||||||
*
|
*
|
||||||
* When `Struct#create` or `Struct#deserialize` are called, each field's definition
|
* When `Struct#create` or `Struct#deserialize` are called, each field's definition
|
||||||
* crates its own type of `FieldRuntimeValue` to manage the field value in that `Struct` instance.
|
* crates its own type of `StructFieldValue` to manage the field value in that `Struct` instance.
|
||||||
*
|
*
|
||||||
* One `FieldDefinition` can represents multiple similar types, just returns the corresponding
|
* One `StructFieldDefinition` can represents multiple similar types, just returns the corresponding
|
||||||
* `FieldRuntimeValue` when `createValue` was called.
|
* `StructFieldValue` when `createValue` was called.
|
||||||
*
|
*
|
||||||
* @template TOptions TypeScript type of this definition's `options`.
|
* @template TOptions TypeScript type of this definition's `options`.
|
||||||
* @template TValueType TypeScript type of this field.
|
* @template TValue TypeScript type of this field.
|
||||||
* @template TOmitInit Optionally remove some fields from the init type. Should be a union of string literal types.
|
* @template TOmitInitKey Optionally remove some fields from the init type. Should be a union of string literal types.
|
||||||
*/
|
*/
|
||||||
export abstract class FieldDefinition<
|
export abstract class StructFieldDefinition<
|
||||||
TOptions = void,
|
TOptions = void,
|
||||||
TValueType = unknown,
|
TValue = unknown,
|
||||||
TOmitInit = never,
|
TOmitInitKey = never,
|
||||||
> {
|
> {
|
||||||
public readonly options: TOptions;
|
public readonly options: TOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When `T` is a type initiated `FieldDefinition`,
|
* When `T` is a type initiated `StructFieldDefinition`,
|
||||||
* use `T['valueType']` to retrieve its `TValueType` type parameter.
|
* use `T['valueType']` to retrieve its `TValue` type parameter.
|
||||||
*/
|
*/
|
||||||
public readonly valueType!: TValueType;
|
public readonly valueType!: TValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When `T` is a type initiated `FieldDefinition`,
|
* When `T` is a type initiated `StructFieldDefinition`,
|
||||||
* use `T['omitInitType']` to retrieve its `TOmitInit` type parameter.
|
* use `T['omitInitKeyType']` to retrieve its `TOmitInitKey` type parameter.
|
||||||
*/
|
*/
|
||||||
public readonly omitInitType!: TOmitInit;
|
public readonly omitInitKeyType!: TOmitInitKey;
|
||||||
|
|
||||||
public constructor(options: TOptions) {
|
public constructor(options: TOptions) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
@ -44,26 +44,26 @@ export abstract class FieldDefinition<
|
||||||
/**
|
/**
|
||||||
* When implemented in derived classes, returns the size (or minimal size if it's dynamic) of this field.
|
* When implemented in derived classes, returns the size (or minimal size if it's dynamic) of this field.
|
||||||
*
|
*
|
||||||
* Actual size can be retrieved from `FieldRuntimeValue#getSize`
|
* Actual size can be retrieved from `StructFieldValue#getSize`
|
||||||
*/
|
*/
|
||||||
public abstract getSize(): number;
|
public abstract getSize(): number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When implemented in derived classes, creates a `FieldRuntimeValue` by parsing the `context`.
|
* When implemented in derived classes, creates a `StructFieldValue` from a given `value`.
|
||||||
|
*/
|
||||||
|
public abstract create(
|
||||||
|
options: Readonly<StructOptions>,
|
||||||
|
context: StructSerializationContext,
|
||||||
|
object: StructValue,
|
||||||
|
struct: TValue,
|
||||||
|
): StructFieldValue<this>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When implemented in derived classes, creates a `StructFieldValue` by parsing `context`.
|
||||||
*/
|
*/
|
||||||
public abstract deserialize(
|
public abstract deserialize(
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
context: StructDeserializationContext,
|
context: StructDeserializationContext,
|
||||||
object: any,
|
struct: StructValue,
|
||||||
): ValueOrPromise<FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TOmitInit>>>;
|
): ValueOrPromise<StructFieldValue<this>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* When implemented in derived classes, creates a `FieldRuntimeValue` from a given `value`.
|
|
||||||
*/
|
|
||||||
public abstract createValue(
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
context: StructSerializationContext,
|
|
||||||
object: any,
|
|
||||||
value: TValueType,
|
|
||||||
): FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TOmitInit>>;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
import { ValueOrPromise } from '../utils';
|
||||||
import { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
import { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
||||||
import { FieldDefinition } from './definition';
|
import { StructFieldDefinition } from './definition';
|
||||||
import { FieldRuntimeValue } from './runtime-value';
|
import { StructFieldValue } from './field-value';
|
||||||
|
import { StructValue } from './struct-value';
|
||||||
|
|
||||||
describe('FieldRuntimeValue', () => {
|
describe('StructFieldValue', () => {
|
||||||
describe('.constructor', () => {
|
describe('.constructor', () => {
|
||||||
it('should save parameters', () => {
|
it('should save parameters', () => {
|
||||||
class MockFieldRuntimeValue extends FieldRuntimeValue {
|
class MockStructFieldValue extends StructFieldValue {
|
||||||
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
@ -14,58 +16,58 @@ describe('FieldRuntimeValue', () => {
|
||||||
const definition = 1 as any;
|
const definition = 1 as any;
|
||||||
const options = 2 as any;
|
const options = 2 as any;
|
||||||
const context = 3 as any;
|
const context = 3 as any;
|
||||||
const object = 4 as any;
|
const struct = 4 as any;
|
||||||
const value = 5 as any;
|
const value = 5 as any;
|
||||||
|
|
||||||
const fieldRuntimeValue = new MockFieldRuntimeValue(definition, options, context, object, value);
|
const fieldValue = new MockStructFieldValue(definition, options, context, struct, value);
|
||||||
expect(fieldRuntimeValue).toHaveProperty('definition', definition);
|
expect(fieldValue).toHaveProperty('definition', definition);
|
||||||
expect(fieldRuntimeValue).toHaveProperty('options', options);
|
expect(fieldValue).toHaveProperty('options', options);
|
||||||
expect(fieldRuntimeValue).toHaveProperty('context', context);
|
expect(fieldValue).toHaveProperty('context', context);
|
||||||
expect(fieldRuntimeValue).toHaveProperty('object', object);
|
expect(fieldValue).toHaveProperty('struct', struct);
|
||||||
expect(fieldRuntimeValue.get()).toBe(value);
|
expect(fieldValue.get()).toBe(value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getSize', () => {
|
describe('#getSize', () => {
|
||||||
it('should return same value as definition\'s', () => {
|
it('should return same value as definition\'s', () => {
|
||||||
class MockFieldDefinition extends FieldDefinition {
|
class MockFieldDefinition extends StructFieldDefinition {
|
||||||
public getSize(): number {
|
public getSize(): number {
|
||||||
return 42;
|
return 42;
|
||||||
}
|
}
|
||||||
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, object: any): FieldRuntimeValue<FieldDefinition<void, unknown, never>> | Promise<FieldRuntimeValue<FieldDefinition<void, unknown, never>>> {
|
public create(options: Readonly<StructOptions>, context: StructSerializationContext, object: StructValue, struct: unknown): StructFieldValue<this> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
public createValue(options: Readonly<StructOptions>, context: StructSerializationContext, object: any, value: unknown): FieldRuntimeValue<FieldDefinition<void, unknown, never>> {
|
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, struct: StructValue): ValueOrPromise<StructFieldValue<this>> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockFieldRuntimeValue extends FieldRuntimeValue {
|
class MockStructFieldValue extends StructFieldValue {
|
||||||
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldDefinition = new MockFieldDefinition();
|
const fieldDefinition = new MockFieldDefinition();
|
||||||
const fieldRuntimeValue = new MockFieldRuntimeValue(fieldDefinition, undefined as any, undefined as any, undefined as any, undefined as any);
|
const fieldValue = new MockStructFieldValue(fieldDefinition, undefined as any, undefined as any, undefined as any, undefined as any);
|
||||||
expect(fieldRuntimeValue.getSize()).toBe(42);
|
expect(fieldValue.getSize()).toBe(42);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#set', () => {
|
describe('#set', () => {
|
||||||
it('should update interval value', () => {
|
it('should update its internal value', () => {
|
||||||
class MockFieldRuntimeValue extends FieldRuntimeValue {
|
class MockStructFieldValue extends StructFieldValue {
|
||||||
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldRuntimeValue = new MockFieldRuntimeValue(undefined as any, undefined as any, undefined as any, undefined as any, undefined as any);
|
const fieldValue = new MockStructFieldValue(undefined as any, undefined as any, undefined as any, undefined as any, undefined as any);
|
||||||
fieldRuntimeValue.set(1);
|
fieldValue.set(1);
|
||||||
expect(fieldRuntimeValue.get()).toBe(1);
|
expect(fieldValue.get()).toBe(1);
|
||||||
|
|
||||||
fieldRuntimeValue.set(2);
|
fieldValue.set(2);
|
||||||
expect(fieldRuntimeValue.get()).toBe(2);
|
expect(fieldValue.get()).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import type { StructOptions, StructSerializationContext } from './context';
|
import type { StructOptions, StructSerializationContext } from './context';
|
||||||
import type { FieldDefinition } from './definition';
|
import type { StructFieldDefinition } from './definition';
|
||||||
|
import type { StructValue } from './struct-value';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field runtime value manages one field of one `Struct` instance.
|
* Field runtime value manages one field of one `Struct` instance.
|
||||||
*
|
*
|
||||||
* If one `FieldDefinition` needs to change other field's semantics
|
* If one `StructFieldDefinition` needs to change other field's semantics
|
||||||
* It can override other fields' `FieldRuntimeValue` in its own `FieldRuntimeValue`'s constructor
|
* It can override other fields' `StructFieldValue` in its own `StructFieldValue`'s constructor
|
||||||
*/
|
*/
|
||||||
export abstract class FieldRuntimeValue<
|
export abstract class StructFieldValue<
|
||||||
TDefinition extends FieldDefinition<any, any, any> = FieldDefinition<any, any, any>
|
TDefinition extends StructFieldDefinition<any, any, any> = StructFieldDefinition<any, any, any>
|
||||||
> {
|
> {
|
||||||
/** Gets the definition associated with this runtime value */
|
/** Gets the definition associated with this runtime value */
|
||||||
public readonly definition: TDefinition;
|
public readonly definition: TDefinition;
|
||||||
|
@ -20,7 +21,7 @@ export abstract class FieldRuntimeValue<
|
||||||
public readonly context: StructSerializationContext;
|
public readonly context: StructSerializationContext;
|
||||||
|
|
||||||
/** Gets the associated `Struct` instance */
|
/** Gets the associated `Struct` instance */
|
||||||
public readonly object: any;
|
public readonly struct: StructValue;
|
||||||
|
|
||||||
protected value: TDefinition['valueType'];
|
protected value: TDefinition['valueType'];
|
||||||
|
|
||||||
|
@ -28,13 +29,13 @@ export abstract class FieldRuntimeValue<
|
||||||
definition: TDefinition,
|
definition: TDefinition,
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
context: StructSerializationContext,
|
context: StructSerializationContext,
|
||||||
object: any,
|
struct: StructValue,
|
||||||
value: TDefinition['valueType'],
|
value: TDefinition['valueType'],
|
||||||
) {
|
) {
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.object = object;
|
this.struct = struct;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export * from './context';
|
export * from './context';
|
||||||
export * from './definition';
|
export * from './definition';
|
||||||
export * from './runtime-value';
|
export * from './field-value';
|
||||||
export * from './runtime-object';
|
export * from './struct-value';
|
||||||
|
|
|
@ -1,38 +1,68 @@
|
||||||
import { createRuntimeObject, getRuntimeValue, setRuntimeValue } from './runtime-object';
|
import { StructValue } from './struct-value';
|
||||||
|
|
||||||
describe('RuntimeObject', () => {
|
describe('StructValue', () => {
|
||||||
describe('createRuntimeObject', () => {
|
describe('.constructor', () => {
|
||||||
it('should create a special object', () => {
|
it('should create `fieldValues` and `value`', () => {
|
||||||
const object = createRuntimeObject();
|
const foo = new StructValue();
|
||||||
expect(Object.getOwnPropertySymbols(object)).toHaveLength(1);
|
const bar = new StructValue();
|
||||||
|
|
||||||
|
expect(foo).toHaveProperty('fieldValues', {});
|
||||||
|
expect(bar).toHaveProperty('fieldValues', {});
|
||||||
|
expect(foo.fieldValues).not.toBe(bar.fieldValues);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getRuntimeValue', () => {
|
describe('#set', () => {
|
||||||
it('should return previously set value', () => {
|
it('should save the `StructFieldValue`', () => {
|
||||||
const object = createRuntimeObject();
|
const object = new StructValue();
|
||||||
const field = 'foo';
|
|
||||||
const value = {} as any;
|
const foo = 'foo';
|
||||||
setRuntimeValue(object, field, value);
|
const fooValue = {} as any;
|
||||||
expect(getRuntimeValue(object, field)).toBe(value);
|
object.set(foo, fooValue);
|
||||||
|
|
||||||
|
const bar = 'bar';
|
||||||
|
const barValue = {} as any;
|
||||||
|
object.set(bar, barValue);
|
||||||
|
|
||||||
|
expect(object.fieldValues[foo]).toBe(fooValue);
|
||||||
|
expect(object.fieldValues[bar]).toBe(barValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should define a property for `key`', () => {
|
||||||
|
const object = new StructValue();
|
||||||
|
|
||||||
|
const foo = 'foo';
|
||||||
|
const fooGetter = jest.fn(() => 42);
|
||||||
|
const fooSetter = jest.fn((value: number) => { });
|
||||||
|
const fooValue = { get: fooGetter, set: fooSetter } as any;
|
||||||
|
object.set(foo, fooValue);
|
||||||
|
|
||||||
|
const bar = 'bar';
|
||||||
|
const barGetter = jest.fn(() => true);
|
||||||
|
const barSetter = jest.fn((value: number) => { });
|
||||||
|
const barValue = { get: barGetter, set: barSetter } as any;
|
||||||
|
object.set(bar, barValue);
|
||||||
|
|
||||||
|
expect(object.value).toHaveProperty(foo, 42);
|
||||||
|
expect(fooGetter).toBeCalledTimes(1);
|
||||||
|
expect(barGetter).toBeCalledTimes(0);
|
||||||
|
|
||||||
|
object.value[foo] = 100;
|
||||||
|
expect(fooSetter).toBeCalledTimes(1);
|
||||||
|
expect(fooSetter).lastCalledWith(100);
|
||||||
|
expect(barSetter).toBeCalledTimes(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setRuntimeValue', () => {
|
describe('#get', () => {
|
||||||
it('should define a proxy property to underlying `RuntimeValue`', () => {
|
it('should return previously set `StructFieldValue`', () => {
|
||||||
const object = createRuntimeObject();
|
const object = new StructValue();
|
||||||
const field = 'foo';
|
|
||||||
const getter = jest.fn(() => 42);
|
|
||||||
const setter = jest.fn((value: number) => { });
|
|
||||||
const value = { get: getter, set: setter } as any;
|
|
||||||
setRuntimeValue(object, field, value);
|
|
||||||
|
|
||||||
expect((object as any)[field]).toBe(42);
|
const foo = 'foo';
|
||||||
expect(getter).toBeCalledTimes(1);
|
const fooValue = {} as any;
|
||||||
|
object.set(foo, fooValue);
|
||||||
|
|
||||||
(object as any)[field] = 100;
|
expect(object.get(foo)).toBe(fooValue);
|
||||||
expect(setter).toBeCalledTimes(1);
|
|
||||||
expect(setter).lastCalledWith(100);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,36 +1,45 @@
|
||||||
import { FieldRuntimeValue } from './runtime-value';
|
import type { StructFieldValue } from './field-value';
|
||||||
|
|
||||||
const RuntimeValues = Symbol('RuntimeValues');
|
/**
|
||||||
|
* Manages the initialization process of a struct value
|
||||||
|
*/
|
||||||
|
export class StructValue {
|
||||||
|
/** @internal */ readonly fieldValues: Record<PropertyKey, StructFieldValue> = {};
|
||||||
|
|
||||||
export interface RuntimeObject {
|
/**
|
||||||
[RuntimeValues]: Record<PropertyKey, FieldRuntimeValue>;
|
* Gets the result struct value object
|
||||||
}
|
*/
|
||||||
|
public readonly value: Record<PropertyKey, unknown> = {};
|
||||||
|
|
||||||
/** Creates a new runtime object that can be used with `getRuntimeValue` and `setRuntimeValue` */
|
/**
|
||||||
export function createRuntimeObject(): RuntimeObject {
|
* Sets a `StructFieldValue` for `key`
|
||||||
return {
|
*
|
||||||
[RuntimeValues]: {},
|
* @param key The field name
|
||||||
};
|
* @param value The associated `StructFieldValue`
|
||||||
}
|
*/
|
||||||
|
public set(key: PropertyKey, value: StructFieldValue): void {
|
||||||
|
// TODO: TypeScript 4.2 will allow this behavior
|
||||||
|
// https://github.com/microsoft/TypeScript/pull/26797
|
||||||
|
// @ts-expect-error Type 'symbol' cannot be used as an index type. ts(2538)
|
||||||
|
this.fieldValues[key] = value;
|
||||||
|
|
||||||
/** Gets the previously set `RuntimeValue` for specified `key` on `object` */
|
Object.defineProperty(this.value, key, {
|
||||||
export function getRuntimeValue(object: RuntimeObject, key: PropertyKey): FieldRuntimeValue {
|
configurable: true,
|
||||||
return object[RuntimeValues][key as any] as FieldRuntimeValue;
|
enumerable: true,
|
||||||
|
get() { return value.get(); },
|
||||||
|
set(v) { value.set(v); },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the `RuntimeValue` for specified `key` on `object`,
|
* Gets a previously `StructFieldValue` for `key`
|
||||||
* also sets up property accessors so reads/writes to `object`'s `key` will be forwarded to
|
*
|
||||||
* the underlying `RuntimeValue`
|
* @param key The field name
|
||||||
*/
|
*/
|
||||||
export function setRuntimeValue(object: RuntimeObject, key: PropertyKey, runtimeValue: FieldRuntimeValue): void {
|
public get(key: PropertyKey): StructFieldValue {
|
||||||
delete (object as any)[key];
|
// TODO: TypeScript 4.2 will allow this behavior
|
||||||
|
// https://github.com/microsoft/TypeScript/pull/26797
|
||||||
object[RuntimeValues][key as any] = runtimeValue;
|
// @ts-expect-error Type 'symbol' cannot be used as an index type. ts(2538)
|
||||||
Object.defineProperty(object, key, {
|
return this.fieldValues[key];
|
||||||
configurable: true,
|
}
|
||||||
enumerable: true,
|
|
||||||
get() { return runtimeValue.get(); },
|
|
||||||
set(value) { runtimeValue.set(value); },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,3 +2,4 @@ export * from './basic';
|
||||||
export * from './struct';
|
export * from './struct';
|
||||||
export { Struct as default } from './struct';
|
export { Struct as default } from './struct';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
export * from './utils';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { createRuntimeObject, FieldDefinition, FieldRuntimeValue, getRuntimeValue, setRuntimeValue, StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext } from './basic';
|
import { StructDefaultOptions, StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from './basic';
|
||||||
import { ArrayBufferFieldType, ArrayBufferLikeFieldType, Evaluate, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, Identity, KeysOfType, NumberFieldDefinition, NumberFieldType, Overwrite, 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';
|
||||||
|
|
||||||
export interface StructLike<TValue> {
|
export interface StructLike<TValue> {
|
||||||
deserialize(context: StructDeserializationContext): Promise<TValue>;
|
deserialize(context: StructDeserializationContext): Promise<TValue>;
|
||||||
|
@ -24,13 +25,13 @@ type AddFieldDescriptor<
|
||||||
TExtra extends object,
|
TExtra extends object,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
TFieldName extends PropertyKey,
|
TFieldName extends PropertyKey,
|
||||||
TDefinition extends FieldDefinition<any, any, any>> =
|
TDefinition extends StructFieldDefinition<any, any, any>> =
|
||||||
Identity<Struct<
|
Identity<Struct<
|
||||||
// Merge two types
|
// Merge two types
|
||||||
// Evaluate immediately to optimize editor hover tooltip
|
// Evaluate immediately to optimize editor hover tooltip
|
||||||
Evaluate<TFields & Record<TFieldName, TDefinition['valueType']>>,
|
Evaluate<TFields & Record<TFieldName, TDefinition['valueType']>>,
|
||||||
// Merge two `TOmitInit
|
// Merge two `TOmitInit
|
||||||
TOmitInit | TDefinition['omitInitType'],
|
TOmitInit | TDefinition['omitInitKeyType'],
|
||||||
TExtra,
|
TExtra,
|
||||||
TPostDeserialized
|
TPostDeserialized
|
||||||
>>;
|
>>;
|
||||||
|
@ -181,7 +182,7 @@ export class Struct<
|
||||||
*/
|
*/
|
||||||
public get size() { return this._size; }
|
public get size() { return this._size; }
|
||||||
|
|
||||||
private _fields: [name: PropertyKey, definition: FieldDefinition<any, any, any>][] = [];
|
private _fields: [name: PropertyKey, definition: StructFieldDefinition<any, any, any>][] = [];
|
||||||
|
|
||||||
private _extra: PropertyDescriptorMap = {};
|
private _extra: PropertyDescriptorMap = {};
|
||||||
|
|
||||||
|
@ -192,11 +193,11 @@ export class Struct<
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends a `FieldDefinition` to the `Struct
|
* Appends a `StructFieldDefinition` to the `Struct
|
||||||
*/
|
*/
|
||||||
public field<
|
public field<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TDefinition extends FieldDefinition<any, any, any>
|
TDefinition extends StructFieldDefinition<any, any, any>
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
definition: TDefinition,
|
definition: TDefinition,
|
||||||
|
@ -516,61 +517,55 @@ export class Struct<
|
||||||
return this as any;
|
return this as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeObject() {
|
private initializeStructValue() {
|
||||||
const object = createRuntimeObject();
|
const value = new StructValue();
|
||||||
Object.defineProperties(object, this._extra);
|
Object.defineProperties(value.value, this._extra);
|
||||||
return object;
|
return value;
|
||||||
}
|
|
||||||
|
|
||||||
public create(init: Evaluate<Omit<TFields, TOmitInit>>, context: StructSerializationContext): Overwrite<TExtra, TFields> {
|
|
||||||
const object = this.initializeObject();
|
|
||||||
|
|
||||||
for (const [name, definition] of this._fields) {
|
|
||||||
const runtimeValue = definition.createValue(this.options, context, object, (init as any)[name]);
|
|
||||||
setRuntimeValue(object, name, runtimeValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return object as any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deserialize(
|
public async deserialize(
|
||||||
context: StructDeserializationContext
|
context: StructDeserializationContext
|
||||||
): Promise<StructDeserializedType<TFields, TExtra, TPostDeserialized>> {
|
): Promise<StructDeserializedType<TFields, TExtra, TPostDeserialized>> {
|
||||||
const object = this.initializeObject();
|
const value = this.initializeStructValue();
|
||||||
|
|
||||||
for (const [name, definition] of this._fields) {
|
for (const [name, definition] of this._fields) {
|
||||||
const runtimeValue = await definition.deserialize(this.options, context, object);
|
const fieldValue = await definition.deserialize(this.options, context, value);
|
||||||
setRuntimeValue(object, name, runtimeValue);
|
value.set(name, fieldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._postDeserialized) {
|
if (this._postDeserialized) {
|
||||||
const result = this._postDeserialized.call(object as TFields, object as TFields);
|
const result = this._postDeserialized.call(value.value as TFields, value as TFields);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return object as any;
|
return value.value as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
public serialize(init: Evaluate<Omit<TFields, TOmitInit>>, context: StructSerializationContext): ArrayBuffer {
|
public serialize(init: Evaluate<Omit<TFields, TOmitInit>>, context: StructSerializationContext): ArrayBuffer {
|
||||||
const object = this.create(init, context) as any;
|
const value = this.initializeStructValue();
|
||||||
|
|
||||||
|
for (const [name, definition] of this._fields) {
|
||||||
|
const fieldValue = definition.create(this.options, context, value, (init as any)[name]);
|
||||||
|
value.set(name, fieldValue);
|
||||||
|
}
|
||||||
|
|
||||||
let structSize = 0;
|
let structSize = 0;
|
||||||
const fieldsInfo: { runtimeValue: FieldRuntimeValue, size: number; }[] = [];
|
const fieldsInfo: { fieldValue: StructFieldValue, size: number; }[] = [];
|
||||||
|
|
||||||
for (const [name] of this._fields) {
|
for (const [name] of this._fields) {
|
||||||
const runtimeValue = getRuntimeValue(object, name);
|
const fieldValue = value.get(name);
|
||||||
const size = runtimeValue.getSize();
|
const size = fieldValue.getSize();
|
||||||
fieldsInfo.push({ runtimeValue, size });
|
fieldsInfo.push({ fieldValue, size });
|
||||||
structSize += size;
|
structSize += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = new ArrayBuffer(structSize);
|
const buffer = new ArrayBuffer(structSize);
|
||||||
const dataView = new DataView(buffer);
|
const dataView = new DataView(buffer);
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for (const { runtimeValue, size } of fieldsInfo) {
|
for (const { fieldValue, size } of fieldsInfo) {
|
||||||
runtimeValue.serialize(dataView, offset, context);
|
fieldValue.serialize(dataView, offset, context);
|
||||||
offset += size;
|
offset += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
import { StructDeserializationContext, StructSerializationContext } from '../basic';
|
import { StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext, StructValue } from '../basic';
|
||||||
import { ArrayBufferFieldType, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer';
|
import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer';
|
||||||
|
|
||||||
describe('Types', () => {
|
describe('Types', () => {
|
||||||
|
describe('ArrayBufferLike', () => {
|
||||||
describe('ArrayBufferFieldType', () => {
|
describe('ArrayBufferFieldType', () => {
|
||||||
it('should have a static instance', () => {
|
it('should have a static instance', () => {
|
||||||
expect(ArrayBufferFieldType.instance).toBeInstanceOf(ArrayBufferFieldType);
|
expect(ArrayBufferFieldType.instance).toBeInstanceOf(ArrayBufferFieldType);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('`toArrayBuffer` should return the same `ArrayBuffer`', () => {
|
it('`#toArrayBuffer` should return the same `ArrayBuffer`', () => {
|
||||||
const arrayBuffer = new ArrayBuffer(10);
|
const arrayBuffer = new ArrayBuffer(10);
|
||||||
expect(ArrayBufferFieldType.instance.toArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
|
expect(ArrayBufferFieldType.instance.toArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('`fromArrayBuffer` should return the same `ArrayBuffer`', () => {
|
it('`#fromArrayBuffer` should return the same `ArrayBuffer`', () => {
|
||||||
const arrayBuffer = new ArrayBuffer(10);
|
const arrayBuffer = new ArrayBuffer(10);
|
||||||
expect(ArrayBufferFieldType.instance.fromArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
|
expect(ArrayBufferFieldType.instance.fromArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('`getSize` should return the `byteLength` of the `ArrayBuffer`', () => {
|
it('`#getSize` should return the `byteLength` of the `ArrayBuffer`', () => {
|
||||||
const arrayBuffer = new ArrayBuffer(10);
|
const arrayBuffer = new ArrayBuffer(10);
|
||||||
expect(ArrayBufferFieldType.instance.getSize(arrayBuffer)).toBe(10);
|
expect(ArrayBufferFieldType.instance.getSize(arrayBuffer)).toBe(10);
|
||||||
});
|
});
|
||||||
|
@ -28,13 +29,13 @@ describe('Types', () => {
|
||||||
expect(Uint8ClampedArrayFieldType.instance).toBeInstanceOf(Uint8ClampedArrayFieldType);
|
expect(Uint8ClampedArrayFieldType.instance).toBeInstanceOf(Uint8ClampedArrayFieldType);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('`toArrayBuffer` should return its `buffer`', () => {
|
it('`#toArrayBuffer` should return its `buffer`', () => {
|
||||||
const array = new Uint8ClampedArray(10);
|
const array = new Uint8ClampedArray(10);
|
||||||
const buffer = array.buffer;
|
const buffer = array.buffer;
|
||||||
expect(Uint8ClampedArrayFieldType.instance.toArrayBuffer(array)).toBe(buffer);
|
expect(Uint8ClampedArrayFieldType.instance.toArrayBuffer(array)).toBe(buffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('`fromArrayBuffer` should return a view of the `ArrayBuffer`', () => {
|
it('`#fromArrayBuffer` should return a view of the `ArrayBuffer`', () => {
|
||||||
const arrayBuffer = new ArrayBuffer(10);
|
const arrayBuffer = new ArrayBuffer(10);
|
||||||
const array = Uint8ClampedArrayFieldType.instance.fromArrayBuffer(arrayBuffer);
|
const array = Uint8ClampedArrayFieldType.instance.fromArrayBuffer(arrayBuffer);
|
||||||
expect(array).toHaveProperty('buffer', arrayBuffer);
|
expect(array).toHaveProperty('buffer', arrayBuffer);
|
||||||
|
@ -42,7 +43,7 @@ describe('Types', () => {
|
||||||
expect(array).toHaveProperty('byteLength', 10);
|
expect(array).toHaveProperty('byteLength', 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('`getSize` should return the `byteLength` of the `ArrayBuffer`', () => {
|
it('`#getSize` should return the `byteLength` of the `Uint8ClampedArray`', () => {
|
||||||
const array = new Uint8ClampedArray(10);
|
const array = new Uint8ClampedArray(10);
|
||||||
expect(Uint8ClampedArrayFieldType.instance.getSize(array)).toBe(10);
|
expect(Uint8ClampedArrayFieldType.instance.getSize(array)).toBe(10);
|
||||||
});
|
});
|
||||||
|
@ -53,7 +54,7 @@ describe('Types', () => {
|
||||||
expect(StringFieldType.instance).toBeInstanceOf(StringFieldType);
|
expect(StringFieldType.instance).toBeInstanceOf(StringFieldType);
|
||||||
});
|
});
|
||||||
|
|
||||||
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: StructSerializationContext = {
|
||||||
|
@ -64,7 +65,7 @@ describe('Types', () => {
|
||||||
expect(StringFieldType.instance.toArrayBuffer(text, context)).toEqual(arrayBuffer);
|
expect(StringFieldType.instance.toArrayBuffer(text, context)).toEqual(arrayBuffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('`fromArrayBuffer` should return the encoded ArrayBuffer', () => {
|
it('`#fromArrayBuffer` should return the encoded ArrayBuffer', () => {
|
||||||
const text = 'foo';
|
const text = 'foo';
|
||||||
const arrayBuffer = Buffer.from(text, 'utf-8');
|
const arrayBuffer = Buffer.from(text, 'utf-8');
|
||||||
const context: StructDeserializationContext = {
|
const context: StructDeserializationContext = {
|
||||||
|
@ -81,8 +82,149 @@ describe('Types', () => {
|
||||||
expect(StringFieldType.instance.fromArrayBuffer(arrayBuffer, context)).toBe(text);
|
expect(StringFieldType.instance.fromArrayBuffer(arrayBuffer, context)).toBe(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('`getSize` should return -1', () => {
|
it('`#getSize` should return -1', () => {
|
||||||
expect(StringFieldType.instance.getSize()).toBe(-1);
|
expect(StringFieldType.instance.getSize()).toBe(-1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
class MockArrayBufferFieldDefinition<TType extends ArrayBufferLikeFieldType>
|
||||||
|
extends ArrayBufferLikeFieldDefinition<TType, number> {
|
||||||
|
public getSize(): number {
|
||||||
|
return this.options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
expect(read).toBeCalledTimes(1);
|
||||||
|
expect(read).toBeCalledWith(size);
|
||||||
|
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
|
||||||
|
|
||||||
|
const value = fieldValue.get();
|
||||||
|
expect(value).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 fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
expect(read).toBeCalledTimes(1);
|
||||||
|
expect(read).toBeCalledWith(10);
|
||||||
|
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
|
||||||
|
|
||||||
|
const value = fieldValue.get();
|
||||||
|
expect(value).toBeInstanceOf(Uint8ClampedArray);
|
||||||
|
expect(value).toHaveProperty('buffer', buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
expect(read).toBeCalledTimes(0);
|
||||||
|
expect(fieldValue.arrayBuffer).toBeInstanceOf(ArrayBuffer);
|
||||||
|
expect(fieldValue.arrayBuffer).toHaveProperty('byteLength', 0);
|
||||||
|
|
||||||
|
const value = fieldValue.get();
|
||||||
|
expect(value).toBeInstanceOf(ArrayBuffer);
|
||||||
|
expect(value).toHaveProperty('byteLength', 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ArrayBufferLikeFieldValue', () => {
|
||||||
|
describe('#set', () => {
|
||||||
|
it('should clear `arrayBuffer` field', async () => {
|
||||||
|
const size = 10;
|
||||||
|
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
||||||
|
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 fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
|
||||||
|
fieldValue.set(new ArrayBuffer(20));
|
||||||
|
expect(fieldValue).toHaveProperty('arrayBuffer', undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#serialize', () => {
|
||||||
|
it('should be able to serialize a deserialized value', async () => {
|
||||||
|
const size = 10;
|
||||||
|
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
||||||
|
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 struct = new StructValue();
|
||||||
|
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
|
||||||
|
const targetArray = new Uint8Array(size);
|
||||||
|
const targetView = new DataView(targetArray.buffer);
|
||||||
|
fieldValue.serialize(targetView, 0, context);
|
||||||
|
|
||||||
|
expect(targetArray).toEqual(sourceArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize a modified value', async () => {
|
||||||
|
const size = 10;
|
||||||
|
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
||||||
|
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 struct = new StructValue();
|
||||||
|
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
fieldValue.set(sourceArray.buffer);
|
||||||
|
|
||||||
|
const targetArray = new Uint8Array(size);
|
||||||
|
const targetView = new DataView(targetArray.buffer);
|
||||||
|
fieldValue.serialize(targetView, 0, context);
|
||||||
|
|
||||||
|
expect(targetArray).toEqual(sourceArray);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { FieldDefinition, FieldRuntimeValue, StructDeserializationContext, StructOptions, StructSerializationContext } from '../basic';
|
import { StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all types that
|
* Base class for all types that
|
||||||
* can be converted from an ArrayBuffer when deserialized,
|
* can be converted from an ArrayBuffer when deserialized,
|
||||||
* and need to be converted to an ArrayBuffer when serializing
|
* and need to be converted to an ArrayBuffer when serializing
|
||||||
*
|
*
|
||||||
* @template TType The actual TypeScript type of this type
|
* @template TValue The actual TypeScript type of this type
|
||||||
* @template TTypeScriptType Optional another type (should be compatible with `TType`)
|
* @template TTypeScriptType Optional another type (should be compatible with `TType`)
|
||||||
* specified by user when creating field definitions.
|
* specified by user when creating field definitions.
|
||||||
*/
|
*/
|
||||||
export abstract class ArrayBufferLikeFieldType<TType = unknown, TTypeScriptType = TType> {
|
export abstract class ArrayBufferLikeFieldType<TValue = unknown, TTypeScriptType = TValue> {
|
||||||
public readonly valueType!: TTypeScriptType;
|
public readonly valueType!: TTypeScriptType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,10 +18,10 @@ export abstract class ArrayBufferLikeFieldType<TType = unknown, TTypeScriptType
|
||||||
* This function should be "pure", i.e.,
|
* This function should be "pure", i.e.,
|
||||||
* same `value` should always be converted to `ArrayBuffer`s that have same content.
|
* same `value` should always be converted to `ArrayBuffer`s that have same content.
|
||||||
*/
|
*/
|
||||||
public abstract toArrayBuffer(value: TType, context: StructSerializationContext): ArrayBuffer;
|
public abstract toArrayBuffer(value: TValue, context: StructSerializationContext): ArrayBuffer;
|
||||||
|
|
||||||
/** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */
|
/** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */
|
||||||
public abstract fromArrayBuffer(arrayBuffer: ArrayBuffer, context: StructDeserializationContext): TType;
|
public abstract fromArrayBuffer(arrayBuffer: ArrayBuffer, context: StructDeserializationContext): TValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When implemented in derived classes, gets the size in byte of the type-specific `value`.
|
* When implemented in derived classes, gets the size in byte of the type-specific `value`.
|
||||||
|
@ -30,7 +30,7 @@ export abstract class ArrayBufferLikeFieldType<TType = unknown, TTypeScriptType
|
||||||
* implementer should returns `-1` so the caller will get its size by first converting it to
|
* implementer should returns `-1` so the caller will get its size by first converting it to
|
||||||
* an `ArrayBuffer` (and cache the result).
|
* an `ArrayBuffer` (and cache the result).
|
||||||
*/
|
*/
|
||||||
public abstract getSize(value: TType): number;
|
public abstract getSize(value: TValue): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An ArrayBufferLike type that's actually an `ArrayBuffer` */
|
/** An ArrayBufferLike type that's actually an `ArrayBuffer` */
|
||||||
|
@ -54,7 +54,7 @@ export class ArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Am ArrayBufferLike type that converts to/from the `ArrayBuffer` from/to a `Uint8ClampedArray` */
|
/** Am ArrayBufferLike type that converts between `ArrayBuffer` and `Uint8ClampedArray` */
|
||||||
export class Uint8ClampedArrayFieldType
|
export class Uint8ClampedArrayFieldType
|
||||||
extends ArrayBufferLikeFieldType<Uint8ClampedArray, Uint8ClampedArray> {
|
extends ArrayBufferLikeFieldType<Uint8ClampedArray, Uint8ClampedArray> {
|
||||||
public static readonly instance = new Uint8ClampedArrayFieldType();
|
public static readonly instance = new Uint8ClampedArrayFieldType();
|
||||||
|
@ -76,7 +76,7 @@ export class Uint8ClampedArrayFieldType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Am ArrayBufferLike type that converts to/from the `ArrayBuffer` from/to a `string` */
|
/** Am ArrayBufferLike type that converts between `ArrayBuffer` and `string` */
|
||||||
export class StringFieldType<TTypeScriptType = string>
|
export class StringFieldType<TTypeScriptType = string>
|
||||||
extends ArrayBufferLikeFieldType<string, TTypeScriptType> {
|
extends ArrayBufferLikeFieldType<string, TTypeScriptType> {
|
||||||
public static readonly instance = new StringFieldType();
|
public static readonly instance = new StringFieldType();
|
||||||
|
@ -102,11 +102,11 @@ const EmptyArrayBuffer = new ArrayBuffer(0);
|
||||||
export abstract class ArrayBufferLikeFieldDefinition<
|
export abstract class ArrayBufferLikeFieldDefinition<
|
||||||
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
|
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
|
||||||
TOptions = void,
|
TOptions = void,
|
||||||
TOmitInit = never,
|
TOmitInitKey = never,
|
||||||
> extends FieldDefinition<
|
> extends StructFieldDefinition<
|
||||||
TOptions,
|
TOptions,
|
||||||
TType['valueType'],
|
TType['valueType'],
|
||||||
TOmitInit
|
TOmitInitKey
|
||||||
>{
|
>{
|
||||||
public readonly type: TType;
|
public readonly type: TType;
|
||||||
|
|
||||||
|
@ -115,16 +115,28 @@ export abstract class ArrayBufferLikeFieldDefinition<
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getDeserializeSize(object: any): number {
|
protected getDeserializeSize(struct: StructValue): number {
|
||||||
return this.getSize();
|
return this.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When implemented in derived classes, creates a `StructFieldValue` for the current field definition.
|
||||||
|
*/
|
||||||
|
public create(
|
||||||
|
options: Readonly<StructOptions>,
|
||||||
|
context: StructSerializationContext,
|
||||||
|
struct: StructValue,
|
||||||
|
value: TType['valueType'],
|
||||||
|
): ArrayBufferLikeFieldValue<this> {
|
||||||
|
return new ArrayBufferLikeFieldValue(this, options, context, struct, value);
|
||||||
|
}
|
||||||
|
|
||||||
public async deserialize(
|
public async deserialize(
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
context: StructDeserializationContext,
|
context: StructDeserializationContext,
|
||||||
object: any,
|
struct: StructValue,
|
||||||
): Promise<ArrayBufferLikeFieldRuntimeValue<ArrayBufferLikeFieldDefinition<TType, TOptions, TOmitInit>>> {
|
): Promise<ArrayBufferLikeFieldValue<this>> {
|
||||||
const size = this.getDeserializeSize(object);
|
const size = this.getDeserializeSize(struct);
|
||||||
|
|
||||||
let arrayBuffer: ArrayBuffer;
|
let arrayBuffer: ArrayBuffer;
|
||||||
if (size === 0) {
|
if (size === 0) {
|
||||||
|
@ -134,25 +146,15 @@ export abstract class ArrayBufferLikeFieldDefinition<
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = this.type.fromArrayBuffer(arrayBuffer, context);
|
const value = this.type.fromArrayBuffer(arrayBuffer, context);
|
||||||
const runtimeValue = this.createValue(options, context, object, value);
|
const fieldValue = this.create(options, context, struct, value);
|
||||||
runtimeValue.arrayBuffer = arrayBuffer;
|
fieldValue.arrayBuffer = arrayBuffer;
|
||||||
return runtimeValue;
|
return fieldValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class ArrayBufferLikeFieldValue<
|
||||||
* When implemented in derived classes, creates a `FieldRuntimeValue` for the current field definition.
|
TDefinition extends ArrayBufferLikeFieldDefinition<ArrayBufferLikeFieldType, any, any>,
|
||||||
*/
|
> extends StructFieldValue<TDefinition> {
|
||||||
public abstract createValue(
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
context: StructSerializationContext,
|
|
||||||
object: any,
|
|
||||||
value: TType['valueType'],
|
|
||||||
): ArrayBufferLikeFieldRuntimeValue<ArrayBufferLikeFieldDefinition<TType, TOptions, TOmitInit>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ArrayBufferLikeFieldRuntimeValue<
|
|
||||||
TDefinition extends ArrayBufferLikeFieldDefinition<any, any, any>,
|
|
||||||
> extends FieldRuntimeValue<TDefinition> {
|
|
||||||
public arrayBuffer: ArrayBuffer | undefined;
|
public arrayBuffer: ArrayBuffer | undefined;
|
||||||
|
|
||||||
public set(value: TDefinition['valueType']): void {
|
public set(value: TDefinition['valueType']): void {
|
||||||
|
@ -166,6 +168,6 @@ export class ArrayBufferLikeFieldRuntimeValue<
|
||||||
}
|
}
|
||||||
|
|
||||||
new Uint8Array(dataView.buffer)
|
new Uint8Array(dataView.buffer)
|
||||||
.set(new Uint8Array(this.arrayBuffer!), offset);
|
.set(new Uint8Array(this.arrayBuffer), offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
packages/struct/src/types/fixed-length-array-buffer.spec.ts
Normal file
13
packages/struct/src/types/fixed-length-array-buffer.spec.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { ArrayBufferFieldType } from './array-buffer';
|
||||||
|
import { FixedLengthArrayBufferLikeFieldDefinition } from './fixed-length-array-buffer';
|
||||||
|
|
||||||
|
describe('Types', () => {
|
||||||
|
describe('FixedLengthArrayBufferLikeFieldDefinition', () => {
|
||||||
|
describe('#getSize', () => {
|
||||||
|
it('should return size in its options', () => {
|
||||||
|
const definition = new FixedLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { length: 10 });
|
||||||
|
expect(definition.getSize()).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,4 @@
|
||||||
import { StructOptions, StructSerializationContext } from '../basic';
|
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType } from './array-buffer';
|
||||||
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldRuntimeValue, ArrayBufferLikeFieldType } from './array-buffer';
|
|
||||||
|
|
||||||
export interface FixedLengthArrayBufferLikeFieldOptions {
|
export interface FixedLengthArrayBufferLikeFieldOptions {
|
||||||
length: number;
|
length: number;
|
||||||
|
@ -15,13 +14,4 @@ export class FixedLengthArrayBufferLikeFieldDefinition<
|
||||||
public getSize(): number {
|
public getSize(): number {
|
||||||
return this.options.length;
|
return this.options.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public createValue(
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
context: StructSerializationContext,
|
|
||||||
object: any,
|
|
||||||
value: TType['valueType']
|
|
||||||
): ArrayBufferLikeFieldRuntimeValue<FixedLengthArrayBufferLikeFieldDefinition<TType, TOptions>> {
|
|
||||||
return new ArrayBufferLikeFieldRuntimeValue(this, options, context, object, value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
export * from './array-buffer';
|
export * from './array-buffer';
|
||||||
export * from './fixed-length-array-buffer';
|
export * from './fixed-length-array-buffer';
|
||||||
export * from './number';
|
export * from './number';
|
||||||
export * from './utils';
|
|
||||||
export * from './variable-length-array-buffer';
|
export * from './variable-length-array-buffer';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { StructDefaultOptions, StructDeserializationContext, StructSerializationContext } from '../basic';
|
import { StructDefaultOptions, StructDeserializationContext, StructSerializationContext, StructValue } from '../basic';
|
||||||
import { NumberFieldDefinition, NumberFieldRuntimeValue, NumberFieldType } from './number';
|
import { NumberFieldDefinition, NumberFieldType } from './number';
|
||||||
|
|
||||||
describe('Types', () => {
|
describe('Types', () => {
|
||||||
describe('Number', () => {
|
describe('Number', () => {
|
||||||
|
@ -62,41 +62,20 @@ describe('Types', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('NumberFieldDefinition', () => {
|
describe('NumberFieldDefinition', () => {
|
||||||
describe('getSize', () => {
|
describe('#getSize', () => {
|
||||||
it('should return 1 for int8', () => {
|
it('should return size of its type', () => {
|
||||||
expect(new NumberFieldDefinition(NumberFieldType.Int8).getSize()).toBe(1);
|
expect(new NumberFieldDefinition(NumberFieldType.Int8).getSize()).toBe(1);
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 1 for uint8', () => {
|
|
||||||
expect(new NumberFieldDefinition(NumberFieldType.Uint8).getSize()).toBe(1);
|
expect(new NumberFieldDefinition(NumberFieldType.Uint8).getSize()).toBe(1);
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 2 for int16', () => {
|
|
||||||
expect(new NumberFieldDefinition(NumberFieldType.Int16).getSize()).toBe(2);
|
expect(new NumberFieldDefinition(NumberFieldType.Int16).getSize()).toBe(2);
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 2 for uint16', () => {
|
|
||||||
expect(new NumberFieldDefinition(NumberFieldType.Uint16).getSize()).toBe(2);
|
expect(new NumberFieldDefinition(NumberFieldType.Uint16).getSize()).toBe(2);
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 4 for int32', () => {
|
|
||||||
expect(new NumberFieldDefinition(NumberFieldType.Int32).getSize()).toBe(4);
|
expect(new NumberFieldDefinition(NumberFieldType.Int32).getSize()).toBe(4);
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 4 for uint32', () => {
|
|
||||||
expect(new NumberFieldDefinition(NumberFieldType.Uint32).getSize()).toBe(4);
|
expect(new NumberFieldDefinition(NumberFieldType.Uint32).getSize()).toBe(4);
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 8 for int64', () => {
|
|
||||||
expect(new NumberFieldDefinition(NumberFieldType.Int64).getSize()).toBe(8);
|
expect(new NumberFieldDefinition(NumberFieldType.Int64).getSize()).toBe(8);
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 8 for uint64', () => {
|
|
||||||
expect(new NumberFieldDefinition(NumberFieldType.Uint64).getSize()).toBe(8);
|
expect(new NumberFieldDefinition(NumberFieldType.Uint64).getSize()).toBe(8);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deserialize', () => {
|
describe('#deserialize', () => {
|
||||||
it('should deserialize Uint8', async () => {
|
it('should deserialize Uint8', async () => {
|
||||||
const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer);
|
const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer);
|
||||||
const context: StructDeserializationContext = {
|
const context: StructDeserializationContext = {
|
||||||
|
@ -106,10 +85,11 @@ describe('Types', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Uint8);
|
const definition = new NumberFieldDefinition(NumberFieldType.Uint8);
|
||||||
|
const struct = new StructValue();
|
||||||
const value = await definition.deserialize(
|
const value = await definition.deserialize(
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
context,
|
context,
|
||||||
{}
|
struct,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(value.get()).toBe(1);
|
expect(value.get()).toBe(1);
|
||||||
|
@ -126,10 +106,11 @@ describe('Types', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
|
const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
|
||||||
|
const struct = new StructValue();
|
||||||
const value = await definition.deserialize(
|
const value = await definition.deserialize(
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
context,
|
context,
|
||||||
{}
|
struct,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(value.get()).toBe((1 << 8) | 2);
|
expect(value.get()).toBe((1 << 8) | 2);
|
||||||
|
@ -146,10 +127,11 @@ describe('Types', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
|
const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
|
||||||
|
const struct = new StructValue();
|
||||||
const value = await definition.deserialize(
|
const value = await definition.deserialize(
|
||||||
{ ...StructDefaultOptions, littleEndian: true },
|
{ ...StructDefaultOptions, littleEndian: true },
|
||||||
context,
|
context,
|
||||||
{}
|
struct,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(value.get()).toBe((2 << 8) | 1);
|
expect(value.get()).toBe((2 << 8) | 1);
|
||||||
|
@ -159,100 +141,121 @@ describe('Types', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('NumberRuntimeValue', () => {
|
describe('NumberFieldValue', () => {
|
||||||
describe('getSize', () => {
|
describe('#getSize', () => {
|
||||||
it('should return 1 for int8', () => {
|
it('should return size of its definition', () => {
|
||||||
const context: StructSerializationContext = {
|
const context: StructSerializationContext = {
|
||||||
encodeUtf8(input) { throw new Error(''); },
|
encodeUtf8(input) { throw new Error(''); },
|
||||||
};
|
};
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Int8);
|
const struct = new StructValue();
|
||||||
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
|
|
||||||
|
|
||||||
expect(runtimeValue.getSize()).toBe(1);
|
expect(
|
||||||
});
|
new NumberFieldDefinition(NumberFieldType.Int8)
|
||||||
|
.create(
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
42,
|
||||||
|
)
|
||||||
|
.getSize()
|
||||||
|
).toBe(1);
|
||||||
|
|
||||||
it('should return 1 for uint8', () => {
|
expect(
|
||||||
const context: StructSerializationContext = {
|
new NumberFieldDefinition(NumberFieldType.Uint8)
|
||||||
encodeUtf8(input) { throw new Error(''); },
|
.create(
|
||||||
};
|
StructDefaultOptions,
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Uint8);
|
context,
|
||||||
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
|
struct,
|
||||||
|
42,
|
||||||
|
)
|
||||||
|
.getSize()
|
||||||
|
).toBe(1);
|
||||||
|
|
||||||
expect(runtimeValue.getSize()).toBe(1);
|
expect(
|
||||||
});
|
new NumberFieldDefinition(NumberFieldType.Int16)
|
||||||
|
.create(
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
42,
|
||||||
|
)
|
||||||
|
.getSize()
|
||||||
|
).toBe(2);
|
||||||
|
|
||||||
it('should return 2 for int16', () => {
|
expect(
|
||||||
const context: StructSerializationContext = {
|
new NumberFieldDefinition(NumberFieldType.Uint16)
|
||||||
encodeUtf8(input) { throw new Error(''); },
|
.create(
|
||||||
};
|
StructDefaultOptions,
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Int16);
|
context,
|
||||||
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
|
struct,
|
||||||
|
42,
|
||||||
|
)
|
||||||
|
.getSize()
|
||||||
|
).toBe(2);
|
||||||
|
|
||||||
expect(runtimeValue.getSize()).toBe(2);
|
expect(
|
||||||
});
|
new NumberFieldDefinition(NumberFieldType.Int32)
|
||||||
|
.create(
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
42,
|
||||||
|
)
|
||||||
|
.getSize()
|
||||||
|
).toBe(4);
|
||||||
|
|
||||||
it('should return 2 for uint16', () => {
|
expect(
|
||||||
const context: StructSerializationContext = {
|
new NumberFieldDefinition(NumberFieldType.Uint32)
|
||||||
encodeUtf8(input) { throw new Error(''); },
|
.create(
|
||||||
};
|
StructDefaultOptions,
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
|
context,
|
||||||
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
|
struct,
|
||||||
|
42,
|
||||||
|
)
|
||||||
|
.getSize()
|
||||||
|
).toBe(4);
|
||||||
|
|
||||||
expect(runtimeValue.getSize()).toBe(2);
|
expect(
|
||||||
});
|
new NumberFieldDefinition(NumberFieldType.Int64)
|
||||||
|
.create(
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
BigInt(100),
|
||||||
|
)
|
||||||
|
.getSize()
|
||||||
|
).toBe(8);
|
||||||
|
|
||||||
it('should return 4 for int32', () => {
|
expect(
|
||||||
const context: StructSerializationContext = {
|
new NumberFieldDefinition(NumberFieldType.Uint64)
|
||||||
encodeUtf8(input) { throw new Error(''); },
|
.create(
|
||||||
};
|
StructDefaultOptions,
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Int32);
|
context,
|
||||||
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
|
struct,
|
||||||
|
BigInt(100),
|
||||||
expect(runtimeValue.getSize()).toBe(4);
|
)
|
||||||
});
|
.getSize()
|
||||||
|
).toBe(8);
|
||||||
it('should return 4 for uint32', () => {
|
|
||||||
const context: StructSerializationContext = {
|
|
||||||
encodeUtf8(input) { throw new Error(''); },
|
|
||||||
};
|
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Uint32);
|
|
||||||
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
|
|
||||||
|
|
||||||
expect(runtimeValue.getSize()).toBe(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 8 for int64', () => {
|
|
||||||
const context: StructSerializationContext = {
|
|
||||||
encodeUtf8(input) { throw new Error(''); },
|
|
||||||
};
|
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Int64);
|
|
||||||
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, BigInt(42));
|
|
||||||
|
|
||||||
expect(runtimeValue.getSize()).toBe(8);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 8 for uint64', () => {
|
|
||||||
const context: StructSerializationContext = {
|
|
||||||
encodeUtf8(input) { throw new Error(''); },
|
|
||||||
};
|
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Uint64);
|
|
||||||
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, BigInt(42));
|
|
||||||
|
|
||||||
expect(runtimeValue.getSize()).toBe(8);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('serialize', () => {
|
describe('#serialize', () => {
|
||||||
it('should serialize uint8', () => {
|
it('should serialize uint8', () => {
|
||||||
const context: StructSerializationContext = {
|
const context: StructSerializationContext = {
|
||||||
encodeUtf8(input) { throw new Error(''); },
|
encodeUtf8(input) { throw new Error(''); },
|
||||||
};
|
};
|
||||||
const definition = new NumberFieldDefinition(NumberFieldType.Int8);
|
const definition = new NumberFieldDefinition(NumberFieldType.Int8);
|
||||||
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
|
const struct = new StructValue();
|
||||||
|
const value = definition.create(
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
42,
|
||||||
|
);
|
||||||
|
|
||||||
const array = new Uint8Array(10);
|
const array = new Uint8Array(10);
|
||||||
const dataView = new DataView(array.buffer);
|
const dataView = new DataView(array.buffer);
|
||||||
runtimeValue.serialize(dataView, 2);
|
value.serialize(dataView, 2);
|
||||||
|
|
||||||
expect(Array.from(array)).toEqual([0, 0, 42, 0, 0, 0, 0, 0, 0, 0]);
|
expect(Array.from(array)).toEqual([0, 0, 42, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FieldDefinition, FieldRuntimeValue, StructDeserializationContext, StructOptions, StructSerializationContext } from '../basic';
|
import { StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic';
|
||||||
|
|
||||||
export type DataViewGetters =
|
export type DataViewGetters =
|
||||||
{ [TKey in keyof DataView]: TKey extends `get${string}` ? TKey : never }[keyof DataView];
|
{ [TKey in keyof DataView]: TKey extends `get${string}` ? TKey : never }[keyof DataView];
|
||||||
|
@ -45,7 +45,7 @@ export class NumberFieldType<TTypeScriptType extends number | bigint = number |
|
||||||
export class NumberFieldDefinition<
|
export class NumberFieldDefinition<
|
||||||
TType extends NumberFieldType = NumberFieldType,
|
TType extends NumberFieldType = NumberFieldType,
|
||||||
TTypeScriptType = TType['valueType'],
|
TTypeScriptType = TType['valueType'],
|
||||||
> extends FieldDefinition<
|
> extends StructFieldDefinition<
|
||||||
void,
|
void,
|
||||||
TTypeScriptType
|
TTypeScriptType
|
||||||
> {
|
> {
|
||||||
|
@ -60,34 +60,33 @@ export class NumberFieldDefinition<
|
||||||
return this.type.size;
|
return this.type.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public create(
|
||||||
|
options: Readonly<StructOptions>,
|
||||||
|
context: StructSerializationContext,
|
||||||
|
struct: StructValue,
|
||||||
|
value: TTypeScriptType,
|
||||||
|
): NumberFieldValue<this> {
|
||||||
|
return new NumberFieldValue(this, options, context, struct, value);
|
||||||
|
}
|
||||||
|
|
||||||
public async deserialize(
|
public async deserialize(
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
context: StructDeserializationContext,
|
context: StructDeserializationContext,
|
||||||
object: any,
|
struct: StructValue,
|
||||||
): Promise<NumberFieldRuntimeValue<TType, TTypeScriptType>> {
|
): Promise<NumberFieldValue<this>> {
|
||||||
const buffer = await context.read(this.getSize());
|
const buffer = await context.read(this.getSize());
|
||||||
const view = new DataView(buffer);
|
const view = new DataView(buffer);
|
||||||
const value = view[this.type.dataViewGetter](
|
const value = view[this.type.dataViewGetter](
|
||||||
0,
|
0,
|
||||||
options.littleEndian
|
options.littleEndian
|
||||||
) as any;
|
) as any;
|
||||||
return this.createValue(options, context, object, value);
|
return this.create(options, context, struct, value);
|
||||||
}
|
|
||||||
|
|
||||||
public createValue(
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
context: StructSerializationContext,
|
|
||||||
object: any,
|
|
||||||
value: TTypeScriptType,
|
|
||||||
): NumberFieldRuntimeValue<TType, TTypeScriptType> {
|
|
||||||
return new NumberFieldRuntimeValue(this, options, context, object, value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NumberFieldRuntimeValue<
|
export class NumberFieldValue<
|
||||||
TType extends NumberFieldType = NumberFieldType,
|
TDefinition extends NumberFieldDefinition<NumberFieldType, any>,
|
||||||
TTypeScriptType = TType['valueType'],
|
> extends StructFieldValue<TDefinition> {
|
||||||
> extends FieldRuntimeValue<NumberFieldDefinition<TType, TTypeScriptType>> {
|
|
||||||
public serialize(dataView: DataView, offset: number): void {
|
public serialize(dataView: DataView, offset: number): void {
|
||||||
// `setBigInt64` requires a `bigint` while others require `number`
|
// `setBigInt64` requires a `bigint` while others require `number`
|
||||||
// So `dataView[DataViewSetters]` requires `bigint & number`
|
// So `dataView[DataViewSetters]` requires `bigint & number`
|
||||||
|
|
468
packages/struct/src/types/variable-length-array-buffer.spec.ts
Normal file
468
packages/struct/src/types/variable-length-array-buffer.spec.ts
Normal file
|
@ -0,0 +1,468 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
describe('Types', () => {
|
||||||
|
describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
expect(read).toBeCalledTimes(1);
|
||||||
|
expect(read).toBeCalledWith(size);
|
||||||
|
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
|
||||||
|
|
||||||
|
const value = fieldValue.get();
|
||||||
|
expect(value).toBe(buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
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()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
expect(read).toBeCalledTimes(1);
|
||||||
|
expect(read).toBeCalledWith(size);
|
||||||
|
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
|
||||||
|
|
||||||
|
const value = fieldValue.get();
|
||||||
|
expect(value).toBe(buffer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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(''); },
|
||||||
|
};
|
||||||
|
const struct = new StructValue();
|
||||||
|
|
||||||
|
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||||
|
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||||
|
StringFieldType.instance,
|
||||||
|
{ length: 2 },
|
||||||
|
),
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
size.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
struct.set(lengthField, originalLengthFieldValue,);
|
||||||
|
|
||||||
|
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||||
|
ArrayBufferFieldType.instance,
|
||||||
|
{ lengthField },
|
||||||
|
);
|
||||||
|
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||||
|
definition,
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
buffer,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(fieldValue).toHaveProperty('definition', definition);
|
||||||
|
|
||||||
|
expect(struct.fieldValues[lengthField]).not.toBe(originalLengthFieldValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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(''); },
|
||||||
|
};
|
||||||
|
const struct = new StructValue();
|
||||||
|
|
||||||
|
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||||
|
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||||
|
StringFieldType.instance,
|
||||||
|
{ length: 2 },
|
||||||
|
),
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
size.toString()
|
||||||
|
);
|
||||||
|
struct.set(lengthField, originalLengthFieldValue,);
|
||||||
|
|
||||||
|
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||||
|
ArrayBufferFieldType.instance,
|
||||||
|
{ lengthField },
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
expect(fieldValue.getSize()).toBe(size);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||||
|
new VariableLengthArrayBufferLikeFieldDefinition(
|
||||||
|
ArrayBufferFieldType.instance,
|
||||||
|
{ lengthField },
|
||||||
|
),
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
buffer,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(fieldValue.getSize()).toBe(size);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||||
|
new VariableLengthArrayBufferLikeFieldDefinition(
|
||||||
|
StringFieldType.instance,
|
||||||
|
{ lengthField },
|
||||||
|
),
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
'test',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(fieldValue.getSize()).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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(''); },
|
||||||
|
};
|
||||||
|
const struct = new StructValue();
|
||||||
|
|
||||||
|
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||||
|
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||||
|
StringFieldType.instance,
|
||||||
|
{ length: 2 },
|
||||||
|
),
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
size.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
struct.set(lengthField, originalLengthFieldValue,);
|
||||||
|
|
||||||
|
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||||
|
new VariableLengthArrayBufferLikeFieldDefinition(
|
||||||
|
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', () => {
|
||||||
|
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()
|
||||||
|
);
|
||||||
|
|
||||||
|
struct.set(lengthField, originalLengthFieldValue,);
|
||||||
|
|
||||||
|
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||||
|
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('#getSize', () => {
|
||||||
|
it('should return size of its original field value', () => {
|
||||||
|
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 definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||||
|
ArrayBufferFieldType.instance,
|
||||||
|
{ lengthField },
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldValue = (await definition.deserialize(StructDefaultOptions, context, struct)) as any as VariableLengthArrayBufferLikeStructFieldValue;
|
||||||
|
expect(fieldValue['lengthFieldValue'].get()).toBe(size);
|
||||||
|
});
|
||||||
|
|
||||||
|
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(''); },
|
||||||
|
};
|
||||||
|
const struct = new StructValue();
|
||||||
|
|
||||||
|
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||||
|
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||||
|
StringFieldType.instance,
|
||||||
|
{ length: 2 },
|
||||||
|
),
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
size.toString()
|
||||||
|
);
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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(''); },
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct = new StructValue();
|
||||||
|
|
||||||
|
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
|
||||||
|
new FixedLengthArrayBufferLikeFieldDefinition(
|
||||||
|
StringFieldType.instance,
|
||||||
|
{ length: 2 },
|
||||||
|
),
|
||||||
|
StructDefaultOptions,
|
||||||
|
context,
|
||||||
|
struct,
|
||||||
|
size.toString()
|
||||||
|
);
|
||||||
|
struct.set(lengthField, originalLengthFieldValue,);
|
||||||
|
|
||||||
|
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||||
|
ArrayBufferFieldType.instance,
|
||||||
|
{ lengthField },
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldValue = (await definition.deserialize(StructDefaultOptions, context, struct)) as any as VariableLengthArrayBufferLikeStructFieldValue;
|
||||||
|
|
||||||
|
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))));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
import { FieldRuntimeValue, getRuntimeValue, setRuntimeValue, StructOptions, StructSerializationContext } from '../basic';
|
import { StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic';
|
||||||
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldRuntimeValue, ArrayBufferLikeFieldType } from './array-buffer';
|
import { KeysOfType } from '../utils';
|
||||||
import { KeysOfType } from './utils';
|
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer';
|
||||||
|
|
||||||
export interface VariableLengthArrayBufferLikeFieldOptions<
|
export interface VariableLengthArrayBufferLikeFieldOptions<
|
||||||
TFields = object,
|
TFields = object,
|
||||||
|
@ -21,81 +21,49 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getDeserializeSize(object: any) {
|
protected getDeserializeSize(struct: StructValue) {
|
||||||
let value = object[this.options.lengthField] as number | string;
|
let value = struct.value[this.options.lengthField] as number | string;
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
value = Number.parseInt(value, 10);
|
value = Number.parseInt(value, 10);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public createValue(
|
public create(
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
context: StructSerializationContext,
|
context: StructSerializationContext,
|
||||||
object: any,
|
struct: StructValue,
|
||||||
value: TType['valueType'],
|
value: TType['valueType'],
|
||||||
): VariableLengthArrayBufferLikeFieldRuntimeValue<TType, TOptions> {
|
): VariableLengthArrayBufferLikeStructFieldValue<this> {
|
||||||
return new VariableLengthArrayBufferLikeFieldRuntimeValue(this, options, context, object, value);
|
return new VariableLengthArrayBufferLikeStructFieldValue(this, options, context, struct, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VariableLengthArrayBufferLikeLengthFieldRuntimeValue extends FieldRuntimeValue {
|
export class VariableLengthArrayBufferLikeStructFieldValue<
|
||||||
protected originalValue: FieldRuntimeValue;
|
TDefinition extends VariableLengthArrayBufferLikeFieldDefinition = VariableLengthArrayBufferLikeFieldDefinition,
|
||||||
|
> extends ArrayBufferLikeFieldValue<TDefinition> {
|
||||||
protected arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
originalValue: FieldRuntimeValue,
|
|
||||||
arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue,
|
|
||||||
) {
|
|
||||||
super(originalValue.definition, originalValue.options, originalValue.context, originalValue.object, 0);
|
|
||||||
this.originalValue = originalValue;
|
|
||||||
this.arrayBufferValue = arrayBufferValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSize() {
|
|
||||||
return this.originalValue.getSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
// TODO: originalValue might be a `string` type, now it always returns `number`.
|
|
||||||
return this.arrayBufferValue.getSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
set() { }
|
|
||||||
|
|
||||||
serialize(dataView: DataView, offset: number, context: StructSerializationContext) {
|
|
||||||
this.originalValue.set(this.get());
|
|
||||||
this.originalValue.serialize(dataView, offset, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class VariableLengthArrayBufferLikeFieldRuntimeValue<
|
|
||||||
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
|
|
||||||
TOptions extends VariableLengthArrayBufferLikeFieldOptions = VariableLengthArrayBufferLikeFieldOptions
|
|
||||||
> extends ArrayBufferLikeFieldRuntimeValue<VariableLengthArrayBufferLikeFieldDefinition<TType, TOptions>> {
|
|
||||||
public static getSize() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected length: number | undefined;
|
protected length: number | undefined;
|
||||||
|
|
||||||
protected lengthFieldValue: VariableLengthArrayBufferLikeLengthFieldRuntimeValue;
|
protected lengthFieldValue: VariableLengthArrayBufferLikeLengthStructFieldValue;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
definition: VariableLengthArrayBufferLikeFieldDefinition<TType, TOptions>,
|
definition: TDefinition,
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
context: StructSerializationContext,
|
context: StructSerializationContext,
|
||||||
object: any,
|
struct: StructValue,
|
||||||
value: TType['valueType'],
|
value: TDefinition['valueType'],
|
||||||
) {
|
) {
|
||||||
super(definition, options, context, object, value);
|
super(definition, options, context, struct, value);
|
||||||
|
|
||||||
// Patch the associated length field.
|
// Patch the associated length field.
|
||||||
const lengthField = this.definition.options.lengthField;
|
const lengthField = this.definition.options.lengthField;
|
||||||
const originalValue = getRuntimeValue(object, lengthField);
|
|
||||||
this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthFieldRuntimeValue(originalValue, this);
|
const originalValue = struct.get(lengthField);
|
||||||
setRuntimeValue(object, lengthField, this.lengthFieldValue);
|
this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthStructFieldValue(
|
||||||
|
originalValue,
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
struct.set(lengthField, this.lengthFieldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSize() {
|
public getSize() {
|
||||||
|
@ -119,3 +87,41 @@ class VariableLengthArrayBufferLikeFieldRuntimeValue<
|
||||||
this.length = undefined;
|
this.length = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class VariableLengthArrayBufferLikeLengthStructFieldValue
|
||||||
|
extends StructFieldValue {
|
||||||
|
protected originalField: StructFieldValue;
|
||||||
|
|
||||||
|
protected arrayBufferField: VariableLengthArrayBufferLikeStructFieldValue;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
originalField: StructFieldValue,
|
||||||
|
arrayBufferField: VariableLengthArrayBufferLikeStructFieldValue,
|
||||||
|
) {
|
||||||
|
super(originalField.definition, originalField.options, originalField.context, originalField.struct, 0);
|
||||||
|
this.originalField = originalField;
|
||||||
|
this.arrayBufferField = arrayBufferField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSize() {
|
||||||
|
return this.originalField.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
get() {
|
||||||
|
let value: string | number = this.arrayBufferField.getSize();
|
||||||
|
|
||||||
|
const originalValue = this.originalField.get();
|
||||||
|
if (typeof originalValue === 'string') {
|
||||||
|
value = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set() { }
|
||||||
|
|
||||||
|
serialize(dataView: DataView, offset: number, context: StructSerializationContext) {
|
||||||
|
this.originalField.set(this.get());
|
||||||
|
this.originalField.serialize(dataView, offset, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
7
packages/struct/src/utils.spec.ts
Normal file
7
packages/struct/src/utils.spec.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { placeholder } from './utils';
|
||||||
|
|
||||||
|
describe('placeholder', () => {
|
||||||
|
it('should return `undefined`', () => {
|
||||||
|
expect(placeholder()).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
|
@ -39,6 +39,9 @@ export type OmitNever<T> = Pick<T, { [K in keyof T]: [T[K]] extends [never] ? ne
|
||||||
export type KeysOfType<T, TValue> =
|
export type KeysOfType<T, TValue> =
|
||||||
{ [TKey in keyof T]: T[TKey] extends TValue ? TKey : never }[keyof T];
|
{ [TKey in keyof T]: T[TKey] extends TValue ? TKey : never }[keyof T];
|
||||||
|
|
||||||
|
export type ValueOrPromise<T> = T | Promise<T>;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a (fake) value of the given type.
|
* Returns a (fake) value of the given type.
|
||||||
*/
|
*/
|
|
@ -13,6 +13,7 @@
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationDir": "dts",
|
"declarationDir": "dts",
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
|
"stripInternal": true,
|
||||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
// "noEmit": true, /* Do not emit outputs. */
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
"importHelpers": true, // /* Import emit helpers from 'tslib'. */
|
"importHelpers": true, // /* Import emit helpers from 'tslib'. */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue