Add `concat` iterable utility

This commit is contained in:
Evan Hahn 2021-05-20 14:51:50 -05:00 committed by Scott Nonnenberg
parent 028b4f162b
commit 7c7f7ee5a0
3 changed files with 98 additions and 5 deletions

View File

@ -4,7 +4,14 @@
import { assert } from 'chai';
import * as sinon from 'sinon';
import { isIterable, size, filter, map, take } from '../../util/iterables';
import {
concat,
filter,
isIterable,
map,
size,
take,
} from '../../util/iterables';
describe('iterable utilities', () => {
describe('isIterable', () => {
@ -82,6 +89,72 @@ describe('iterable utilities', () => {
});
});
describe('concat', () => {
it('returns an empty iterable when passed nothing', () => {
assert.deepEqual([...concat()], []);
});
it('returns an empty iterable when passed empty iterables', () => {
assert.deepEqual([...concat([])], []);
assert.deepEqual([...concat(new Set())], []);
assert.deepEqual([...concat(new Set(), [], new Map())], []);
});
it('concatenates multiple iterables', () => {
const everyNumber = {
*[Symbol.iterator]() {
for (let i = 4; true; i += 1) {
yield i;
}
},
};
const result = concat([1, 2], new Set([3]), [], everyNumber);
const iterator = result[Symbol.iterator]();
assert.deepEqual(iterator.next(), { value: 1, done: false });
assert.deepEqual(iterator.next(), { value: 2, done: false });
assert.deepEqual(iterator.next(), { value: 3, done: false });
assert.deepEqual(iterator.next(), { value: 4, done: false });
assert.deepEqual(iterator.next(), { value: 5, done: false });
assert.deepEqual(iterator.next(), { value: 6, done: false });
assert.deepEqual(iterator.next(), { value: 7, done: false });
});
it("doesn't start the iterable until the last minute", () => {
const oneTwoThree = {
[Symbol.iterator]: sinon.fake(() => {
let n = 0;
return {
next() {
if (n > 3) {
return { done: true };
}
n += 1;
return { value: n, done: false };
},
};
}),
};
const result = concat([1, 2], oneTwoThree);
const iterator = result[Symbol.iterator]();
sinon.assert.notCalled(oneTwoThree[Symbol.iterator]);
iterator.next();
sinon.assert.notCalled(oneTwoThree[Symbol.iterator]);
iterator.next();
sinon.assert.notCalled(oneTwoThree[Symbol.iterator]);
iterator.next();
sinon.assert.calledOnce(oneTwoThree[Symbol.iterator]);
iterator.next();
sinon.assert.calledOnce(oneTwoThree[Symbol.iterator]);
});
});
describe('filter', () => {
it('returns an empty iterable when passed an empty iterable', () => {
const fn = sinon.fake();

View File

@ -48,6 +48,7 @@ import {
LinkPreviewImage,
LinkPreviewMetadata,
} from '../linkPreviews/linkPreviewFetch';
import { concat } from '../util/iterables';
function stringToArrayBuffer(str: string): ArrayBuffer {
if (typeof str !== 'string') {
@ -1706,10 +1707,12 @@ export default class MessageSender {
isNotMe = r => r !== myE164;
}
const blockedIdentifiers = new Set([
...window.storage.getBlockedUuids(),
...window.storage.getBlockedNumbers(),
]);
const blockedIdentifiers = new Set(
concat(
window.storage.getBlockedUuids(),
window.storage.getBlockedNumbers()
)
);
const recipients = groupMembers.filter(
recipient => isNotMe(recipient) && !blockedIdentifiers.has(recipient)

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable max-classes-per-file */
/* eslint-disable no-restricted-syntax */
export function isIterable(value: unknown): value is Iterable<unknown> {
return (
@ -28,6 +29,22 @@ export function size(iterable: Iterable<unknown>): number {
return result;
}
export function concat<T>(
...iterables: ReadonlyArray<Iterable<T>>
): Iterable<T> {
return new ConcatIterable(iterables);
}
class ConcatIterable<T> implements Iterable<T> {
constructor(private readonly iterables: ReadonlyArray<Iterable<T>>) {}
*[Symbol.iterator](): Iterator<T> {
for (const iterable of this.iterables) {
yield* iterable;
}
}
}
export function filter<T, S extends T>(
iterable: Iterable<T>,
predicate: (value: T) => value is S