Types: Separate shape from data

This commit is contained in:
Daniel Lehenbauer 2020-09-15 21:48:03 +00:00
Родитель 446c8c6956
Коммит a538266223
12 изменённых файлов: 267 добавлений и 83 удалений

Просмотреть файл

@ -1,3 +1,8 @@
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { IProducer, IReader, IConsumer } from "@tiny-calc/types"; import { IProducer, IReader, IConsumer } from "@tiny-calc/types";
import { ConsumerSet, addConsumer, removeConsumer, forEachConsumer } from "../consumerset"; import { ConsumerSet, addConsumer, removeConsumer, forEachConsumer } from "../consumerset";
@ -6,12 +11,12 @@ export abstract class Producer<TMap> implements IProducer<TMap>, IReader<TMap> {
//#region IProducer //#region IProducer
open(consumer: IConsumer<TMap>): IReader<TMap> { public open(consumer: IConsumer<TMap>): IReader<TMap> {
this.consumers = addConsumer(this.consumers, consumer); this.consumers = addConsumer(this.consumers, consumer);
return this; return this;
} }
close(consumer: IConsumer<TMap>): void { public close(consumer: IConsumer<TMap>): void {
this.consumers = removeConsumer(this.consumers, consumer); this.consumers = removeConsumer(this.consumers, consumer);
} }
@ -19,15 +24,15 @@ export abstract class Producer<TMap> implements IProducer<TMap>, IReader<TMap> {
//#region IReader //#region IReader
public abstract get<K extends keyof TMap>(property: K): TMap[K]; public abstract get<K extends keyof TMap>(key: K): TMap[K];
public get producer() { return this; } public get producer():IProducer<TMap> { return this; }
//#endregion IReader //#endregion IReader
protected invalidateValue<K extends keyof TMap>(property: K): void { protected invalidateValue<K extends keyof TMap>(key: K): void {
forEachConsumer(this.consumers, (consumer) => { forEachConsumer(this.consumers, (consumer) => {
consumer.valueChanged(property, this); consumer.keyChanged(key, this);
}); });
} }
} }

Просмотреть файл

@ -6,7 +6,14 @@
import "mocha"; import "mocha";
import { strict as assert } from "assert"; import { strict as assert } from "assert";
import { Random } from "best-random"; import { Random } from "best-random";
import { IMatrixConsumer, IMatrixReader, IMatrixProducer, IVectorWriter, IMatrixWriter } from "@tiny-calc/types"; import {
IMatrixConsumer,
IMatrixReader,
IMatrixProducer,
IMatrixWriter,
IVectorShapeWriter,
IVectorWriter,
} from "@tiny-calc/types";
import { DenseVector, RowMajorMatrix } from "../src"; import { DenseVector, RowMajorMatrix } from "../src";
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -95,8 +102,8 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
public constructor ( public constructor (
producer: IMatrixProducer<T>, producer: IMatrixProducer<T>,
private readonly rowWriter: IVectorWriter<TRow>, private readonly rowWriter: IVectorWriter<TRow> & IVectorShapeWriter,
private readonly colWriter: IVectorWriter<TCol>, private readonly colWriter: IVectorWriter<TCol> & IVectorShapeWriter,
private readonly cellWriter: IMatrixWriter<T> private readonly cellWriter: IMatrixWriter<T>
) { ) {
this.reader = producer.openMatrix(this); this.reader = producer.openMatrix(this);
@ -109,7 +116,7 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
public rowsChanged(rowStart: number, removedCount: number, insertedCount: number): void { public rowsChanged(rowStart: number, removedCount: number, insertedCount: number): void {
const rowEnd = rowStart + removedCount; const rowEnd = rowStart + removedCount;
assert(0 <= rowStart && rowStart <= rowEnd && rowEnd <= this.consumed.rowCount); assert(0 <= rowStart && rowStart <= rowEnd && rowEnd <= this.consumed.rowCount);
if (removedCount > 0) { this.consumed.removeRows(rowStart, removedCount); } if (removedCount > 0) { this.consumed.removeRows(rowStart, removedCount); }
@ -154,7 +161,7 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
const actual = this.reader.getCell(row, col); const actual = this.reader.getCell(row, col);
const expected = this.expected.getCell(row, col); const expected = this.expected.getCell(row, col);
assert.equal(actual, expected); assert.equal(actual, expected);
assert.equal(this.consumed.getCell(row, col), expected); assert.equal(this.consumed.getCell(row, col), expected);
@ -173,7 +180,7 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
this.expected.setCell(row, col, value); this.expected.setCell(row, col, value);
this.cellWriter.setCell(row, col, value); this.cellWriter.setCell(row, col, value);
assert.equal(this.getCell(row, col), value, assert.equal(this.getCell(row, col), value,
`Writer.setCell(${row},${col}) must update matrix value.`); `Writer.setCell(${row},${col}) must update matrix value.`);
} }
@ -239,7 +246,7 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
} }
public extract(): ReadonlyArray<ReadonlyArray<T>> { public extract(): ReadonlyArray<ReadonlyArray<T>> {
const m: T[][] = []; const m: T[][] = [];
for (let r = 0; r < this.rowCount; r++) { for (let r = 0; r < this.rowCount; r++) {
const row: T[] = []; const row: T[] = [];
m.push(row); m.push(row);
@ -265,7 +272,7 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
public expectSize(rowCount: number, colCount: number): void { public expectSize(rowCount: number, colCount: number): void {
this.check(); this.check();
assert.equal(this.rowCount, rowCount); assert.equal(this.rowCount, rowCount);
assert.equal(this.colCount, colCount); assert.equal(this.colCount, colCount);
} }

Просмотреть файл

@ -6,7 +6,7 @@
import "mocha"; import "mocha";
import { strict as assert } from "assert"; import { strict as assert } from "assert";
import { Random } from "best-random"; import { Random } from "best-random";
import { IVectorConsumer, IVectorReader, IVectorProducer, IVectorWriter } from "@tiny-calc/types"; import { IVectorConsumer, IVectorReader, IVectorProducer, IVectorWriter, IVectorShapeWriter } from "@tiny-calc/types";
import { DenseVector } from "../src"; import { DenseVector } from "../src";
export class TestVector<T> implements IVectorConsumer<T> { export class TestVector<T> implements IVectorConsumer<T> {
@ -14,7 +14,7 @@ export class TestVector<T> implements IVectorConsumer<T> {
private readonly consumed: T[] = []; private readonly consumed: T[] = [];
private readonly expected: T[] = []; private readonly expected: T[] = [];
public constructor(producer: IVectorProducer<T>, private readonly writer: IVectorWriter<T>) { public constructor(producer: IVectorProducer<T>, private readonly writer: IVectorWriter<T> & IVectorShapeWriter) {
this.actual = producer.openVector(this); this.actual = producer.openVector(this);
for (let i = 0; i < this.actual.length; i++) { for (let i = 0; i < this.actual.length; i++) {
@ -41,11 +41,11 @@ export class TestVector<T> implements IVectorConsumer<T> {
public itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void { public itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void {
const inserted = []; const inserted = [];
for (let i = start; insertedCount > 0; i++, insertedCount--) { for (let i = start; insertedCount > 0; i++, insertedCount--) {
inserted.push(this.actual.getItem(i)); inserted.push(this.actual.getItem(i));
} }
this.consumed.splice(start, removedCount, ...inserted); this.consumed.splice(start, removedCount, ...inserted);
} }

Просмотреть файл

@ -3,7 +3,7 @@
* Licensed under the MIT License. * Licensed under the MIT License.
*/ */
import { import {
IConsumer, IConsumer,
IProducer, IProducer,
IVectorConsumer, IVectorConsumer,
@ -25,33 +25,33 @@ export class LoggingConsumer<T> implements IConsumer<T>, IVectorConsumer<T>, IMa
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(producer as any)[idSym] = value; (producer as any)[idSym] = value;
} }
private getProducerId(producer: AnyProducer): string { private getProducerId(producer: AnyProducer): string {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
return (producer as any)[idSym] ?? "Error: Missing call to LoggingConsumer.setProducerId(..)"; return (producer as any)[idSym] ?? "Error: Missing call to LoggingConsumer.setProducerId(..)";
} }
// #region IConsumer<T> // #region IConsumer<T>
public valueChanged<U extends T, K extends keyof U>(property: K, producer: IProducer<U>): void { public keyChanged<U extends T, K extends keyof U>(property: K, producer: IProducer<U>): void {
this.log.push({ property, producer: this.getProducerId(producer) }); this.log.push({ property, producer: this.getProducerId(producer) });
} }
// #endregion IConsumer<T> // #endregion IConsumer<T>
// #region IVectorConsumer<T> // #region IVectorConsumer<T>
public itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void { public itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void {
this.log.push({ start, removedCount, insertedCount, producer: this.getProducerId(producer) }); this.log.push({ start, removedCount, insertedCount, producer: this.getProducerId(producer) });
} }
// #endregion IVectorConsumer<T> // #endregion IVectorConsumer<T>
// #region IMatrixConsumer<T> // #region IMatrixConsumer<T>
public rowsChanged(rowStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void { public rowsChanged(rowStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void {
this.log.push({ rowStart, removedCount, insertedCount, producer: this.getProducerId(producer) }); this.log.push({ rowStart, removedCount, insertedCount, producer: this.getProducerId(producer) });
} }
public colsChanged(colStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void { public colsChanged(colStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void {
this.log.push({ colStart, removedCount, insertedCount, producer: this.getProducerId(producer) }); this.log.push({ colStart, removedCount, insertedCount, producer: this.getProducerId(producer) });
} }
public cellsChanged(rowStart: number, colStart: number, rowCount: number, colCount: number, producer: IMatrixProducer<T>): void { public cellsChanged(rowStart: number, colStart: number, rowCount: number, colCount: number, producer: IMatrixProducer<T>): void {
this.log.push({ rowStart, colStart, rowCount, colCount, producer: this.getProducerId(producer) }); this.log.push({ rowStart, colStart, rowCount, colCount, producer: this.getProducerId(producer) });
} }

Просмотреть файл

@ -9,7 +9,7 @@ class NullConsumer<T> implements IConsumer<T>, IVectorConsumer<T>, IMatrixConsum
public rowsChanged(): void { } public rowsChanged(): void { }
public colsChanged(): void { } public colsChanged(): void { }
public cellsChanged(): void { } public cellsChanged(): void { }
public valueChanged(): void { } public keyChanged(): void { }
public itemsChanged(): void { } public itemsChanged(): void { }
} }

Просмотреть файл

@ -9,5 +9,5 @@ const listFormula = new ListFormula(context, "IF(Time.Now = Time.Now, Math.Max(T
}); });
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
listFormula.valueChanged(); listFormula.keyChanged();
} }

Просмотреть файл

@ -30,7 +30,7 @@ export class Context implements Producer {
open() { open() {
const producer: Producer = this; const producer: Producer = this;
return { return {
get: (key: string) => this.fields[key], get: (key: string) => this.fields[key],
producer producer
}; };
@ -47,7 +47,7 @@ export class TimeProducer implements Producer {
open() { open() {
const time = Date.now(); const time = Date.now();
const producer: Producer = this; const producer: Producer = this;
return { return {
get: () => time, get: () => time,
producer producer
}; };
@ -114,7 +114,7 @@ export class ListFormula implements FormulaHost {
} }
} }
valueChanged() { keyChanged() {
console.time("recalc"); console.time("recalc");
const scope = createCalcValue(this.scope); const scope = createCalcValue(this.scope);
const values = []; const values = [];

Просмотреть файл

@ -3,9 +3,36 @@
* Licensed under the MIT License. * Licensed under the MIT License.
*/ */
export { IConsumer, IProducer, IReader, IWriter } from './record'; export {
export { IVectorConsumer, IVectorProducer, IVectorReader, IVectorWriter } from './vector'; IConsumer,
export { IMatrixConsumer, IMatrixProducer, IMatrixReader, IMatrixWriter } from './matrix'; IProducer,
IReader,
IShapeConsumer,
IShapeProducer,
IShapeReader,
IShapeWriter,
IWriter
} from './record';
export {
IVectorConsumer,
IVectorProducer,
IVectorReader,
IVectorShapeConsumer,
IVectorShapeProducer,
IVectorShapeReader,
IVectorShapeWriter,
IVectorWriter
} from './vector';
export {
IMatrixConsumer,
IMatrixProducer,
IMatrixReader,
IMatrixShapeConsumer,
IMatrixShapeProducer,
IMatrixShapeReader,
IMatrixShapeWriter,
IMatrixWriter
} from './matrix';
export { export {
ITreeShapeConsumer, ITreeShapeConsumer,
ITreeShapeProducer, ITreeShapeProducer,

Просмотреть файл

@ -4,27 +4,80 @@
*/ */
/** An observable 2D numerically indexed collection. */ /** An observable 2D numerically indexed collection. */
export interface IMatrixProducer<T> { export interface IMatrixShapeProducer {
/** /**
* Acquire a reader for this matrix's values and implicitly subscribe the consumer * Acquire a reader for this matrix's values and implicitly subscribe the consumer
* to value change notifications. * to value change notifications.
* *
* @param consumer - The consumer to be notified of matrix changes.
*/
openMatrix(consumer: IMatrixShapeConsumer): IMatrixShapeReader;
/**
* Unsubscribe the consumer from this matrix's change notifications.
*
* @param consumer - The consumer to unregister from the matrix.
*/
closeMatrix(consumer: IMatrixShapeConsumer): void;
}
/** A consumer of change notifications for a matrix. */
export interface IMatrixShapeConsumer {
/** Notification that rows have been inserted, removed, and/or replaced in the given matrix. */
rowsChanged(rowStart: number, removedCount: number, insertedCount: number, producer: IMatrixShapeProducer): void;
/** Notification that cols have been inserted, removed, and/or replaced in the given matrix. */
colsChanged(colStart: number, removedCount: number, insertedCount: number, producer: IMatrixShapeProducer): void;
}
/** Capability to read cells in a matrix. */
export interface IMatrixShapeReader {
readonly rowCount: number;
readonly colCount: number;
/**
* A reference to the underlying producer that provides values for this reader,
* or undefined if the producer is immutable.
*/
readonly matrixProducer?: IMatrixShapeProducer;
}
/** Capability to write cells in a matrix. */
export interface IMatrixShapeWriter {
spliceRows(rowStart: number, deleteCount: number, insertCount: number): void;
spliceCols(colStart: number, deleteCount: number, insertCount: number): void;
}
/** An observable 2D numerically indexed collection. */
export interface IMatrixProducer<T> extends IMatrixShapeProducer {
/**
* Acquire a reader for this matrix's values and implicitly subscribe the consumer
* to value change notifications.
*
* @param consumer - The consumer to be notified of matrix changes. * @param consumer - The consumer to be notified of matrix changes.
*/ */
openMatrix(consumer: IMatrixConsumer<T>): IMatrixReader<T>; openMatrix(consumer: IMatrixConsumer<T>): IMatrixReader<T>;
/** /**
* Unsubscribe the consumer from this matrix's change notifications. * Unsubscribe the consumer from this matrix's change notifications.
* *
* @param consumer - The consumer to unregister from the matrix. * @param consumer - The consumer to unregister from the matrix.
*/ */
closeMatrix(consumer: IMatrixConsumer<T>): void; closeMatrix(consumer: IMatrixConsumer<T>): void;
} }
/** A consumer of change notifications for a matrix. */
export interface IMatrixConsumer<T> extends IMatrixShapeConsumer {
/**
* Notification that a range of cells have been replaced in the given matrix. If the source
* matrix has the new cell values already in an array, it may optionally pass these to consumers
* as an optimization.
*/
cellsChanged(rowStart: number, colStart: number, rowCount: number, colCount: number, producer: IMatrixProducer<T>): void;
}
/** Capability to read cells in a matrix. */ /** Capability to read cells in a matrix. */
export interface IMatrixReader<T> { export interface IMatrixReader<T> extends IMatrixShapeReader {
readonly rowCount: number;
readonly colCount: number;
getCell(row: number, col: number): T; getCell(row: number, col: number): T;
/** /**
@ -39,22 +92,6 @@ export interface IMatrixWriter<T> {
setCell(row: number, col: number, value: T): void; setCell(row: number, col: number, value: T): void;
} }
/** A consumer of change notifications for a matrix. */
export interface IMatrixConsumer<T> {
/** Notification that rows have been inserted, removed, and/or replaced in the given matrix. */
rowsChanged(rowStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void;
/** Notification that cols have been inserted, removed, and/or replaced in the given matrix. */
colsChanged(colStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void;
/**
* Notification that a range of cells have been replaced in the given matrix. If the source
* matrix has the new cell values already in an array, it may optionally pass these to consumers
* as an optimization.
*/
cellsChanged(rowStart: number, colStart: number, rowCount: number, colCount: number, producer: IMatrixProducer<T>): void;
}
export interface MatrixIteratorSpec { export interface MatrixIteratorSpec {
/** /**
* Iterates over empty cells. * Iterates over empty cells.

Просмотреть файл

@ -3,45 +3,85 @@
* Licensed under the MIT License. * Licensed under the MIT License.
*/ */
/** An observable key/value collection, such as a Record or Map. */
export interface IShapeProducer<TKey> {
/**
* Acquire a reader for this producer's values and implicitly subscribe the consumer
* to value change notifications.
*
* @param consumer - The consumer to be notified of value changes.
*/
open(consumer: IShapeConsumer<TKey>): IShapeReader<TKey>;
/**
* Unsubscribe the given 'consumer' from this producer's change notifications.
*
* @param consumer - The consumer to unregister from the producer.
*/
close(consumer: IShapeConsumer<TKey>): void;
}
/** A consumer of change notifications for a key/value collection, such as a Record or Map. */
export interface IShapeConsumer<TKey> {
keyChanged(key: TKey, producer: IShapeProducer<TKey>): void;
}
/** Capability to read values of a key/value collection. */
export interface IShapeReader<TKey> {
keys(): IterableIterator<TKey>;
has(key: TKey): boolean;
readonly size: number;
/**
* A reference to the underlying producer that provides the shape for this reader,
* or undefined if the producer is immutable.
*/
readonly producer?: IShapeProducer<TKey>;
}
export interface IShapeWriter<TKey> {
delete(key: TKey): void;
clear(): void;
}
/** An observable key/value collection, such as a Record or Map. */ /** An observable key/value collection, such as a Record or Map. */
export interface IProducer<T> { export interface IProducer<T> {
/** /**
* Acquire a reader for this producer's values and implicitly subscribe the consumer * Acquire a reader for this producer's values and implicitly subscribe the consumer
* to value change notifications. * to value change notifications.
* *
* @param consumer - The consumer to be notified of value changes. * @param consumer - The consumer to be notified of value changes.
*/ */
open(consumer: IConsumer<T>): IReader<T>; open(consumer: IConsumer<T>): IReader<T> | (IReader<T> & IShapeReader<T>);
/** /**
* Unsubscribe the given 'consumer' from this producer's change notifications. * Unsubscribe the given 'consumer' from this producer's change notifications.
* *
* @param consumer - The consumer to unregister from the producer. * @param consumer - The consumer to unregister from the producer.
*/ */
close(consumer: IConsumer<T>): void; close(consumer: IConsumer<T>): void;
} }
/** A consumer of change notifications for a key/value collection, such as a Record or Map. */
export interface IConsumer<T> {
keyChanged<K extends keyof T>(key: K, producer: IProducer<T>): void;
}
/** Capability to read values of a key/value collection. */ /** Capability to read values of a key/value collection. */
export interface IReader<T> { export interface IReader<T> {
/** Return the value associated with `property`. */ /** Return the value associated with `key`. */
get<K extends keyof T>(property: K): T[K]; get<K extends keyof T>(key: K): T[K];
/** /**
* A reference to the underlying producer that provides values for this reader, * A reference to the underlying producer that provides values for this reader,
* or undefined if the producer is immutable. * or undefined if the producer is immutable.
*/ */
readonly producer?: IProducer<T>; readonly producer?: IProducer<T> | (IProducer<T> & IShapeProducer<keyof T>);
} }
/** Capability to set values of a key/value collection. */ /** Capability to set values of a key/value collection. */
export interface IWriter<T> { export interface IWriter<T> {
set<K extends keyof T>(property: K, value: T[K]): void; set<K extends keyof T>(key: K, value: T[K]): void;
}
/** A consumer of change notifications for a key/value collection, such as a Record or Map. */
export interface IConsumer<T> {
/**
* Invoked whenever the data this object is bound to is changed.
*/
valueChanged<U extends T, K extends keyof U>(property: K, producer: IProducer<U>): void;
} }

Просмотреть файл

@ -4,26 +4,64 @@
*/ */
/** An observable 1D numerically indexed collection, such as an Array. */ /** An observable 1D numerically indexed collection, such as an Array. */
export interface IVectorProducer<T> { export interface IVectorShapeProducer {
/** /**
* Acquire a reader for this vector's values and implicitly subscribe the consumer * Acquire a reader for this vector's values and implicitly subscribe the consumer
* to value change notifications. * to value change notifications.
* *
* @param consumer - The consumer to be notified of vector changes.
*/
openVector(consumer: IVectorShapeConsumer): IVectorShapeReader;
/**
* Unsubscribe the given 'consumer' from this vector's change notifications.
*
* @param consumer - The consumer to unregister from the vector.
*/
closeVector(consumer: IVectorShapeConsumer): void;
}
/** Capability to read items in a vector. */
export interface IVectorShapeReader {
readonly length: number;
/**
* A reference to the underlying producer that provides values for this reader,
* or undefined if the producer is immutable.
*/
readonly vectorProducer?: IVectorShapeProducer;
}
/** Capability to insert, replace, and remove items in a vector. */
export interface IVectorShapeWriter {
splice(start: number, deleteCount: number, insertCount: number): void;
}
export interface IVectorShapeConsumer {
/** Notification that a range of items have been inserted, removed, and/or replaced in the given vector. */
itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorShapeProducer): void;
}
/** An observable 1D numerically indexed collection, such as an Array. */
export interface IVectorProducer<T> extends IVectorShapeProducer {
/**
* Acquire a reader for this vector's values and implicitly subscribe the consumer
* to value change notifications.
*
* @param consumer - The consumer to be notified of vector changes. * @param consumer - The consumer to be notified of vector changes.
*/ */
openVector(consumer: IVectorConsumer<T>): IVectorReader<T>; openVector(consumer: IVectorConsumer<T>): IVectorReader<T>;
/** /**
* Unsubscribe the given 'consumer' from this vector's change notifications. * Unsubscribe the given 'consumer' from this vector's change notifications.
* *
* @param consumer - The consumer to unregister from the vector. * @param consumer - The consumer to unregister from the vector.
*/ */
closeVector(consumer: IVectorConsumer<T>): void; closeVector(consumer: IVectorConsumer<T>): void;
} }
/** Capability to read items in a vector. */ /** Capability to read items in a vector. */
export interface IVectorReader<T> { export interface IVectorReader<T> extends IVectorShapeReader {
readonly length: number;
getItem(index: number): T; getItem(index: number): T;
/** /**
@ -35,12 +73,11 @@ export interface IVectorReader<T> {
/** Capability to insert, replace, and remove items in a vector. */ /** Capability to insert, replace, and remove items in a vector. */
export interface IVectorWriter<T> { export interface IVectorWriter<T> {
splice(start: number, deleteCount: number, insertCount: number): void;
setItem(index: number, item: T): void; setItem(index: number, item: T): void;
} }
/** A consumer of change notifications for a vector. */ /** A consumer of change notifications for a vector. */
export interface IVectorConsumer<T> { export interface IVectorConsumer<T> extends IVectorShapeConsumer {
/** Notification that a range of items have been inserted, removed, and/or replaced in the given vector. */ /** Notification that a range of items have been inserted, removed, and/or replaced in the given vector. */
itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void; itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void;
} }

Просмотреть файл

@ -5,32 +5,63 @@
import "mocha"; import "mocha";
import { strict as assert } from "assert"; import { strict as assert } from "assert";
import { IReader, IWriter } from "../src"; import { IReader, IWriter, IShapeReader, IShapeWriter } from "../src";
describe("Record", () => { describe("Record", () => {
describe("must be compatible with ES6 map", () => { describe("must be compatible with ES6 map", () => {
let map: Map<string, number>; let map: Map<string, number>;
let shapeReader: IShapeReader<string>;
let shapeWriter: IShapeWriter<string>;
let reader: IReader<Record<string, number | undefined>>; let reader: IReader<Record<string, number | undefined>>;
let writer: IWriter<Record<string, number>>; let writer: IWriter<Record<string, number>>;
beforeEach(() => { beforeEach(() => {
map = new Map<string, number>(); map = new Map<string, number>();
shapeReader = map;
shapeWriter = map;
reader = map; reader = map;
writer = map; writer = map;
}); });
it("ES6 map must satisfy IReader", () => { it("ES6 map must satisfy IReader", () => {
map.set("0", 0); map.set("0", 0);
assert.equal(reader.get("0"), 0); assert.equal(reader.get("0"), 0);
}); });
it("ES6 map must satisfy IWriter", () => { it("ES6 map must satisfy IWriter", () => {
writer.set("0", 0); writer.set("0", 0);
assert.equal(map.get("0"), 0); assert.equal(map.get("0"), 0);
}); });
it("ES6 map must satisfy IShapeReader", () => {
assert.deepEqual([...shapeReader.keys()], []);
const keys = ["a", "b", "c"];
for (const key of keys) {
assert.equal(shapeReader.has(key), false);
map.set(key, key.charCodeAt(0));
assert.equal(shapeReader.has(key), true);
}
assert.deepEqual([...shapeReader.keys()], keys);
});
it("ES6 map must satisfy IShapeWriter", () => {
const keys = ["a", "b", "c"];
for (const key of keys) {
assert.equal(shapeReader.has(key), false);
assert.equal(shapeWriter.delete(key), false);
map.set(key, key.charCodeAt(0));
assert.equal(shapeReader.has(key), true);
assert.equal(shapeWriter.delete(key), true);
assert.equal(shapeReader.has(key), false);
}
});
it("Must support key constraints", () => { it("Must support key constraints", () => {
const map = new Map([ const map = new Map<"0" | "1", number>([
["0", 0], ["0", 0],
["1", 1], ["1", 1],
]); ]);
@ -39,7 +70,7 @@ describe("Record", () => {
"0": number | undefined, "0": number | undefined,
"1": number | undefined, "1": number | undefined,
}> = map; }> = map;
assert.equal(reader.get("0"), 0); assert.equal(reader.get("0"), 0);
assert.equal(reader.get("1"), 1); assert.equal(reader.get("1"), 1);