app/src/test/java/com/equwal/sbm/TsvTest.kt (5178 bytes)
1 package com.equwal.sbm 2 3 import net.jqwik.api.Arbitraries 4 import net.jqwik.api.Arbitrary 5 import net.jqwik.api.Combinators 6 import net.jqwik.api.Example 7 import net.jqwik.api.ForAll 8 import net.jqwik.api.Property 9 import net.jqwik.api.Provide 10 11 class TsvTest { 12 @Provide 13 fun bookmarks(): Arbitrary<Bookmark> { 14 val url = Arbitraries.strings().withCharRange('!', '~').ofMinLength(1).ofMaxLength(40) 15 .filter { !it.startsWith("#") } 16 val desc = Arbitraries.strings().ofMaxLength(40) 17 .filter { d -> d.none { it == '\t' || it == '\r' || it == '\n' } } 18 val tag = Arbitraries.strings().withCharRange('a', 'z').withChars('-', '0', '9') 19 .ofMinLength(1).ofMaxLength(10) 20 return Combinators.combine(url, desc, tag.list().ofMaxSize(5)).`as` { u, d, t -> Bookmark(u, d, t) } 21 } 22 23 @Provide 24 fun files(): Arbitrary<List<Bookmark>> = bookmarks().list().ofMaxSize(20) 25 26 @Property 27 fun `a written bookmark reads back the same`(@ForAll("bookmarks") b: Bookmark): Boolean = 28 Tsv.parse(Tsv.format(b) + "\n") == listOf(b) 29 30 @Property 31 fun `a written file reads back the same`(@ForAll("files") bs: List<Bookmark>): Boolean = 32 Tsv.parse(bs.joinToString("") { Tsv.format(it) + "\n" }) == bs 33 34 @Property 35 fun `a file with CRLF line ends reads the same`(@ForAll("files") bs: List<Bookmark>): Boolean { 36 val text = bs.joinToString("") { Tsv.format(it) + "\n" } 37 return Tsv.parse(text.replace("\n", "\r\n")) == Tsv.parse(text) 38 } 39 40 // Web addresses as people bookmark them: scheme, host, path. 41 @Provide 42 fun hosts(): Arbitrary<String> { 43 val label = Arbitraries.strings().withCharRange('a', 'z').withCharRange('0', '9') 44 .ofMinLength(1).ofMaxLength(10) 45 val tld = Arbitraries.strings().withCharRange('a', 'z').ofMinLength(2).ofMaxLength(4) 46 return Combinators.combine(label.list().ofMinSize(1).ofMaxSize(3), tld) 47 .`as` { labels, t -> labels.joinToString(".") + "." + t } 48 .filter { !it.startsWith("www.") } 49 } 50 51 @Provide 52 fun paths(): Arbitrary<String> = 53 Arbitraries.strings().withCharRange('a', 'z').withCharRange('A', 'Z').ofMinLength(1).ofMaxLength(6) 54 .list().ofMaxSize(3).map { segments -> segments.joinToString("") { "/$it" } } 55 56 @Property 57 fun `the scheme does not matter`(@ForAll("hosts") h: String, @ForAll("paths") p: String): Boolean = 58 Tsv.norm("http://$h$p") == Tsv.norm("https://$h$p") 59 60 @Property 61 fun `a leading www does not matter`(@ForAll("hosts") h: String, @ForAll("paths") p: String): Boolean = 62 Tsv.norm("https://www.$h$p") == Tsv.norm("https://$h$p") 63 64 @Property 65 fun `trailing slashes do not matter`(@ForAll("hosts") h: String, @ForAll("paths") p: String): Boolean = 66 Tsv.norm("https://$h$p/") == Tsv.norm("https://$h$p") && 67 Tsv.norm("https://$h$p//") == Tsv.norm("https://$h$p") 68 69 @Property 70 fun `the case of the host does not matter`(@ForAll("hosts") h: String, @ForAll("paths") p: String): Boolean = 71 Tsv.norm("https://${h.uppercase()}$p") == Tsv.norm("https://$h$p") 72 73 @Property 74 fun `the path counts`(@ForAll("hosts") h: String, @ForAll("paths") p: String): Boolean = 75 Tsv.norm("https://$h$p") == h + p 76 77 @Example 78 fun `comments, blank lines and old lines`() { 79 val text = "# notes\n\nhttps://a.example\tA\tcode sec\nhttps://b.example B site | tag\n" 80 check(Tsv.parse(text) == listOf( 81 Bookmark("https://a.example", "A", listOf("code", "sec")), 82 Bookmark("https://b.example", "B site | tag", emptyList()))) 83 } 84 85 @Example 86 fun `a line without tags`() { 87 check(Tsv.parse("https://a.example\tA\t\n") == listOf(Bookmark("https://a.example", "A", emptyList()))) 88 } 89 90 @Example 91 fun `writing cleans each field`() { 92 val b = Bookmark(" https://a.example/x \n", "one\ttwo\nthree", listOf("a b", "", "c")) 93 check(Tsv.format(b) == "https://a.example/x\tone two three\ta b c") 94 } 95 96 @Example 97 fun `the same page, as bm finds it`() { 98 val known = listOf(Bookmark("https://www.Example.com/a/", "A", emptyList())) 99 check(Tsv.find(known, "http://example.com/a") == known[0]) 100 check(Tsv.find(known, "https://example.com/b") == null) 101 } 102 103 @Example 104 fun `a new line always starts on a line of its own`() { 105 check(Tsv.appendix("", "x") == "x\n") 106 check(Tsv.appendix("a\n", "x") == "x\n") 107 check(Tsv.appendix("a", "x") == "\nx\n") 108 } 109 110 @Example 111 fun `text that is no bookmark goes where bm sends it`() { 112 check(Tsv.target("suckless.org/dwm") == "https://suckless.org/dwm") 113 check(Tsv.target("gopher://x") == "gopher://x") 114 check(Tsv.target("posix sh printf") == "https://duckduckgo.com/?q=posix+sh+printf") 115 } 116 117 @Example 118 fun `host and first address`() { 119 check(Tsv.host("https://www.a.example/x?y#z") == "www.a.example") 120 check(Tsv.host("a.example") == "a.example") 121 check(Tsv.firstUrl("Look at https://a.example/x now") == "https://a.example/x") 122 check(Tsv.firstUrl("no address") == null) 123 } 124 }