Skip to content

Commit

Permalink
Fixup, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rekmarks committed May 6, 2022
1 parent e38e559 commit cadc8a2
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 70 deletions.
176 changes: 132 additions & 44 deletions src/collections.test.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,134 @@
import { FrozenMap, FrozenSet } from './collections';

// describe('FrozenMap', () => {
// it.todo('does the thing');
// })

// describe('FrozenSet', () => {
// it.todo('does the thing');
// })

console.log(Object.isFrozen(FrozenSet));
console.log(Object.isFrozen(FrozenSet.prototype));

const foo = new FrozenSet(['a', 'b', 'c']);
console.log(foo);
console.log(foo.toString());
console.log(new FrozenSet().toString());
console.log(new FrozenSet(['a']).toString());
console.log(new FrozenSet(['a', 'b']).toString());
console.log(new Set());
console.log(new Set(['a', 'b', 'c']));

console.log();
console.log();

const bar = new FrozenMap([
['a', 1],
['b', 2],
['c', 3],
]);
console.log(bar);
console.log(bar.toString());
console.log(new FrozenMap().toString());
console.log(new FrozenMap([['a', 1]]).toString());
console.log(
new FrozenMap([
['a', 1],
['b', 2],
]).entries(),
);
console.log(new Map());
console.log(new Map([
['a', 1],
['b', 2],
['c', 3],
]));
describe('FrozenMap', () => {
describe('immutability', () => {
it('is frozen and cannot be mutated', () => {
const frozenMap: any = new FrozenMap();
expect(frozenMap.set).toBeUndefined();
expect(frozenMap.clear).toBeUndefined();

const expectedProperties = new Set([
'entries',
'forEach',
'get',
'has',
'keys',
'values',
]);
const properties = Object.getOwnPropertyNames(frozenMap);
expect(properties).toHaveLength(expectedProperties.size);
properties.forEach((propertyName) =>
expect(expectedProperties.has(propertyName)).toBe(true),
);

expect(frozenMap.valueOf() instanceof Map).toBe(false);

expect(Object.isFrozen(frozenMap)).toBe(true);
expect(Object.isFrozen(frozenMap.prototype)).toBe(true);

expect(Object.isFrozen(FrozenMap)).toBe(true);
expect(Object.isFrozen(FrozenMap.prototype)).toBe(true);
});
});

describe('iteration', () => {
it('can be used as an iterator', () => {
const input = [
['a', 1],
['b', 2],
['c', 3],
] as const;
const map = new Map([...input]);
const frozenMap = new FrozenMap([...input]);

for (const [key, value] of frozenMap) {
expect(map.get(key)).toStrictEqual(value);
}
});
});

describe('toString', () => {
it('stringifies as expected', () => {
expect(new FrozenMap().toString()).toMatchInlineSnapshot(
`"FrozenMap(0) {}"`,
);

expect(new FrozenMap([['a', 1]]).toString()).toMatchInlineSnapshot(
`"FrozenMap(1) { a => 1 }"`,
);

expect(
new FrozenMap([
['a', 1],
['b', 2],
['c', 3],
]).toString(),
).toMatchInlineSnapshot(`"FrozenMap(3) { a => 1, b => 2, c => 3 }"`);
});
});
// describe('forEach', () => {})
// describe('get', () => {})
// describe('has', () => {})
// describe('keys', () => {})
// describe('values', () => {})
// describe('size', () => {})
});

describe('FrozenSet', () => {
describe('immutability', () => {
it('is frozen and cannot be mutated', () => {
const frozenSet: any = new FrozenSet();
expect(frozenSet.set).toBeUndefined();
expect(frozenSet.clear).toBeUndefined();

const expectedProperties = new Set([
'entries',
'forEach',
'has',
'keys',
'values',
]);
const properties = Object.getOwnPropertyNames(frozenSet);
expect(properties).toHaveLength(expectedProperties.size);
properties.forEach((propertyName) =>
expect(expectedProperties.has(propertyName)).toBe(true),
);

expect(frozenSet.valueOf() instanceof Set).toBe(false);

expect(Object.isFrozen(frozenSet)).toBe(true);
expect(Object.isFrozen(frozenSet.prototype)).toBe(true);

expect(Object.isFrozen(FrozenSet)).toBe(true);
expect(Object.isFrozen(FrozenSet.prototype)).toBe(true);
});
});

describe('iteration', () => {
it('can be used as an iterator', () => {
const input = ['a', 'b', 'c'];
const set = new Set([...input]);
const frozenSet = new FrozenSet([...input]);

for (const value of frozenSet) {
expect(set.has(value)).toBe(true);
}
});
});

describe('toString', () => {
it('stringifies as expected', () => {
expect(new FrozenSet().toString()).toMatchInlineSnapshot(
`"FrozenSet(0) {}"`,
);

expect(new FrozenSet(['a']).toString()).toMatchInlineSnapshot(
`"FrozenSet(1) { a }"`,
);

expect(new FrozenSet(['a', 'b', 'c']).toString()).toMatchInlineSnapshot(
`"FrozenSet(3) { a, b, c }"`,
);
});
});
});
54 changes: 28 additions & 26 deletions src/collections.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
/**
* A {@link ReadonlyMap} that cannot be modified after instantiation.
* The implementation uses an inner map hidden via a private field, and the
* immutability guarantee relies on it being impossible to get a reference
* to this map.
*/
class FrozenMap<Key, Value> implements ReadonlyMap<Key, Value> {
#map: Map<Key, Value>;
readonly #map: Map<Key, Value>;

public entries: ReadonlyMap<Key, Value>['entries'];
public readonly entries: ReadonlyMap<Key, Value>['entries'];

public forEach: ReadonlyMap<Key, Value>['forEach'];
public readonly forEach: ReadonlyMap<Key, Value>['forEach'];

public get: ReadonlyMap<Key, Value>['get'];
public readonly get: ReadonlyMap<Key, Value>['get'];

public has: ReadonlyMap<Key, Value>['has'];
public readonly has: ReadonlyMap<Key, Value>['has'];

public keys: ReadonlyMap<Key, Value>['keys'];
public readonly keys: ReadonlyMap<Key, Value>['keys'];

public values: ReadonlyMap<Key, Value>['values'];
public readonly values: ReadonlyMap<Key, Value>['values'];

public get size() {
return this.#map.size;
}

public get [Symbol.iterator]() {
return this.#map[Symbol.iterator];
public [Symbol.iterator]() {
return this.#map[Symbol.iterator]();
}

constructor(entries?: readonly (readonly [Key, Value])[] | null) {
Expand Down Expand Up @@ -50,26 +53,29 @@ class FrozenMap<Key, Value> implements ReadonlyMap<Key, Value> {

/**
* A {@link ReadonlySet} that cannot be modified after instantiation.
* The implementation uses an inner set hidden via a private field, and the
* immutability guarantee relies on it being impossible to get a reference
* to this set.
*/
class FrozenSet<Value> implements ReadonlySet<Value> {
#set: Set<Value>;
readonly #set: Set<Value>;

public entries: ReadonlySet<Value>['entries'];
public readonly entries: ReadonlySet<Value>['entries'];

public forEach: ReadonlySet<Value>['forEach'];
public readonly forEach: ReadonlySet<Value>['forEach'];

public has: ReadonlySet<Value>['has'];
public readonly has: ReadonlySet<Value>['has'];

public keys: ReadonlySet<Value>['keys'];
public readonly keys: ReadonlySet<Value>['keys'];

public values: ReadonlySet<Value>['values'];
public readonly values: ReadonlySet<Value>['values'];

public get size() {
return this.#set.size;
}

public get [Symbol.iterator]() {
return this.#set[Symbol.iterator];
public [Symbol.iterator]() {
return this.#set[Symbol.iterator]();
}

constructor(values?: readonly Value[] | null) {
Expand All @@ -93,14 +99,10 @@ class FrozenSet<Value> implements ReadonlySet<Value> {
}
}

Object.freeze(FrozenSet)
Object.freeze(FrozenSet.prototype)
Object.freeze(FrozenSet);
Object.freeze(FrozenSet.prototype);

Object.freeze(FrozenMap)
Object.freeze(FrozenMap.prototype)
Object.freeze(FrozenMap);
Object.freeze(FrozenMap.prototype);


export {
FrozenMap,
FrozenSet,
}
export { FrozenMap, FrozenSet };

0 comments on commit cadc8a2

Please sign in to comment.