import { O, identity, filter } from "../../../../source/omnitool" import { Pipe } from "../../../../source/types/sequence/pipe" import { Span } from "../../../../source/types/sequence/span" import { checkResult, checkSequenceResult, errorTag, expectElements } from "../../../utilities" import { Sequence } from "../../../../source/types/types" describe("extracted functions", () => { describe("all", () => { const all = checkResult(O.all) const numbers = [1, 2, 3, 4, 5] test("returns true if all elements match a predicate", () => { expect(all(numbers, (element) => element < 10)).toEqual(true) }) test("returns false if any element does not match a predicate", () => { expect(all(numbers, (element) => element < 5)).toEqual(false) }) test("returns true if no elements are present", () => { expect(all([], (element) => element < 10)).toEqual(true) }) }) describe("as", () => { const as = O.as const elements = [0, 1, null, 3, 4] test("casts the elements of the sequence to another type", () => { const filtered = as(elements.filter((element) => element)) for (const element of filtered) { identity(element + 1) } }) }) describe("concat", () => { const concat = checkSequenceResult(O.concat) const first = [1, 2, 3] const second = [4, 5] test("yields all elements of the first sequence, then the second", () => { expectElements(concat(first, second), [1, 2, 3, 4, 5]) }) test("handles empty", () => { expectElements(concat(first, []), first) expectElements(concat([], second), second) }) }) describe("append", () => { const append = checkSequenceResult(O.append) const numbers = [1, 2, 3] test("yields all elements of the sequence, then the provided elements", () => { expectElements(append(numbers, 4), [1, 2, 3, 4]) expectElements(append(numbers, 4, 5), [1, 2, 3, 4, 5]) }) test("handles empty", () => { expectElements(append(numbers), numbers) expectElements(append(numbers, ...[]), numbers) expectElements(append([], ...numbers), numbers) expectElements(append([], ...[]), []) expectElements(append([]), []) }) }) describe("associate", () => { const associate = checkSequenceResult(O.associate) const animals = ["hawk", "cougar", "bear"] test("yields key-value pairs using a transform that returns the key", () => { expectElements(associate(animals, (element) => element.length), [[4, "hawk"], [6, "cougar"], [4, "bear"]]) }) test("handles empty", () => { expectElements(associate([] as string[], (element) => element.length), []) }) }) describe("at", () => { const at = checkResult(O.at) const numbers = [1, 2, 3, 4, 5] test("returns the element at an index", () => { expect(at(numbers, 2)).toEqual(3) }) test("can return the first element", () => { expect(at(numbers, 0)).toEqual(1) }) test("can return the last element", () => { expect(at(numbers, 4)).toEqual(5) }) test("throws an error if the index is negative", () => { expect(() => at(numbers, -1)).toThrowError(errorTag("at")) }) test("throws an error if the index beyond the end", () => { expect(() => at(numbers, 5)).toThrowError(errorTag("at")) }) }) describe("cleave", () => { const cleave = checkResult(O.cleave) const animals = ["hawk", "cougar", "bear", "dolphin"] test("splits the sequence into two arrays at a provided index", () => { expect(cleave(animals, 2)).toEqual([["hawk", "cougar"], ["bear", "dolphin"]]) }) test("can cleave at index zero", () => { expect(cleave(animals, 0)).toEqual([[], animals]) }) test("can cleave one beyond the last index", () => { expect(cleave(animals, 4)).toEqual([animals, []]) }) test("throws an error if the index is negative", () => { expect(() => cleave(animals, -1)).toThrowError(errorTag("cleave")) }) test("throws an error if the index is greater than the number of elements", () => { expect(() => cleave(animals, 5)).toThrowError(errorTag("cleave")) }) test("handles empty", () => { expect(cleave([], 0)).toEqual([[], []]) }) }) describe("count", () => { const count = checkResult(O.count) const numbers = [1, 2, 3, 4, 5] describe("no argument", () => { test("returns the number of elements", () => { expect(count(numbers)).toEqual(5) }) test("handles empty", () => { expect(count([])).toEqual(0) }) }) describe("predicate", () => { test("returns the number of elements that match the predicate", () => { expect(count(numbers, (element) => element < 3)).toEqual(2) }) test("handles empty", () => { expect(count([] as number[], (element) => element < 3)).toEqual(0) }) }) }) describe("cut", () => { const cut = checkSequenceResult(O.cut) const numbers = [1, 2, 3, 4, 5] describe("without count", () => { test("removes the element at a specified index", () => { expectElements(cut(numbers, 1), [1, 3, 4, 5]) }) test("can remove the first element", () => { expectElements(cut(numbers, 0), [2, 3, 4, 5]) }) test("can remove the last element", () => { expectElements(cut(numbers, 4), [1, 2, 3, 4]) }) test("throws an error if there are no elements", () => { expect(() => cut([], 0).toArray()).toThrowError(errorTag("cut")) }) }) describe("with count", () => { test("removes a given number of elements after and including the element at a start index", () => { expectElements(cut(numbers, 1, 2), [1, 4, 5]) }) test("removes all elements until the end of the sequence if the count is equal to the number of " + "remaining elements", () => { expectElements(cut(numbers, 1, 4), [1]) }) test("removes all elements until the end of the sequence if the count is greater than the number of " + "elements", () => { expectElements(cut(numbers, 1, 5), [1]) }) test("can remove zero elements", () => { expectElements(cut(numbers, 1, 0), numbers) }) test("interprets negative count as zero", () => { expectElements(cut(numbers, 1, -1), numbers) }) }) }) describe("chunk", () => { const chunk = checkSequenceResult(O.chunk) const numbers = [1, 2, 3, 4, 5] test("divides the sequence into subsequences of a given size", () => { expectElements(chunk(numbers, 1), [[1], [2], [3], [4], [5]]) expectElements(chunk(numbers, 2), [[1, 2], [3, 4], [5]]) expectElements(chunk(numbers, 3), [[1, 2, 3], [4, 5]]) expectElements(chunk(numbers, 5), [[1, 2, 3, 4, 5]]) expectElements(chunk(numbers, 10), [[1, 2, 3, 4, 5]]) expectElements(chunk(new Span([1, 2, 3, 4]), 2), [[1, 2], [3, 4]]) }) test("handles empty", () => { expectElements(chunk([], 1), []) expectElements(chunk([], 2), []) }) test("throws an error if the chunk size is less than 1", () => { expect(() => chunk([], 0)).toThrowError(errorTag("chunk")) expect(() => chunk([], -1)).toThrowError(errorTag("chunk")) expect(() => chunk([], 0.5)).toThrowError(errorTag("chunk")) }) }) describe("difference", () => { const difference = checkSequenceResult(O.difference) describe("no transform", () => { const a = [1, 3, 2, 4, 4] const b = [1, 2, 5] test("yields all elements unique between two sequences starting with the first", () => { expectElements(difference(a, b), [3, 4, 5]) }) test("yields no elements if both sequences have the same elements", () => { expectElements(difference(a, a), []) }) test("handles empty", () => { expectElements(difference(b, []), b) expectElements(difference([], b), b) expectElements(difference([], []), []) }) }) describe("transform", () => { const a = [ {animal: "hawk"}, {animal: "cougar"}, {animal: "bear"}, {animal: "snail"} ] const b = [ {animal: "hawk"}, {animal: "tiger"}, {animal: "lion"}, {animal: "bear"} ] const getAnimal = (element: { animal: string }) => element.animal test("yields all elements unique between two sequences according to a transform function starting with " + "the first", () => { expectElements(difference(a, b, getAnimal), [ {animal: "cougar"}, {animal: "snail"}, {animal: "tiger"}, {animal: "lion"} ]) }) test("yields no elements if both sequences have the same elements", () => { expectElements(difference(a, a, getAnimal), []) }) test("handles empty", () => { expectElements(difference(b, [], getAnimal), b) expectElements(difference([], b, getAnimal), b) expectElements(difference([], [], getAnimal), []) }) }) }) describe("drop", () => { const drop = checkSequenceResult(O.drop) const numbers = [1, 2, 3, 4, 3, 2, 1] describe("no argument", () => { test("removes a single element from the front", () => { expectElements(drop(numbers), [2, 3, 4, 3, 2, 1]) }) test("handles empty", () => { expectElements(drop([]), []) }) }) describe("count", () => { test("removes a given number of elements from the front", () => { expectElements(drop(numbers, 4), [3, 2, 1]) }) test("yields no elements if the count is greater than the number of elements", () => { expectElements(drop(numbers, 10), []) }) test("yields all elements if the count is zero", () => { expectElements(drop(numbers, 0), numbers) }) test("interprets negative count as zero", () => { expectElements(drop(numbers, -2), numbers) }) test("handles empty", () => { expectElements(drop([], 2), []) expectElements(drop([], -2), []) }) }) describe("predicate", () => { test("removes elements from the front until the predicate returns false on an element", () => { expectElements(drop(numbers, (element) => element < 4), [4, 3, 2, 1]) }) test("yields no elements if all elements match the predicate", () => { expectElements(drop(numbers, (element) => element < 10), []) }) test("yields all elements if the first element does not match the predicate", () => { expectElements(drop(numbers, (element) => element > 10), numbers) }) test("handles empty", () => { expectElements(drop([], (element) => element < 10), []) }) }) }) describe("dropLast", () => { const dropLast = checkSequenceResult(O.dropLast) const numbers = [1, 2, 3, 4, 3, 2, 1] describe("no argument", () => { test("removes a single element from the front", () => { expectElements(dropLast(numbers, 4), [1, 2, 3]) }) test("handles empty", () => { expectElements(dropLast([]), []) }) }) describe("count", () => { test("removes a given number of elements from the front", () => { expectElements(dropLast(numbers, 4), [1, 2, 3]) }) test("yields no elements if the count is greater than the number of elements", () => { expectElements(dropLast(numbers, 10), []) }) test("yields all elements if the count is zero", () => { expectElements(dropLast(numbers, 0), numbers) }) test("interprets negative count as zero", () => { expectElements(dropLast(numbers, -2), numbers) }) test("handles empty", () => { expectElements(dropLast([], 2), []) expectElements(dropLast([], -2), []) }) }) describe("predicate", () => { test("removes elements from the back until the predicate returns false on an element", () => { expectElements(dropLast(numbers, (element) => element < 4), [1, 2, 3, 4]) }) test("yields no elements if all elements match the predicate", () => { expectElements(dropLast(numbers, (element) => element < 10), []) }) test("yields all elements if the last element does not match the predicate", () => { expectElements(dropLast(numbers, (element) => element > 10), numbers) }) test("handles empty", () => { expectElements(dropLast([], (element) => element < 10), []) }) }) }) describe("each", () => { const animals = ["hawk", "cougar", "bear"] const animalsWithIndex = [["hawk", 0], ["cougar", 1], ["bear", 2]] function* yieldAll(sequence: Sequence) { yield* sequence } describe("iterates over all elements, provides the index and returns the original sequence", () => { test("pipe", () => { const output: [string, number][] = [] O.each(new Pipe(animals).take(), (element, i) => { output.push([element, i]) }).each((element, i) => { output.push([element, i]) }) expect(output).toEqual(animalsWithIndex) }) test("span", () => { const output: [string, number][] = [] O.each(animals, (element, i) => { output.push([element, i]) }).each((element, i) => { output.push([element, i]) }) expect(output).toEqual(animalsWithIndex.concat(animalsWithIndex)) }) }) describe("can be exited early by calling the done() callback", () => { test("pipe", () => { const output: string[] = [] O.each(new Pipe(animals).take(), (element, i, done) => { output.push(element) if (i === 1) { done() } }) expect(output).toEqual(["hawk", "cougar"]) }) test("span", () => { const output: string[] = [] O.each(animals, (element, i, done) => { output.push(element) if (i === 1) { done() } }) expect(output).toEqual(["hawk", "cougar"]) }) }) }) describe("endsWith", () => { const endsWith = checkResult(O.endsWith) const numbers = [1, 2, 3, 2, 1] test("returns true if the last elements of the sequence are equal to those of a suffix", () => { expect(endsWith(numbers, [3, 2, 1])).toEqual(true) }) test("returns false otherwise", () => { expect(endsWith(numbers, [-3, -2, -1])).toEqual(false) }) test("can use a custom equals function", () => { expect(endsWith(numbers, [-3, -2, -1], (a, b) => Math.abs(a) === Math.abs(b))).toEqual(true) }) test("returns true if the suffix is empty", () => { expect(endsWith(numbers, [])) }) test("handles empty", () => { expect(endsWith([], [1, 2, 3])).toEqual(false) expect(endsWith([], [1, 2, 3], (a, b) => a === b)).toEqual(false) expect(endsWith([], [])).toEqual(true) expect(endsWith([], [], (a, b) => a === b)).toEqual(true) }) }) describe("entries", () => { const entries = checkSequenceResult(O.entries) const animals = ["hawk", "cougar", "bear"] test("yields index-value pairs for all elements", () => { expectElements(entries(animals), [[0, "hawk"], [1, "cougar"], [2, "bear"]]) }) test("handles empty", () => { expectElements(entries([]), []) }) }) describe("entriesReversed", () => { const entriesReversed = checkSequenceResult(O.entriesReversed) const animals = ["hawk", "cougar", "bear"] test("yields index-value pairs for all elements in reversed order", () => { expectElements(entriesReversed(animals), [[2, "bear"], [1, "cougar"], [0, "hawk"]]) }) test("handles empty", () => { expectElements(entriesReversed([]), []) }) }) describe("filter", () => { const filter = checkSequenceResult(O.filter) describe("no predicate", () => { const elements = [false, true, 0, 1, null, undefined, {}, [], "", "string"] test("yields all truthy elements", () => { expectElements(filter(elements), [true, 1, {}, [], "string"]) }) }) describe("predicate", () => { const numbers = [1, 2, 3, 4, 3, 2, 1] test("yields all elements that match a predicate", () => { expectElements(filter(numbers, (element) => element > 2), [3, 4, 3]) }) test("handles empty", () => { expectElements(filter([], (element) => element > 2), []) }) }) }) describe("find", () => { const find = checkResult(O.find) const animals = ["hawk", "cougar", "bear", "dolphin"] test("returns the first element matching a predicate", () => { expect(find(animals, (element) => element.length > 4)).toEqual("cougar") }) test("can return a default value if the element is not found", () => { expect(find(animals, (element) => element.length > 10, "default")).toEqual("default") }) test("returns null if the element is not found and no default is provided", () => { expect(find(animals, (element) => element.length > 10)).toEqual(null) }) test("handles empty", () => { expect(find([] as string[], (element) => element.length > 10, "default")).toEqual("default") expect(find([] as string[], (element) => element.length > 10)).toEqual(null) }) }) describe("findLast", () => { const findLast = checkResult(O.findLast) const animals = ["hawk", "cougar", "bear", "dolphin"] test("returns the last element matching a predicate", () => { expect(findLast(animals, (element) => element.length > 4)).toEqual("dolphin") }) test("can return a default value if the element is not found", () => { expect(findLast(animals, (element) => element.length > 10, "default")).toEqual("default") }) test("throws an error if the element is not found and no default is provided", () => { expect(findLast(animals, (element) => element.length > 10)).toEqual(null) }) test("handles empty", () => { expect(findLast([] as string[], (element) => element.length > 10, "default")).toEqual("default") expect(findLast([] as string[], (element) => element.length > 10)).toEqual(null) }) }) describe("first", () => { const first = checkResult(O.first) const animals = ["hawk", "cougar", "bear", "dolphin"] test("returns the first element", () => { expect(first(animals)).toEqual("hawk") }) test("can return a default value if there is no first element", () => { expect(first([], "default")).toEqual("default") }) test("throws an error if there is no first element and no default is provided", () => { expect(() => first([])).toThrowError(errorTag("first")) }) }) describe("flat", () => { const flat = checkSequenceResult(O.flat) const nested = [ [], [1, 2], new Span([3, 4]), new Set([5, 6]), [[1]], [] ] test("flattens an iterable of iterables one level", () => { expectElements(flat(nested), [1, 2, 3, 4, 5, 6, [1]]) }) test("handles empty", () => { expectElements(flat([]), []) }) }) describe("fold", () => { const fold = checkResult(O.fold) const numbers = [1, 2, 3, 2, 1] test("reduces all elements to a single value by continuously applying a reducer function to the accumulated" + " value and the current element", () => { expect(fold(numbers, 0, (a, b) => a + b)).toEqual(9) }) test("handles empty", () => { expect(fold([], 0, (a, b) => a + b)).toEqual(0) }) }) describe("index", () => { const index = checkResult(O.index) const numbers = [1, 2, 3, 2, 1] test("returns the index of the first element that matches a predicate", () => { expect(index(numbers, (element) => element === 2)).toEqual(1) }) test("returns negative one if the element is not found", () => { expect(index(numbers, (element) => element > 10)).toEqual(-1) }) test("handles empty", () => { expect(index([], (element) => element > 10)).toEqual(-1) }) }) describe("indices", () => { const indices = checkSequenceResult(O.indices) describe("no predicate", () => { const animals = ["hawk", "cougar", "bear"] test("yields the indices of all elements", () => { expectElements(indices(animals), [0, 1, 2]) }) test("handles empty", () => { expectElements(indices([]), []) }) }) describe("predicate", () => { const numbers = [1, 2, 3, 2, 1] test("returns the index of the first element that matches a predicate", () => { expectElements(indices(numbers, (element) => element === 2), [1, 3]) }) test("returns no elements if no elements match the predicate", () => { expect(indices(numbers, (element) => element > 10)).toEqual([]) }) test("handles empty", () => { expect(indices([], (element) => element > 10)).toEqual([]) }) }) }) describe("indicesReversed", () => { const indicesReversed = checkSequenceResult(O.indicesReversed) describe("no predicate", () => { const animals = ["hawk", "cougar", "bear"] test("yields the indices of all elements in reversed order", () => { expectElements(indicesReversed(animals), [2, 1, 0]) }) test("handles empty", () => { expectElements(indicesReversed([]), []) }) }) describe("predicate", () => { const numbers = [1, 2, 3, 2, 1] test("returns the index of the first element that matches a predicate", () => { expectElements(indicesReversed(numbers, (element) => element === 2), [3, 1]) }) test("returns no elements if no elements match the predicate", () => { expect(indicesReversed(numbers, (element) => element > 10)).toEqual([]) }) test("handles empty", () => { expect(indicesReversed([], (element) => element > 10)).toEqual([]) }) }) }) describe("inject", () => { const inject = checkSequenceResult(O.inject) const numbers = [1, 2, 3, 4, 5] test("yields all elements with the elements of another sequence inserted at a specified index", () => { expectElements(inject(numbers, 1, [10, 10]), [1, 10, 10, 2, 3, 4, 5]) }) test("can inject at start", () => { expectElements(inject(numbers, 0, [10, 10]), [10, 10, 1, 2, 3, 4, 5]) }) test("can inject at end", () => { expectElements(inject(numbers, 5, [10, 10]), [1, 2, 3, 4, 5, 10, 10]) }) test("throws an error if the index is negative", () => { expect(() => inject(numbers, -1, [10, 10])).toThrowError(errorTag("inject")) }) test("throws an error if the index is greater than the number of elements", () => { expect(() => inject(numbers, 6, [10, 10])).toThrowError(errorTag("inject")) }) test("handles empty", () => { expectElements(inject([], 0, [10, 10]), [10, 10]) expectElements(inject(numbers, 1, []), numbers) }) }) describe("intersect", () => { const intersect = checkSequenceResult(O.intersect) describe("no transform", () => { const a = [1, 3, 2, 2, 4, 4] const b = [1, 2, 5, 1, 5] test("yields all elements common to both sequences starting with the first", () => { expectElements(intersect(a, b), [1, 2]) }) test("yields all elements if both sequences have the same elements", () => { expectElements(intersect(a, a), [1, 3, 2, 4]) }) test("handles empty", () => { expectElements(intersect(b, []), []) expectElements(intersect([], b), []) expectElements(intersect([], []), []) }) }) describe("transform", () => { const a = [ {animal: "hawk"}, {animal: "cougar"}, {animal: "bear"}, {animal: "snail"} ] const b = [ {animal: "hawk"}, {animal: "tiger"}, {animal: "lion"}, {animal: "bear"} ] const getName = (element: { animal: string }) => element.animal test("yields all elements common to both sequences according to a transform function starting with " + "the first", () => { expectElements(intersect(a, b, getName), [ {animal: "hawk"}, {animal: "bear"} ]) }) test("yields all elements if both sequences have the same elements", () => { expectElements(intersect(a, a, getName), a) }) test("handles empty", () => { expectElements(intersect(b, [], getName), []) expectElements(intersect([], b, getName), []) expectElements(intersect([], [], getName), []) }) }) }) describe("insert", () => { const insert = checkSequenceResult(O.insert) const numbers = [1, 2, 3, 4, 5] test("yields all elements with the elements of another sequence inserted at a specified index", () => { expectElements(insert(numbers, 1, 10, 10), [1, 10, 10, 2, 3, 4, 5]) }) test("can inject at start", () => { expectElements(insert(numbers, 0, 10, 10), [10, 10, 1, 2, 3, 4, 5]) }) test("can inject at end", () => { expectElements(insert(numbers, 5, 10, 10), [1, 2, 3, 4, 5, 10, 10]) }) test("throws an error if the index is negative", () => { expect(() => insert(numbers, -1, 10, 10)).toThrowError(errorTag("insert")) }) test("throws an error if the index is greater than the number of elements", () => { expect(() => insert(numbers, 6, 10, 10)).toThrowError(errorTag("insert")) }) test("handles empty", () => { expectElements(insert([], 0, 10, 10), [10, 10]) expectElements(insert(numbers, 1), numbers) }) }) describe("invert", () => { const invert = checkSequenceResult(O.invert) const animalsToNumbers = [["hawk", 1], ["cougar", 5], ["bear", 10]] as [string, number][] test("flips a sequence of key-value pairs", () => { expectElements(invert(animalsToNumbers), [[1, "hawk"], [5, "cougar"], [10, "bear"]]) }) test("handles empty", () => { expectElements(invert([]), []) }) }) describe("join", () => { const join = checkResult(O.join) const animals = ["hawk", "cougar", "bear"] const word = "great" test("joins a sequence of strings with no separator", () => { expect(join(animals)).toEqual("hawkcougarbear") expect(join(word)).toEqual("great") }) test("joins a sequence of strings a separator", () => { expect(join(animals, " ")).toEqual("hawk cougar bear") expect(join(word, " ")).toEqual("g r e a t") }) test("handles empty", () => { expect(join([], " ")).toEqual("") expect(join("", " ")).toEqual("") }) }) describe("last", () => { const last = checkResult(O.last) const animals = ["hawk", "cougar", "bear", "dolphin"] test("returns the first element", () => { expect(last(animals)).toEqual("dolphin") }) test("can return a default value if there is no last element", () => { expect(last([], "default")).toEqual("default") }) test("throws an error if there is no last element and no default is provided", () => { expect(() => last([])).toThrowError(errorTag("last")) }) }) describe("lastIndex", () => { const lastIndex = checkResult(O.lastIndex) describe("no predicate", () => { const numbers = [1, 2, 3, 4, 5] test("returns the index of the last element", () => { expect(lastIndex(numbers)).toEqual(4) }) test("returns negative one if there are no elements", () => { expect(lastIndex([])).toEqual(-1) }) }) describe("predicate", () => { const numbers = [1, 2, 3, 2, 1] test("returns the index of the first element that matches a predicate", () => { expect(lastIndex(numbers, (element) => element === 2)).toEqual(3) }) test("returns negative one if the element is not found", () => { expect(lastIndex(numbers, (element) => element > 10)).toEqual(-1) }) test("handles empty", () => { expect(lastIndex([], (element) => element > 10)).toEqual(-1) }) }) }) describe("map", () => { const map = checkSequenceResult(O.map) const animals = ["hawk", "cougar", "bear", "dolphin"] test("applies a transform to each element, yielding each result", () => { expectElements(map(animals, (element) => element.length), [4, 6, 4, 7]) }) test("handles empty", () => { expectElements(map([] as string[], (element) => element.length), []) }) }) describe("max", () => { const max = checkResult(O.max) const numbers = [1, 2, 3, 2, 1] const animals = ["hawk", "cougar", "bear"] describe("no transform", () => { test("returns the maximum value of a sequence of numbers", () => { expect(max(numbers)).toEqual(3) }) test("throws an error if there are no elements", () => { expect(() => max([])).toThrowError(errorTag("max")) }) }) describe("transform", () => { test("returns the first element for which a transform function returns the largest number", () => { expect(max(animals, (element) => element.length)).toEqual("cougar") }) test("throws an error if there are no elements", () => { expect(() => max([] as string[], (element) => element.length)).toThrowError(errorTag("max")) }) }) }) describe("mean", () => { const mean = checkResult(O.mean) const numbers = [1, 2, 3, 3, 2, 1] test("returns the mean of a sequence of numbers", () => { expect(mean(numbers)).toBeCloseTo(2) }) test("returns zero if there are no elements", () => { expect(mean([])).toEqual(0) }) }) describe("min", () => { const min = checkResult(O.min) const numbers = [1, 2, 3, 2, 1] const animals = ["hawk", "cougar", "bear"] describe("no transform", () => { test("returns the minimum value of a sequence of numbers", () => { expect(min(numbers)).toEqual(1) }) test("throws an error if there are no elements", () => { expect(() => min([])).toThrowError(errorTag("min")) }) }) describe("transform", () => { test("returns the first element for which a transform function returns the largest number", () => { expect(min(animals, (element) => element.length)).toEqual("hawk") }) test("throws an error if there are no elements", () => { expect(() => min([] as string[], (element) => element.length)).toThrowError(errorTag("min")) }) }) }) describe("none", () => { const none = checkResult(O.none) const numbers = [1, 2, 3, 2, 1] describe("no predicate", () => { test("returns true if there are no elements", () => { expect(none([])).toEqual(true) }) test("returns false if there are any elements", () => { expect(none([1])).toEqual(false) }) }) describe("predicate", () => { test("returns true if no elements match a predicate", () => { expect(none(numbers, (element) => element > 10)).toEqual(true) }) test("returns false if any element matches a predicate", () => { expect(none(numbers, (element) => element === 1)).toEqual(false) }) test("returns true if there are no elements", () => { expect(none([], (element) => element === 1)).toEqual(true) }) }) }) describe("partition", () => { const partition = O.partition const numbers = [1, 2, 3, 4, 3, 2, 1] test("divides elements into two arrays, one with elements that match a predicate and the other with those" + " that don't", () => { expect(partition(numbers, (element) => element < 3)).toEqual([[1, 2, 2, 1], [3, 4, 3]]) }) test("handles empty", () => { expect(partition([], (element) => element < 3)).toEqual([[], []]) }) }) describe("permute", () => { const permute = O.permute const numbers = [1, 2, 3] test("yields all permutations of the sequence as arrays", () => { expectElements(permute(numbers), [ [1, 2, 3], [2, 1, 3], [3, 1, 2], [1, 3, 2], [2, 3, 1], [3, 2, 1] ]) }) test("handles empty", () => { expectElements(permute([]), [[]]) }) }) describe("prepend", () => { const prepend = checkSequenceResult(O.prepend) const numbers = [1, 2, 3] test("yields the provided elements, then the elements of the sequence", () => { expectElements(prepend(numbers, 0), [0, 1, 2, 3]) expectElements(prepend(numbers, -1, 0), [-1, 0, 1, 2, 3]) }) test("handles empty", () => { expectElements(prepend(numbers), numbers) expectElements(prepend(numbers, ...[]), numbers) expectElements(prepend([], ...numbers), numbers) expectElements(prepend([], ...[]), []) expectElements(prepend([]), []) }) }) describe("rank", () => { const rank = checkSequenceResult(O.rank) const animals = ["dolphin", "hawk", "cougar", "dog"] test("yields elements sorted in ascending order by their value according to a transform", () => { expectElements(rank(animals, (element) => element.length), ["dog", "hawk", "cougar", "dolphin"]) }) test("yields elements sorted in descending order if the transform returns a negative", () => { expectElements(rank(animals, (element) => -element.length), ["dolphin", "cougar", "hawk", "dog"]) }) test("handles empty", () => { expectElements(rank([] as string[], (element) => element.length), []) }) }) describe("remove", () => { const remove = checkSequenceResult(O.remove) const numbers = [1, 2, 3, 4, 3, 2, 1] describe("no count", () => { test("removes all elements that do not match the predicate", () => { expectElements(remove(numbers, (element) => element < 3), [3, 4, 3]) }) test("handles empty", () => { expectElements(remove([] as number[], (element) => element < 3), []) }) }) describe("count", () => { test("removes a maximum number of elements that match the predicate", () => { expectElements(remove(numbers, (element) => element < 3, 2), [3, 4, 3, 2, 1]) }) test("interprets a negative count as zero", () => { expectElements(remove(numbers, (element) => element < 3, -2), numbers) }) test("handles empty", () => { expectElements(remove([] as number[], (element) => element < 3, 2), []) expectElements(remove([] as number[], (element) => element < 3, -2), []) }) }) }) describe("removeLast", () => { const removeLast = checkSequenceResult(O.removeLast) const numbers = [1, 3, 2, 3, 1, 2, 3, 1] test("removes a maximum number of elements from the back of the sequence that match the predicate", () => { expectElements(removeLast(numbers, (element) => element < 3, 2), [1, 3, 2, 3, 1, 3]) }) test("interprets a negative count as zero", () => { expectElements(removeLast(numbers, (element) => element < 3, -2), numbers) }) test("handles empty", () => { expectElements(removeLast([] as number[], (element) => element < 3, 2), []) expectElements(removeLast([] as number[], (element) => element < 3, -2), []) }) }) describe("repeat", () => { const repeat = checkSequenceResult(O.repeat) const numbers = [1, 2, 3] describe("count", () => { test("yields the sequence of elements repeated a given number of times", () => { expectElements(repeat(numbers, 3), [1, 2, 3, 1, 2, 3, 1, 2, 3]) }) test("yields no elements with a count of zero", () => { expectElements(repeat(numbers, 0), []) }) test("interprets a negative count as zero", () => { expectElements(repeat(numbers, -2), []) }) test("handles empty", () => { expectElements(repeat([], 3), []) expectElements(repeat([], 0), []) expectElements(repeat([], -2), []) }) }) describe("no count", () => { test("yields the sequence of elements repeated infinitely", () => { expectElements(O.repeat(numbers).take(9), [1, 2, 3, 1, 2, 3, 1, 2, 3]) }) }) }) describe("replace", () => { const replace = checkSequenceResult(O.replace) const numbers = [1, 2, 3, 4, 3, 2, 1] describe("no count", () => { test("replaces all elements matching a predicate with a replacement value", () => { expectElements(replace(numbers, (element) => element % 2 === 0, 10), [1, 10, 3, 10, 3, 10, 1]) }) test("handles empty", () => { expectElements(replace([], (element) => element % 2 === 0, 10), []) }) }) describe("count", () => { test("replaces a maximum number of elements matching a predicate with a replacement value", () => { expectElements(replace(numbers, (element) => element % 2 === 0, 10, 2), [1, 10, 3, 10, 3, 2, 1]) expectElements(replace(numbers, (element) => element % 2 === 0, 10, 10), [1, 10, 3, 10, 3, 10, 1]) }) test("handles empty", () => { expectElements(replace([], (element) => element % 2 === 0, 10, 2), []) }) }) }) describe("replaceLast", () => { const replaceLast = checkSequenceResult(O.replaceLast) const numbers = [1, 2, 3, 4, 3, 2, 1] test("starting from the back, replaces a maximum number of from elements matching a predicate with a" + " replacement value", () => { expectElements(replaceLast(numbers, (element) => element % 2 === 0, 10, 2), [1, 2, 3, 10, 3, 10, 1]) expectElements(replaceLast(numbers, (element) => element % 2 === 0, 10, 10), [1, 10, 3, 10, 3, 10, 1]) }) test("handles empty", () => { expectElements(replaceLast([], (element) => element % 2 === 0, 10, 2), []) }) }) describe("reversed", () => { const reversed = checkSequenceResult(O.reversed) const numbers = [1, 2, 3] test("yields the sequence of elements repeated a given number of times", () => { expectElements(reversed(numbers), [3, 2, 1]) }) test("handles empty", () => { expectElements(reversed([]), []) }) }) describe("set", () => { const set = checkSequenceResult(O.set) const numbers = [1, 2, 3, 4] test("yields all elements with the element at an index swapped with another element", () => { expectElements(set(numbers, 1, 10), [1, 10, 3, 4]) expectElements(set(numbers, 3, 10), [1, 2, 3, 10]) }) test("throws an error if the index is out of bounds", () => { expect(() => set(numbers, -1, 10).toArray()).toThrowError(errorTag("set")) expect(() => set(numbers, 4, 10).toArray()).toThrowError(errorTag("set")) }) }) describe("slice", () => { const slice = checkSequenceResult(O.slice) const numbers = [1, 2, 3, 2, 1] test("yields all elements after and including the start index but before the end index", () => { expectElements(slice(numbers, 2, 4), [3, 2]) }) test("yields all elements if the start is zero and the end is the number of elements", () => { expectElements(slice(numbers, 0, 5), numbers) }) test("yields no elements if the indices are the same", () => { expectElements(slice(numbers, 0, 0), []) expectElements(slice(numbers, 2, 2), []) expectElements(slice(numbers, 5, 5), []) }) test("throws an error if the start index is negative", () => { expect(() => slice(numbers, -1, 4)).toThrowError(errorTag("slice")) }) test("throws an error if the start index is greater than the number of elements", () => { expect(() => slice(numbers, 6, 10)).toThrowError(errorTag("slice")) }) test("throws an error if the end index is less than that start index", () => { expect(() => slice(numbers, 3, 2)).toThrowError(errorTag("slice")) expect(() => slice(numbers, 10, -10)).toThrowError(errorTag("slice")) }) }) describe("some", () => { const some = checkResult(O.some) const numbers = [1, 2, 3, 2, 1] describe("no predicate", () => { test("returns true if there are any elements", () => { expect(some([1])).toEqual(true) }) test("returns false if there are no elements", () => { expect(some([])).toEqual(false) }) }) describe("predicate", () => { test("returns true if any element matches a predicate", () => { expect(some(numbers, (element) => element === 1)).toEqual(true) }) test("returns false if no element matches a predicate", () => { expect(some(numbers, (element) => element > 10)).toEqual(false) }) test("returns false if there are no elements", () => { expect(some([], (element) => element === 1)).toEqual(false) }) }) }) describe("sort", () => { const sort = checkSequenceResult(O.sort) const numbers = [1, 2, 3, 2, 1] const animals = ["hawk", "cougar", "cat", "dolphin"] describe("no comparator", () => { describe("number", () => { test("sorts a sequence of numbers into ascending order", () => { expectElements(sort(numbers), [1, 1, 2, 2, 3]) expectElements(sort(numbers, false), [1, 1, 2, 2, 3]) }) test("can sort into descending order if specified", () => { expectElements(sort(numbers, true), [3, 2, 2, 1, 1]) }) }) describe("string", () => { test("sorts a sequence of strings into ascending lexicographic order", () => { expectElements(sort(animals), ["cat", "cougar", "dolphin", "hawk"]) expectElements(sort(animals, false), ["cat", "cougar", "dolphin", "hawk"]) }) test("can sort into descending order if specified", () => { expectElements(sort(animals, true), ["hawk", "dolphin", "cougar", "cat"]) }) }) test("handles empty", () => { expectElements(sort([]), []) }) }) describe("comparator", () => { test("sorts using a comparator in the same manner as the built-in array sort method", () => { expectElements(sort(animals, (a, b) => a.length - b.length), ["cat", "hawk", "cougar", "dolphin"]) }) test("handles empty", () => { expectElements(sort([] as string[], (a, b) => a.length - b.length), []) }) }) }) describe("startsWith", () => { const startsWith = checkResult(O.startsWith) const numbers = [1, 2, 3, 2, 1] test("returns true if the first elements of the sequence are equal to those of a prefix", () => { expect(startsWith(numbers, [1, 2, 3])).toEqual(true) }) test("returns false otherwise", () => { expect(startsWith(numbers, [-1, -2, -3])).toEqual(false) }) test("can use a custom equals function", () => { expect(startsWith(numbers, [-1, -2, -3], (a, b) => Math.abs(a) === Math.abs(b))).toEqual(true) }) test("returns true if the prefix is empty", () => { expect(startsWith(numbers, [])) }) test("handles empty", () => { expect(startsWith([], [1, 2, 3])).toEqual(false) expect(startsWith([], [1, 2, 3], (a, b) => a === b)).toEqual(false) expect(startsWith([], [])).toEqual(true) expect(startsWith([], [], (a, b) => a === b)).toEqual(true) }) }) describe("sum", () => { const sum = checkResult(O.sum) const numbers = [0, 2, 4, 6] test("returns the sum of a sequence of numbers", () => { expect(sum(numbers)).toEqual(12) }) test("returns zero for an empty sequence", () => { expect(sum([])).toEqual(0) }) }) describe("swap", () => { const swap = checkSequenceResult(O.swap) const numbers = [0, 1, 2, 3, 4] test("swaps the elements at specified indices", () => { expectElements(swap(numbers, 1, 3), [0, 3, 2, 1, 4]) }) test("order of indices doesn't matter", () => { expectElements(swap(numbers, 3, 1), [0, 3, 2, 1, 4]) }) test("can swap the first and last elements", () => { expectElements(swap(numbers, 0, 4), [4, 1, 2, 3, 0]) }) test("can swap an element with itself", () => { expectElements(swap(numbers, 0, 0), numbers) expectElements(swap(numbers, 1, 1), numbers) expectElements(swap(numbers, 4, 4), numbers) }) test("throws an error if the first index is out of bounds", () => { expect(() => swap(numbers, -1, 4)).toThrowError(errorTag("swap")) expect(() => swap(numbers, 5, 4)).toThrowError(errorTag("swap")) }) test("throws an error if the second index is out of bounds", () => { expect(() => swap(numbers, 0, -1)).toThrowError(errorTag("swap")) expect(() => swap(numbers, 5, 4)).toThrowError(errorTag("swap")) }) test("throws an error if both indices are out of bounds", () => { expect(() => swap(numbers, -1, -1)).toThrowError(errorTag("swap")) expect(() => swap(numbers, -1, 5)).toThrowError(errorTag("swap")) expect(() => swap(numbers, 5, -1)).toThrowError(errorTag("swap")) expect(() => swap(numbers, 5, 5)).toThrowError(errorTag("swap")) }) }) describe("take", () => { const take = checkSequenceResult(O.take) const numbers = [1, 2, 3, 2, 1] describe("no arguments", () => { test("returns a pipe or span over all elements", () => { expect(O.take(numbers)).toEqual(new Span(numbers)) expect(O.take(new Pipe(numbers))).toEqual(new Pipe(numbers)) }) }) describe("count", () => { test("yields a given number of elements from the start of the sequence", () => { expectElements(take(numbers, 3), [1, 2, 3]) }) test("yields no elements with a count of zero", () => { expectElements(take(numbers, 0), []) }) test("interprets negative count as zero", () => { expectElements(take(numbers, -2), []) }) test("handles empty", () => { expectElements(take([], 3), []) expectElements(take([], 0), []) expectElements(take([], -2), []) }) }) describe("predicate", () => { const numbers = [1, 2, 3, 2, 1] test("yields elements from the start of the sequence while a predicate is true", () => { expectElements(take(numbers, (element) => element < 3), [1, 2]) }) test("yields all elements if all elements match the predicate", () => { expectElements(take(numbers, (element) => element < 10), numbers) }) test("yields no elements if the first element does not match the predicate", () => { expectElements(take(numbers, (element) => element > 10), []) }) test("handles empty", () => { expectElements(take([], (element) => element < 3), []) }) }) }) describe("takeLast", () => { const takeLast = checkSequenceResult(O.takeLast) const numbers = [1, 2, 3, 2, 1] describe("count", () => { test("yields a given number of elements from the end of the sequence", () => { expectElements(takeLast(numbers, 3), [3, 2, 1]) }) test("yields no elements with a count of zero", () => { expectElements(takeLast(numbers, 0), []) }) test("interprets negative count as zero", () => { expectElements(takeLast(numbers, -2), []) }) test("handles empty", () => { expectElements(takeLast([], 3), []) expectElements(takeLast([], 0), []) expectElements(takeLast([], -2), []) }) }) describe("union", () => { const union = checkSequenceResult(O.union) describe("no transform", () => { const a = [1, 3, 2, 4, 4, 2] const b = [1, 2, 5, 2] test("yields all elements present in both sequences without duplicates", () => { expectElements(union(a, b), [1, 3, 2, 4, 5]) }) test("yields all elements if both sequences have the same elements", () => { expectElements(union(a, a), [1, 3, 2, 4]) }) test("handles empty", () => { expectElements(union(a, []), [1, 3, 2, 4]) expectElements(union([], a), [1, 3, 2, 4]) expectElements(union([], []), []) }) }) describe("transform", () => { const a = [ {animal: "hawk"}, {animal: "cougar"}, {animal: "bear"}, {animal: "snail"} ] const b = [ {animal: "hawk"}, {animal: "tiger"}, {animal: "lion"}, {animal: "bear"} ] const getAnimal = (object: { animal: string }) => object.animal test("yields all elements present in both sequences without duplicates according to a transform", () => { expectElements(union(a, b, getAnimal), [ {animal: "hawk"}, {animal: "cougar"}, {animal: "bear"}, {animal: "snail"}, {animal: "tiger"}, {animal: "lion"} ]) }) test("yields all elements if both sequences have the same elements", () => { expectElements(union(a, a, getAnimal), a) }) test("handles empty", () => { expectElements(union(a, [], getAnimal), a) expectElements(union([], a, getAnimal), a) expectElements(union([], [], getAnimal), []) }) }) }) describe("unique", () => { const unique = checkSequenceResult(O.unique) describe("no transform", () => { const numbers = [1, 3, 3, 2, 4, 3, 1, 8, 1, 0, 3] test("removes all duplicate elements", () => { expectElements(unique(numbers), [1, 3, 2, 4, 8, 0]) }) test("handles empty", () => { expectElements(unique([]), []) }) }) describe("transform", () => { const numbers = ["fish", "hawk", "hawk", "cougar", "bear", "cougar", "bear", "tiger"] test("returns only elements unique by a given transform", () => { expectElements(unique(numbers, (element) => element.length), ["fish", "cougar", "tiger"]) }) test("handles empty", () => { expectElements(unique([] as string[], (element) => element.length), []) }) }) }) describe("predicate", () => { const numbers = [1, 2, 3, 2, 1] test("yields elements from the start of the sequence while a predicate is true", () => { expectElements(takeLast(numbers, (element) => element < 3), [2, 1]) }) test("yields all elements if all elements match the predicate", () => { expectElements(takeLast(numbers, (element) => element < 10), numbers) }) test("yields no elements if the first element does not match the predicate", () => { expectElements(takeLast(numbers, (element) => element > 10), []) }) test("handles empty", () => { expectElements(takeLast([], (element) => element < 3), []) }) }) }) describe("toArray", () => { const toArray = O.toArray const animals = ["hawk", "cougar", "bear"] const numbers = [1, 2, 3] const animalsToNumbers = [ ["hawk", 1], ["cougar", 2], ["bear", 3] ] as [string, number][] describe("no destination", () => { test("returns the elements of the sequence as an array", () => { const array = ["original"] const result = toArray(animals) expect(result).toEqual(["hawk", "cougar", "bear"]) }) test("handles empty", () => { expect(toArray([])).toEqual([]) }) }) describe("destination", () => { test("outputs all elements to a specified array and returns the array", () => { const array = ["original"] const result = toArray(animals, array) expect(result).toEqual(["original", "hawk", "cougar", "bear"]) expect(result).toBe(array) }) test("can clear the array if specified", () => { const array = ["original"] const result = toArray(animals, array, true) expect(result).toEqual(["hawk", "cougar", "bear"]) expect(result).toBe(array) }) test("handles empty", () => { expect(toArray([], [])).toEqual([]) }) }) }) describe("toMap", () => { const toMap = O.toMap const animals = ["hawk", "cougar", "bear"] const numbers = [1, 2, 3] const animalsToNumbers = [ ["hawk", 1], ["cougar", 2], ["bear", 3] ] as [string, number][] describe("no destination", () => { test("outputs all elements to a specified map and returns the map", () => { expect(toMap(animalsToNumbers)).toEqual(new Map(animalsToNumbers)) }) test("handles empty", () => { expect(toMap([])).toEqual(new Map()) }) }) describe("destination", () => { test("outputs all elements to a specified map and returns the map", () => { const map = new Map([["original", 1], ["hawk", 10]]) const result = toMap(animalsToNumbers, map) expect(result).toEqual(new Map([["original", 1], ["hawk", 1], ["cougar", 2], ["bear", 3]])) expect(result).toBe(map) }) test("can clear the map if specified", () => { const map = new Map([["original", 1], ["hawk", 10]]) const result = toMap(animalsToNumbers, map, true) expect(result).toEqual(new Map([["hawk", 1], ["cougar", 2], ["bear", 3]])) expect(result).toBe(map) }) test("handles empty", () => { expect(toMap([], new Map())).toEqual(new Map()) }) }) }) describe("toObject", () => { const toObject = O.toObject const animals = ["hawk", "cougar", "bear"] const numbers = [1, 2, 3] const animalsToNumbers = [ ["hawk", 1], ["cougar", 2], ["bear", 3] ] as [string, number][] describe("no destination", () => { test("outputs all string-value pairs as an object and returns the object", () => { const object = { original: 1, hawk: 10 } expect(toObject(animalsToNumbers)).toEqual({ hawk: 1, cougar: 2, bear: 3 }) }) test("handles empty", () => { expect(toObject([])).toEqual({}) }) }) describe("destination", () => { test("outputs all string-value pairs to a specified object and returns the object", () => { const object = { original: 1, hawk: 10 } const result = toObject(animalsToNumbers, object) expect(result).toEqual({ original: 1, hawk: 1, cougar: 2, bear: 3 }) expect(result).toBe(object) }) test("can clear the object if specified", () => { const object = { original: 1, hawk: 10 } const result = toObject(animalsToNumbers, object, true) expect(result).toEqual({ hawk: 1, cougar: 2, bear: 3, }) }) test("handles empty", () => { expect(toObject([], {})).toEqual({}) }) }) }) describe("toSet", () => { const toSet = O.toSet const animals = ["hawk", "cougar", "bear"] const numbers = [1, 2, 3] const animalsToNumbers = [ ["hawk", 1], ["cougar", 2], ["bear", 3] ] as [string, number][] describe("destination", () => { test("outputs all elements as a set and returns the set", () => { const result = toSet(animals) expect(result).toEqual(new Set(animals)) }) test("handles empty", () => { expect(toSet([])).toEqual(new Set([])) }) }) describe("destination", () => { test("outputs all elements to a specified set and returns the set", () => { const set = new Set(["original", "hawk"]) const result = toSet(animals, set) expect(result).toEqual(new Set(["original", "hawk", "cougar", "bear"])) expect(result).toBe(set) }) test("can clear the set if specified", () => { const set = new Set(["original", "hawk"]) const result = toSet(animals, set, true) expect(result).toEqual(new Set(["hawk", "cougar", "bear"])) expect(result).toBe(set) }) test("handles empty", () => { expect(toSet([], new Set())).toEqual(new Set([])) }) }) }) describe("solid", () => { const animals = ["hawk", "cougar", "bear"] test("converts a non-span pipe into a span", () => { expect(O.solid(new Pipe(animals))).toEqual(new Span(animals)) expect(O.filter(animals, (element) => element.length > 4).solid()).toEqual(new Span(["cougar"])) }) test("returns the same object if the pipe is already a span", () => { const span = new Span(animals) expect(span.solid()).toBe(span) }) test("handles empty", () => { expect(new Pipe([]).solid()).toEqual(new Span([])) expect(new Span([]).solid()).toEqual(new Span([])) }) }) describe("zip", () => { const zip = checkSequenceResult(O.zip) const animals = ["hawk", "cougar", "bear"] const numbers = [1, 2, 3] test("combines a sequence with another sequence, yielding key-value pairs", () => { expectElements(zip(animals, numbers), [["hawk", 1], ["cougar", 2], ["bear", 3]]) }) test("yields the number of elements equal to the minimum sequence length", () => { expectElements(zip(animals.slice(0, 2), numbers), [["hawk", 1], ["cougar", 2]]) expectElements(zip(animals, numbers.slice(0, 2)), [["hawk", 1], ["cougar", 2]]) }) test("handles empty", () => { expectElements(zip([], []), []) expectElements(zip(animals, []), []) expectElements(zip([], numbers), []) }) }) })