@drop-in/new
Version:
A SvelteKit Svelte 5 PocketBase Starter Kit with a CLI
183 lines (153 loc) • 4.45 kB
text/typescript
import {describe, expect, test} from 'vitest';
import {mergeIterables, wrapIterable} from './iterables.js';
import fc from 'fast-check';
function* range(start = 0, end = Infinity, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
test('wrapper should be iterable', () => {
const result = [];
for (const item of wrapIterable(range(0, 3))) {
result.push(item);
}
expect(result).toEqual([0, 1, 2]);
});
test('wrapper should wrap be iterable', () => {
const result = [];
for (const item of wrapIterable([0, 1, 2])) {
result.push(item);
}
expect(result).toEqual([0, 1, 2]);
});
test('wrapper should wrap be iterable 2', () => {
const result = [];
for (const item of wrapIterable('abc💩')) {
result.push(item);
}
expect(result).toEqual(['a', 'b', 'c', '💩']);
});
test('filter', () => {
const result = wrapIterable(range(0, 10)).filter(x => x % 2 === 0);
expect([...result]).toEqual([0, 2, 4, 6, 8]);
});
test('filter index', () => {
const result = wrapIterable(range(0, 10)).filter((_, i) => i % 2 === 0);
expect([...result]).toEqual([0, 2, 4, 6, 8]);
});
test('map', () => {
const result = wrapIterable(range(0, 10)).map(x => x * 2);
expect([...result]).toEqual([0, 2, 4, 6, 8, 10, 12, 14, 16, 18]);
});
test('map index', () => {
const result = wrapIterable('abc').map((c, i) => [c, i * 2]);
expect([...result]).toEqual([
['a', 0],
['b', 2],
['c', 4],
]);
});
test('chaining filter and map', () => {
const result = wrapIterable(range(0, 10))
.filter(x => x % 2 === 0)
.map(x => x * 2);
expect([...result]).toEqual([0, 4, 8, 12, 16]);
});
describe('mergeIterables', () => {
test('no dupes, interleaved items', () => {
const iterables = [
[1, 3, 5],
[2, 4, 6],
];
const result = mergeIterables(iterables, (l, r) => l - r);
expect([...result]).toEqual([1, 2, 3, 4, 5, 6]);
});
test('dupes', () => {
const iterables = [
[1, 2, 3],
[1, 2, 3],
];
let result = mergeIterables(iterables, (l, r) => l - r);
expect([...result]).toEqual([1, 1, 2, 2, 3, 3]);
result = mergeIterables(iterables, (l, r) => l - r, true);
expect([...result]).toEqual([1, 2, 3]);
});
test('fuzz', () => {
fc.assert(
fc.property(
fc.array(fc.array(fc.integer())),
fc.boolean(),
(arrays, noDupes) => {
const sorted = arrays.map(a => a.slice().sort((l, r) => l - r));
const result = mergeIterables(sorted, (l, r) => l - r, noDupes);
const expected = sorted.flat().sort((l, r) => l - r);
expect([...result]).toEqual(
noDupes ? [...new Set(expected)] : expected,
);
},
),
);
});
test('return', () => {
let it1 = new TestIterable([1, 2, 3]);
let it2 = new TestIterable([1, 2, 3]);
let result = mergeIterables([it1, it2], (l, r) => (l ?? 0) - (r ?? 0));
expect(it1.returned).toBe(false);
expect(it2.returned).toBe(false);
for (const _ of result) {
// noop
}
expect(it1.returned).toBe(true);
expect(it2.returned).toBe(true);
it1 = new TestIterable([1, 2]);
it2 = new TestIterable([1, 2, 3, 4]);
result = mergeIterables([it1, it2], (l, r) => (l ?? 0) - (r ?? 0));
for (const _ of result) {
// noop
}
expect(it1.returned).toBe(true);
expect(it2.returned).toBe(true);
});
test('throw', () => {
const it1 = new TestIterable(new ThrowingIterable());
const it2 = new TestIterable(new ThrowingIterable());
const result = mergeIterables([it1, it2], (l, r) => (l ?? 0) - (r ?? 0));
expect(it1.returned).toBe(false);
expect(it2.returned).toBe(false);
expect(() => {
for (const _ of result) {
// noop
}
}).toThrow();
expect(it1.returned).toBe(true);
expect(it2.returned).toBe(true);
});
});
class ThrowingIterable {
[Symbol.iterator]() {
return {
next: () => {
throw new Error();
},
};
}
}
class TestIterable {
readonly #data;
returned: boolean = false;
thrown: boolean = false;
constructor(data: Iterable<number>) {
this.#data = data;
}
[Symbol.iterator]() {
const iterator = this.#data[Symbol.iterator]();
return {
next: () => iterator.next(),
return: () => this.return(),
};
}
return() {
this.returned = true;
return {value: undefined, done: true};
}
}