{"name":"matchers","version":"master","files":{"Matchers.mo":{"content":"/// Composable assertions\n///\n/// This module contains functions for building and combining `Matcher`s that\n/// can be used to build up assertions for testing.\n/// ```motoko\n/// import M \"mo:matchers/Matchers\";\n/// import T \"mo:matchers/Testable\";\n///\n/// assertThat(5 + 5, M.equals(T.nat(10)));\n/// assertThat(5 + 5, M.allOf<Nat>([M.greaterThan(8), M.lessThan(12)]));\n/// assertThat([1, 2], M.array([M.equals(T.nat(1)), M.equals(T.nat(2))]));\n/// ```\nimport Debug \"mo:base/Debug\";\nimport Int \"mo:base/Int\";\nimport Nat \"mo:base/Nat\";\nimport Option \"mo:base/Option\";\nimport T \"Testable\";\n\nmodule {\n /// A `Matcher` is a composable way of building up assertions for tests\n public type Matcher<A> = {\n matches : (item: A) -> Bool;\n describeMismatch : (item : A, description : Description) -> ();\n };\n\n /// Matches an `item` against a matcher and traps with an error if the\n /// matcher fails. This is primarily for experimentation and one-offs. For\n /// writing an actual test suite you probably want to use the functions in\n /// [Suite](Suite.html).\n public func assertThat<A>(item : A, matcher : Matcher<A>) {\n if (not matcher.matches(item)) {\n let description = Description();\n matcher.describeMismatch(item, description);\n Debug.print(description.toText());\n assert(false)\n }\n };\n\n /// Matches an `item` against a matcher and returns `null` in case the\n /// match succeeds and `?errorMessage` if it fails.\n public func attempt<A>(item : A, matcher : Matcher<A>) : { #success; #fail : Text } {\n if (matcher.matches(item)) {\n #success\n } else {\n let description = Description();\n matcher.describeMismatch(item, description);\n #fail(description.toText())\n }\n };\n\n /// Turns a `Matcher` for `A`s into a `Matcher` for `B`s by using `f` as an adapter.\n // TODO Maybe call this adapt?\n public func contramap<A, B>(matcher : Matcher<A>, f : B -> A) : Matcher<B> = {\n matches = func (item : B) : Bool = matcher.matches(f(item));\n describeMismatch = func (item : B, description : Description) =\n matcher.describeMismatch(f(item), description);\n };\n\n /// `Matcher`s describe match failures by inserting them into a `Description`.\n // TODO More complicated descriptions? Maybe a bit more structure than raw text?\n public class Description() {\n var message : Text = \"\";\n\n public func appendText(text : Text) {\n message := message # text;\n };\n\n public func toText() : Text = message;\n };\n\n /// Always matches, useful if you don’t care what the object under test is\n public func anything<A>() : Matcher<A> = {\n matches = func (item : A) : Bool = true;\n describeMismatch = func (item : A, description : Description) = ();\n };\n\n /// Decorator that allows adding a custom failure description\n public func describedAs<A>(msg : Text, matcher : Matcher<A>) : Matcher<A> = {\n matches = func (item : A) : Bool = matcher.matches(item);\n describeMismatch = func (item : A, description : Description) =\n description.appendText(msg);\n };\n\n /// Matches values equal to an expected `Testable`\n public func equals<A>(expected : T.TestableItem<A>) : Matcher<A> = {\n matches = func (item : A) : Bool = expected.equals(expected.item, item);\n describeMismatch = func (item : A, description : Description) =\n description.appendText(expected.display(item) # \" was expected to be \" # expected.display(expected.item));\n };\n\n /// Matches values greater than `expected`\n public func greaterThan<A <: Int>(expected : Int) : Matcher<A> = {\n matches = func (item : A) : Bool = item > expected;\n describeMismatch = func (item : A, description : Description) =\n description.appendText(Int.toText(expected) # \" was expected to be greater than \" # Int.toText(item));\n };\n\n /// Matches values greater than or equal to `expected`\n public func greaterThanOrEqual<A <: Int>(expected : Int) : Matcher<A> = {\n matches = func (item : A) : Bool = item >= expected;\n describeMismatch = func (item : A, description : Description) =\n description.appendText(Int.toText(expected) # \" was expected to be greater than or equal to \" # Int.toText(item));\n };\n\n /// Matches values less than `expected`\n public func lessThan<A <: Int>(expected : Int) : Matcher<A> = {\n matches = func (item : A) : Bool = item < expected;\n describeMismatch = func (item : A, description : Description) =\n description.appendText(Int.toText(expected) # \" was expected to be less than \" # Int.toText(item));\n };\n\n /// Matches values less than or equal to `expected`\n public func lessThanOrEqual<A <: Int>(expected : Int) : Matcher<A> = {\n matches = func (item : A) : Bool = item <= expected;\n describeMismatch = func (item : A, description : Description) =\n description.appendText(Int.toText(expected) # \" was expected to be less than or equal to \" # Int.toText(item));\n };\n\n /// Matches values for being in inclusive range `[lower .. upper]`\n public func inRange<A <: Int>(lower : Int, upper : Int) : Matcher<A> = {\n matches = func (item : A) : Bool = lower <= item and item <= upper;\n describeMismatch = func (item : A, description : Description) =\n description.appendText(Int.toText(item) # \" was expected to be in range [\" # Int.toText(lower) # \" .. \" # Int.toText(upper) # \"]\");\n };\n\n /// Matches if all matchers match, short circuits (like `and`)\n public func allOf<A>(matchers : [Matcher<A>]) : Matcher<A> = {\n matches = func (item : A) : Bool {\n for (matcher in matchers.vals()) {\n if (not matcher.matches(item)) {\n return false;\n }\n };\n return true;\n };\n describeMismatch = func (item : A, description : Description) {\n var first = true;\n for (matcher in matchers.vals()) {\n if (not matcher.matches(item)) {\n if (first) {\n first := false;\n } else {\n description.appendText(\"\\nand \");\n };\n matcher.describeMismatch(item, description);\n };\n };\n };\n };\n\n /// Matches if any matchers match, short circuits (like `or`)\n public func anyOf<A>(matchers : [Matcher<A>]) : Matcher<A> = {\n matches = func (item : A) : Bool {\n for (matcher in matchers.vals()) {\n if (matcher.matches(item)) {\n return true;\n }\n };\n return false;\n };\n describeMismatch = func (item : A, description : Description) {\n var first = true;\n for (matcher in matchers.vals()) {\n if (not matcher.matches(item)) {\n if (first) {\n first := false;\n } else {\n description.appendText(\"\\nor \");\n };\n matcher.describeMismatch(item, description);\n };\n };\n };\n };\n\n /// Matches if the wrapped matcher doesn’t match and vice versa\n public func not_<A>(matcher : Matcher<A>) : Matcher<A> = {\n matches = func (item : A) : Bool = not matcher.matches(item);\n describeMismatch = func (item : A, description : Description) {\n // Do I need to return a `Matcher<Testable<A>>` instead?\n // Would be a little unfortunate\n description.appendText(\"Shouldn't have matched.\");\n };\n };\n\n /// Test an array’s elements against an array of matchers\n public func array<A>(matchers : [Matcher<A>]) : Matcher<[A]> = {\n matches = func (items : [A]) : Bool {\n if (items.size() != matchers.size()) {\n return false;\n };\n for (ix in items.keys()) {\n if (not matchers[ix].matches(items[ix])) {\n return false\n }\n };\n return true;\n };\n describeMismatch = func (items : [A], description : Description) {\n if (items.size() != matchers.size()) {\n description.appendText(\n \"Length mismatch between \" #\n Nat.toText(items.size()) #\n \" items and \" #\n Nat.toText(matchers.size()) #\n \" matchers\"\n );\n return;\n };\n for (ix in items.keys()) {\n if (not matchers[ix].matches(items[ix])) {\n description.appendText(\"At index \" # Nat.toText(ix) # \": \");\n matchers[ix].describeMismatch(items[ix], description);\n description.appendText(\"\\n\");\n }\n };\n };\n };\n\n /// Tests that a value is not-null\n public func isSome<A>() : Matcher<?A> = {\n matches = func (item : ?A) : Bool = Option.isSome(item);\n describeMismatch = func (item : ?A, description : Description) =\n description.appendText(\"expected some value, but got `null`\");\n };\n\n /// Tests that a value is null\n public func isNull<A>() : Matcher<T.TestableItem<?A>> = {\n matches = func (testable : T.TestableItem<?A>) : Bool = Option.isNull(testable.item);\n describeMismatch = func (testable : T.TestableItem<?A>, description : Description) =\n Option.iterate<A>(testable.item, func (i) {\n description.appendText(\"expected `null`, but got \" # testable.display(?i))\n });\n };\n\n}\n"},"Canister.mo":{"content":"/// Unit testing for canisters\n///\n/// The `Tester` class in this module can be used to define unit tests for canisters.\nimport Buffer \"mo:base/Buffer\";\nimport List \"mo:base/List\";\nimport Nat \"mo:base/Nat\";\nimport M \"Matchers\";\n\nmodule {\n\npublic type Protocol = { #start : Nat; #cont : [Text]; #done : [Text] };\npublic type TestResult = { #success; #fail : Text };\n\ntype Test = (Text, () -> async TestResult);\n\n/// Instantiate one of these on canister initialization. Then you can use it to\n/// register your tests and it will take care of running them.\n///\n/// Use `runAll` for simple setups and `run` once that stops working.\n///\n/// When using `run` the `Tester` will execute your tests in the right batch\n/// sizes. It will keep calling your `test` function over and over, so make sure\n/// to not do any work outside the registered tests.\n///\n/// ```motoko\n/// import Canister \"canister:YourCanisterNameHere\";\n/// import C \"mo:matchers/Canister\";\n/// import M \"mo:matchers/Matchers\";\n/// import T \"mo:matchers/Testable\";\n///\n/// actor {\n/// let it = C.Tester({ batchSize = 8 });\n/// public shared func test() : async Text {\n///\n/// it.should(\"greet me\", func () : async C.TestResult = async {\n/// let greeting = await Canister.greet(\"Christoph\");\n/// M.attempt(greeting, M.equals(T.text(\"Hello, Christoph!\")))\n/// });\n///\n/// it.shouldFailTo(\"greet him-whose-name-shall-not-be-spoken\", func () : async () = async {\n/// let greeting = await Canister.greet(\"Voldemort\");\n/// ignore greeting\n/// });\n///\n/// await it.runAll()\n/// // await it.run()\n/// }\n/// }\n/// ```\npublic class Tester(options : { batchSize : Nat }) {\n var tests : List.List<Test> = List.nil();\n var running : Bool = false;\n\n /// Registers a test. You can use `attempt` to use a `Matcher` to\n /// produce a `TestResult`.\n public func should(name : Text, test : () -> async TestResult) {\n if(running) return;\n tests := List.push((name, test), tests);\n };\n\n /// Registers a test that should throw an exception.\n public func shouldFailTo(name : Text, test : () -> async ()) {\n if(running) return;\n tests := List.push((name, func () : async TestResult = async {\n try {\n let testResult = await test();\n #fail(\"Should've failed, but didn't\")\n } catch _ {\n #success\n }\n }), tests)\n };\n\n /// Runs all your tests in one go and returns a summary Text. If calling\n /// this runs out of gas, try using `run` with a configured `batchSize`.\n public func runAll() : async Text {\n running := true;\n var allTests = List.reverse(tests);\n var result = \"\";\n var failed = 0;\n var testCount = List.size(allTests);\n label l loop {\n switch allTests {\n case null break l;\n case (?((name, test), tl)) {\n allTests := tl;\n try {\n result #= switch (await test()) {\n case (#success) {\n \"\\\"\" # name # \"\\\"\" # \" succeeded.\\n\"\n };\n case (#fail(msg)) {\n failed += 1;\n \"\\\"\" # name # \"\\\"\" # \" failed: \" # msg # \"\\n\"\n };\n };\n } catch _ {\n failed += 1;\n result #= \"\\\"\" # name # \"\\\"\" # \"failed with an unexpected trap.\" # \"\\n\";\n }\n };\n }\n };\n\n if (failed == 0) {\n result #= \"Success! \"\n } else {\n result #= \"Failure! \"\n };\n result # Nat.toText(testCount - failed) # \"/\" # Nat.toText(testCount) # \" succeeded.\"\n };\n\n /// You must call this as the last thing in your unit test.\n public func run() : async Protocol {\n if (not running) {\n running := true;\n tests := List.reverse(tests);\n return #start(List.size(tests))\n };\n let results : Buffer.Buffer<Text> = Buffer.Buffer(options.batchSize);\n var capacity = options.batchSize;\n while(capacity > 0) {\n capacity -= 1;\n switch tests {\n case null {\n return #done(results.toArray())\n };\n case (?((name, test), tl)) {\n tests := tl;\n try {\n let testResult = await test();\n results.add(switch testResult {\n case (#success) {\n \"\\\"\" # name # \"\\\"\" # \" succeeded.\\n\"\n };\n case (#fail(msg)) {\n \"\\\"\" # name # \"\\\"\" # \" failed: \" # msg # \"\\n\"\n };\n });\n } catch _ {\n results.add(\"\\\"\" # name # \"\\\"\" # \"failed with an unexpected trap.\" # \"\\n\");\n }\n }\n }\n };\n return if(List.isNil(tests)) {\n #done(results.toArray());\n } else {\n #cont(results.toArray());\n };\n }\n}\n}\n"},"matchers/Hashmap.mo":{"content":"/// Matchers for Hashmaps\n///\n/// This module contains utility matchers that make it easier\n/// to write assertions that involve Hashmaps.\n\nimport HM \"mo:base/HashMap\";\nimport M \"../Matchers\";\nimport Option \"mo:base/Option\";\nimport T \"../Testable\";\n\nmodule {\n\n /// Tests that a HashMap contains a key\n public func hasKey<K, V>(key : T.TestableItem<K>) : M.Matcher<HM.HashMap<K, V>> = {\n matches = func (map : HM.HashMap<K, V>) : Bool = Option.isSome(map.get(key.item));\n describeMismatch = func (map : HM.HashMap<K, V>, description : M.Description) {\n description.appendText(\"Missing key \" # key.display(key.item))\n };\n };\n\n /// Tests that a HashMap matches at a given key\n public func atKey<K, V>(key : T.TestableItem<K>, matcher : M.Matcher<V>) : M.Matcher<HM.HashMap<K, V>> = {\n matches = func (map : HM.HashMap<K, V>) : Bool =\n Option.getMapped(map.get(key.item), matcher.matches, false);\n describeMismatch = func (map : HM.HashMap<K, V>, description : M.Description) {\n switch (map.get(key.item)) {\n case null {\n description.appendText(\"Missing key \" # key.display(key.item))\n };\n case (?v) {\n matcher.describeMismatch(v, description)\n };\n }\n };\n };\n}\n"},"Suite.mo":{"content":"/// A simple test runner\n///\n/// The functions in this module let you build up trees of tests, and run them.\n///\n/// ```motoko\n/// import M \"mo:matchers/Matchers\";\n/// import T \"mo:matchers/Testable\";\n/// import Suite \"mo:matchers/Suite\";\n///\n/// let suite = Suite.suite(\"My test suite\", [\n/// Suite.suite(\"Nat tests\", [\n/// Suite.test(\"10 is 10\", 10, M.equals(T.nat(10))),\n/// Suite.test(\"5 is greater than three\", 5, M.greaterThan<Nat>(3)),\n/// ])\n/// ]);\n/// Suite.run(suite);\n/// ```\n\nimport Array \"mo:base/Array\";\nimport Debug \"mo:base/Debug\";\nimport Matchers \"Matchers\";\nimport Nat \"mo:base/Nat\";\n\nmodule {\n\n type Failure = {\n names : [Text];\n error : Matchers.Description;\n };\n\n func joinWith(xs : [Text], sep : Text) : Text {\n let size = xs.size();\n\n if (size == 0) return \"\";\n if (size == 1) return xs[0];\n\n var result = xs[0];\n var i = 0;\n label l loop {\n i += 1;\n if (i >= size) { break l; };\n result #= sep # xs[i]\n };\n result\n };\n\n func displayFailure(failure : Failure) : Text =\n \"\\n\" # joinWith(failure.names, \"/\") # \" failed:\\n\" # failure.error.toText();\n\n /// A collection of tests to be run together\n // TODO: Maybe this should be a Forest rather than a Tree?\n public type Suite = {\n #node : { name : Text; children : [Suite] };\n #test : { name : Text; test: () -> ?Matchers.Description };\n };\n\n func prependPath(name : Text) : Failure -> Failure = func (failure : Failure) : Failure = {\n names = Array.append([name], failure.names);\n error = failure.error;\n };\n\n func runInner(suite : Suite) : [Failure] {\n switch suite {\n case (#node({ name; children })) {\n let childFailures = Array.flatten (Array.map(children, runInner));\n Array.map(childFailures, prependPath(name))\n };\n case (#test({ name; test })) {\n switch(test()) {\n case null {\n []\n };\n case (?err) {\n [{ names = [name]; error = err }]\n }\n }\n };\n }\n };\n\n /// Runs a given suite of tests. Will exit with a non-zero exit code in case any of the tests fail.\n public func run(suite : Suite) {\n let failures = runInner(suite);\n if (failures.size() == 0) {\n Debug.print(\"All tests passed.\");\n } else {\n for (failure in failures.vals()) {\n Debug.print(displayFailure(failure))\n };\n Debug.print(\"\\n\" # Nat.toText(failures.size()) # \" tests failed.\");\n\n // Is there a more graceful way to `exit(1)` here?\n assert(false)\n }\n };\n\n /// Constructs a test suite from a name and an Array of\n public func suite(suiteName : Text, suiteChildren : [Suite]) : Suite {\n #node({ name = suiteName; children = suiteChildren })\n };\n\n /// Constructs a single test by matching the given `item` against a `matcher`.\n public func test<A>(testName : Text, item : A, matcher : Matchers.Matcher<A>) : Suite {\n testLazy(testName, func(): A = item, matcher)\n };\n\n /// Like `test`, but accepts a thunk `mkItem` that creates the value to match against.\n /// Use this to delay the evaluation of the to be matched value until the tests actually run.\n public func testLazy<A>(testName : Text, mkItem : () -> A, matcher : Matchers.Matcher<A>) : Suite {\n #test({\n name = testName;\n test = func () : ?Matchers.Description {\n let item = mkItem();\n if (matcher.matches(item)) {\n null\n } else {\n let description = Matchers.Description();\n matcher.describeMismatch(item, description);\n ?(description)\n };\n }\n })\n };\n}\n"},"Testable.mo":{"content":"/// Things we can compare in tests\n///\n/// This module contains the `Testable<A>` abstraction, which bundles\n/// `toText` and `equals` for a type `A` so we can use them as \"expected\"\n/// values in tests.\n/// It also contains a few helpers to build `Testable`'s for compound types\n/// like Arrays and Optionals. If you want to test your own objects or control\n/// how things are printed and compared in your own tests you'll need to create\n/// your own `Testable`'s.\n/// ```motoko\n/// import T \"mo:matchers/Testable\";\n///\n/// type Person = { name : Text, surname : ?Text };\n/// // Helper\n/// let optText : Testable<(?Text)> = T.optionalTestable(T.textTestable)\n/// let testablePerson : Testable<Person> = {\n/// display = func (person : Person) : Text =\n/// person.name # \" \" #\n/// optText.display(person.surname)\n/// equals = func (person1 : Person, person2 : Person) : Bool =\n/// person1.name == person2.name and\n/// optText.equals(person1.surname, person2.surname)\n/// }\n/// ```\nimport Array \"mo:base/Array\";\nimport Bool \"mo:base/Bool\";\nimport Int \"mo:base/Int\";\nimport List \"mo:base/List\";\nimport Nat \"mo:base/Nat\";\nimport Nat8 \"mo:base/Nat8\";\nimport Nat16 \"mo:base/Nat16\";\nimport Nat32 \"mo:base/Nat32\";\nimport Nat64 \"mo:base/Nat64\";\nimport Result \"mo:base/Result\";\nimport Prim \"mo:prim\";\n\nmodule {\n /// Packs up all the functions we need to compare and display values under test\n public type Testable<A> = {\n display : A -> Text;\n equals : (A, A) -> Bool\n };\n\n /// A value combined with its `Testable`\n public type TestableItem<A> = {\n display : A -> Text;\n equals : (A, A) -> Bool;\n item : A;\n };\n\n public let textTestable : Testable<Text> = {\n // TODO Actually escape the text here\n display = func (text : Text) : Text { \"\\\"\" # text # \"\\\"\" };\n equals = func (t1 : Text, t2 : Text) : Bool { t1 == t2 }\n };\n\n public func text(t : Text) : TestableItem<Text> = {\n item = t;\n display = textTestable.display;\n equals = textTestable.equals;\n };\n\n public let natTestable : Testable<Nat> = {\n display = func (nat : Nat) : Text = Nat.toText(nat);\n equals = func (n1 : Nat, n2 : Nat) : Bool = n1 == n2\n };\n\n public func nat(n : Nat) : TestableItem<Nat> = {\n item = n;\n display = natTestable.display;\n equals = natTestable.equals;\n };\n\n public let nat8Testable : Testable<Nat8> = {\n display = func (nat : Nat8) : Text {Nat8.toText(nat)};\n equals = func (n1 : Nat8, n2 : Nat8) : Bool {n1 == n2};\n };\n\n public func nat8(n : Nat8) : TestableItem<Nat8> = {\n item = n;\n display = nat8Testable.display;\n equals = nat8Testable.equals;\n };\n\n public let nat16Testable : Testable<Nat16> = {\n display = func (nat : Nat16) : Text {Nat16.toText(nat)};\n equals = func (n1 : Nat16, n2 : Nat16) : Bool {n1 == n2};\n };\n\n public func nat16(n : Nat16) : TestableItem<Nat16> = {\n item = n;\n display = nat16Testable.display;\n equals = nat16Testable.equals;\n };\n\n public let nat32Testable : Testable<Nat32> = {\n display = func (nat : Nat32) : Text {Nat32.toText(nat)};\n equals = func (n1 : Nat32, n2 : Nat32) : Bool {n1 == n2};\n };\n\n public func nat32(n : Nat32) : TestableItem<Nat32> = {\n item = n;\n display = nat32Testable.display;\n equals = nat32Testable.equals;\n };\n\n\n public let nat64Testable : Testable<Nat64> = {\n display = func (nat : Nat64) : Text {Nat64.toText(nat)};\n equals = func (n1 : Nat64, n2 : Nat64) : Bool {n1 == n2};\n };\n\n public func nat64(n : Nat64) : TestableItem<Nat64> = {\n item = n;\n display = nat64Testable.display;\n equals = nat64Testable.equals;\n };\n\n public let intTestable : Testable<Int> = {\n display = func (n : Int) : Text = Int.toText(n);\n equals = func (n1 : Int, n2 : Int) : Bool = n1 == n2\n };\n\n public func int(n : Int) : TestableItem<Int> = {\n item = n;\n display = intTestable.display;\n equals = intTestable.equals;\n };\n\n public let boolTestable : Testable<Bool> = {\n display = func (n : Bool) : Text = Bool.toText(n);\n equals = func (n1 : Bool, n2 : Bool) : Bool = n1 == n2\n };\n\n public func bool(n : Bool) : TestableItem<Bool> = {\n item = n;\n display = boolTestable.display;\n equals = boolTestable.equals;\n };\n\n public let charTestable : Testable<Char> = {\n display = func (n : Char) : Text { \"'\" # Prim.charToText(n) # \"'\" };\n equals = func (n1 : Char, n2 : Char) : Bool { n1 == n2 };\n };\n\n public func char(n : Char) : TestableItem<Char> {\n {\n item = n;\n display = charTestable.display;\n equals = charTestable.equals;\n }\n };\n\n public func arrayTestable<A>(testableA : Testable<A>) : Testable<[A]> {\n {\n display = func (xs : [A]) : Text =\n \"[\" # joinWith(Array.map<A, Text>(xs, testableA.display), \", \") # \"]\";\n equals = func (xs1 : [A], xs2 : [A]) : Bool =\n Array.equal(xs1, xs2, testableA.equals)\n }\n };\n\n public func array<A>(testableA : Testable<A>, xs : [A]) : TestableItem<[A]> {\n let testableAs = arrayTestable(testableA);\n {\n item = xs;\n display = testableAs.display;\n equals = testableAs.equals;\n };\n };\n\n public func listTestable<A>(testableA : Testable<A>) : Testable<List.List<A>> = {\n display = func (xs : List.List<A>) : Text =\n // TODO fix leading comma\n \"[\" #\n List.foldLeft(xs, \"\", func(acc : Text, x : A) : Text =\n acc # \", \" # testableA.display(x)\n ) #\n \"]\";\n equals = func (xs1 : List.List<A>, xs2 : List.List<A>) : Bool =\n List.equal(xs1, xs2, testableA.equals)\n };\n\n public func list<A>(testableA : Testable<A>, xs : List.List<A>) : TestableItem<List.List<A>> {\n let testableAs = listTestable(testableA);\n {\n item = xs;\n display = testableAs.display;\n equals = testableAs.equals;\n };\n };\n\n public func optionalTestable<A>(testableA : Testable<A>) : Testable<?A> {\n {\n display = func (x : ?A) : Text = switch(x) {\n case null { \"null\" };\n case (?a) { \"(?\" # testableA.display(a) # \")\" };\n };\n equals = func (x1 : ?A, x2 : ?A) : Bool = switch(x1) {\n case null switch(x2) {\n case null { true };\n case _ { false };\n };\n case (?x1) switch(x2) {\n case null { false };\n case (?x2) { testableA.equals(x1, x2) };\n };\n };\n }\n };\n\n public func optional<A>(testableA : Testable<A>, x : ?A) : TestableItem<?A> {\n let testableOA = optionalTestable(testableA);\n {\n item = x;\n display = testableOA.display;\n equals = testableOA.equals;\n };\n };\n\n public func resultTestable<R, E>(\n rTestable : Testable<R>,\n eTestable : Testable<E>\n ) : Testable<Result.Result<R, E>> = {\n display = func (r : Result.Result<R, E>) : Text = switch r {\n case (#ok(ok)) {\n \"#ok(\" # rTestable.display(ok) # \")\"\n };\n case (#err(err)) {\n \"#err(\" # eTestable.display(err) # \")\"\n };\n };\n equals = func (r1 : Result.Result<R, E>, r2 : Result.Result<R, E>) : Bool = switch (r1, r2) {\n case (#ok(ok1), #ok(ok2)) {\n rTestable.equals(ok1, ok2)\n };\n case (#err(err1), #err(err2)) {\n eTestable.equals(err1, err2)\n };\n case (_) { false };\n };\n };\n\n public func result<R, E>(\n rTestable : Testable<R>,\n eTestable : Testable<E>,\n x : Result.Result<R, E>\n ) : TestableItem<Result.Result<R, E>> {\n let resTestable = resultTestable(rTestable, eTestable);\n {\n display = resTestable.display;\n equals = resTestable.equals;\n item = x;\n }\n };\n\n public func tuple2Testable<A, B>(ta : Testable<A>, tb : Testable<B>) : Testable<(A, B)> {\n {\n display = func ((a, b) : (A, B)) : Text =\n \"(\" # ta.display(a) # \", \" # tb.display(b) # \")\";\n equals = func((a1, b1) : (A, B), (a2, b2) : (A, B)) : Bool =\n ta.equals(a1, a2) and tb.equals(b1, b2);\n }\n };\n\n public func tuple2<A, B>(ta : Testable<A>, tb : Testable<B>, x : (A, B)) : TestableItem<(A, B)> {\n let testableTAB = tuple2Testable(ta, tb);\n {\n item = x;\n display = testableTAB.display;\n equals = testableTAB.equals;\n }\n };\n\n func joinWith(xs : [Text], sep : Text) : Text {\n let size = xs.size();\n\n if (size == 0) return \"\";\n if (size == 1) return xs[0];\n\n var result = xs[0];\n var i = 0;\n label l loop {\n i += 1;\n if (i >= size) { break l; };\n result #= sep # xs[i]\n };\n result\n };\n}\n"}}}