1 module dshould.contain; 2 3 import dshould.ShouldType; 4 import dshould.basic : not, should; 5 import std.traits : isAssociativeArray; 6 7 /** 8 * The word `.contain` takes one value, expected to appear in the range on the left hand side. 9 */ 10 public void contain(Should, T)(Should should, T expected, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 11 if (isInstanceOf!(ShouldType, Should)) 12 { 13 should.allowOnlyWords!("not", "only").before!"contain"; 14 15 should.addWord!"contain".checkContain(expected, file, line); 16 } 17 18 /// 19 unittest 20 { 21 [2, 3, 4].should.contain(3); 22 [2, 3, 4].should.not.contain(5); 23 } 24 25 public auto contain(Should)(Should should) 26 if (isInstanceOf!(ShouldType, Should)) 27 { 28 should.allowOnlyWords!("not").before!"contain"; 29 30 return should.addWord!"contain"; 31 } 32 33 /** 34 * The phrase `.contain.only` or `.only.contain` takes a range, the elements of which are expected to be the only 35 * elements appearing in the range on the left hand side. 36 */ 37 public void only(Should, T)(Should should, T expected, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 38 if (isInstanceOf!(ShouldType, Should)) 39 { 40 should.requireWord!"contain".before!"only"; 41 should.allowOnlyWords!("not", "contain").before!"only"; 42 43 should.addWord!"only".checkContain(expected, file, line); 44 } 45 46 /// 47 unittest 48 { 49 [3, 4].should.only.contain([4, 3]); 50 [3, 4].should.only.contain([1, 2, 3, 4]); 51 [3, 4].should.contain.only([4, 3]); 52 [2, 3, 4].should.not.only.contain([4, 3]); 53 } 54 55 public auto only(Should)(Should should) 56 if (isInstanceOf!(ShouldType, Should)) 57 { 58 should.allowOnlyWords!("not").before!"only"; 59 60 return should.addWord!"only"; 61 } 62 63 /** 64 * The phrase `.contain.all` takes a range, all elements of which are expected to appear 65 * in the range on the left hand side. 66 */ 67 public void all(Should, T)(Should should, T expected, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 68 if (isInstanceOf!(ShouldType, Should)) 69 { 70 should.requireWord!"contain".before!"all"; 71 should.allowOnlyWords!("not", "contain").before!"all"; 72 73 should.addWord!"all".checkContain(expected, file, line); 74 } 75 76 /// 77 unittest 78 { 79 [2, 3, 4].should.contain.all([3]); 80 [2, 3, 4].should.contain.all([4, 3]); 81 [2, 3, 4].should.not.contain.all([3, 4, 5]); 82 } 83 84 /** 85 * The phrase `.contain.any` takes a range, at least one element of which is expected to appear 86 * in the range on the left hand side. 87 */ 88 public void any(Should, T)(Should should, T expected, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 89 if (isInstanceOf!(ShouldType, Should)) 90 { 91 should.requireWord!"contain".before!"any"; 92 should.allowOnlyWords!("not", "contain").before!"any"; 93 94 should.addWord!"any".checkContain(expected, file, line); 95 } 96 97 /// 98 unittest 99 { 100 [2, 3, 4].should.contain.any([4, 5]); 101 [2, 3, 4].should.not.contain.any([5, 6]); 102 } 103 104 unittest 105 { 106 const int[] constArray = [2, 3, 4]; 107 108 constArray.should.contain(4); 109 } 110 111 unittest 112 { 113 string[string] assocArray = ["a": "b"]; 114 115 assocArray.should.contain.all(["a": "b"]); 116 assocArray.should.not.contain.any(["a": "y"]); 117 assocArray.should.not.contain.any(["x": "b"]); 118 } 119 120 /** 121 * The phrase `.contain.exactly` indicates that two ranges are expected to contain exactly 122 * the same elements, but possibly in a different order. 123 */ 124 public void exactly(Should, T)( 125 Should should, T expected, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 126 if (isInstanceOf!(ShouldType, Should)) 127 { 128 import std.algorithm : all, sort; 129 130 should.requireWord!"contain".before!"exactly"; 131 should.allowOnlyWords!"contain".before!"exactly"; 132 133 string colorCodedDelta(LHS, RHS)(LHS lhs, RHS rhs) 134 { 135 import dshould.stringcmp : colorizedDiff, green, red; 136 import std.array : array; 137 import std.conv : to; 138 import std.algorithm : map; 139 import std.format : format; 140 141 alias removePred = lines => lines.map!(line => red("- " ~ line)); 142 alias addPred = lines => lines.map!(line => green("+ " ~ line)); 143 alias keepPred = lines => lines.map!(line => " " ~ line); 144 145 return format( 146 "\n[\n%-(%s,\n%)\n]", 147 colorizedDiff!(string[], removePred, addPred, keepPred)( 148 rhs.map!(to!string).array.sort.array, lhs.map!(to!string).array.sort.array)); 149 } 150 151 with (should) 152 { 153 auto got = should.got(); 154 155 check( 156 got.all!(a => expected.canFind(a)) && expected.all!(a => got.canFind(a)), 157 "exact set of values", 158 colorCodedDelta(got, expected), 159 file, line); 160 } 161 } 162 163 /// 164 unittest 165 { 166 import dshould : equal; 167 import dshould.stringcmp : green, red; 168 import dshould.thrown : throwA; 169 170 [3, 4].should.contain.exactly([3, 4]); 171 [3, 4].should.contain.exactly([4, 3]); 172 [3, 4].should.contain.exactly([3]).should.throwA!FluentException.where.msg.should.equal( 173 "Test failed: expected exact set of values, but got \n" 174 ~ "[\n" 175 ~ " 3,\n" 176 ~ green("+ 4") ~ "\n" 177 ~ "]"); 178 [3, 4].should.contain.exactly([3, 4, 5]).should.throwA!FluentException.where.msg.should.equal( 179 "Test failed: expected exact set of values, but got \n" 180 ~ "[\n" 181 ~ " 3,\n" 182 ~ " 4,\n" 183 ~ red("- 5") ~ "\n" 184 ~ "]"); 185 [3, 4].should.contain.exactly([3, 5]).should.throwA!FluentException.where.msg.should.equal( 186 "Test failed: expected exact set of values, but got \n" 187 ~ "[\n" 188 ~ " 3,\n" 189 ~ green("+ 4") ~ ",\n" 190 ~ red("- 5") ~ "\n" 191 ~ "]"); 192 } 193 194 private void checkContain(Should, T)(Should should, T expected, string file, size_t line) 195 if (isInstanceOf!(ShouldType, Should) && isAssociativeArray!T && is(const typeof(should.got()) == const T)) 196 { 197 import std.algorithm : any, all, canFind; 198 import std.format : format; 199 200 alias pairEqual = (a, b) => a.key == b.key && a.value == b.value; 201 202 with (should) 203 { 204 auto got = should.got(); 205 alias inExpected = a => expected.byKeyValue.canFind!pairEqual(a); 206 alias inGot = a => got.byKeyValue.canFind!pairEqual(a); 207 208 static if (hasWord!"only") 209 { 210 static if (hasWord!"not") 211 { 212 check( 213 !got.byKeyValue.all!inExpected, 214 format("associative array containing pairs other than %s", expected), 215 format("%s", got), 216 file, line); 217 } 218 else 219 { 220 check( 221 got.byKeyValue.all!inExpected, 222 format("associative array containing only the pairs %s", expected), 223 format("%s", got), 224 file, line); 225 } 226 } 227 else static if (hasWord!"all") 228 { 229 static if (hasWord!"not") 230 { 231 check( 232 !expected.byKeyValue.all!inGot, 233 format("associative array not containing every pair in %s", expected), 234 format("%s", got), 235 file, line); 236 } 237 else 238 { 239 check( 240 expected.byKeyValue.all!inGot, 241 format("associative array containing every pair in %s", expected), 242 format("%s", got), 243 file, line); 244 } 245 } 246 else static if (hasWord!"any") 247 { 248 static if (hasWord!"not") 249 { 250 check( 251 !expected.byKeyValue.any!inGot, 252 format("associative array not containing any pair in %s", expected), 253 format("%s", got), 254 file, line); 255 } 256 else 257 { 258 check( 259 expected.byKeyValue.any!inGot, 260 format("associative array containing any pair of %s", expected), 261 format("%s", got), 262 file, line); 263 } 264 } 265 else 266 { 267 static assert(false, 268 `bad grammar: expected "contain all", "contain any", "contain only" (or "only contain")`); 269 } 270 } 271 } 272 273 private void checkContain(Should, T)(Should should, T expected, string file, size_t line) 274 if (isInstanceOf!(ShouldType, Should) && !isAssociativeArray!T) 275 { 276 import std.algorithm : any, all, canFind; 277 import std.format : format; 278 import std.range : ElementType, save; 279 280 with (should) 281 { 282 auto got = should.got(); 283 284 enum rhsIsValue = is(const T == const ElementType!(typeof(got))); 285 286 static if (rhsIsValue) 287 { 288 allowOnlyWords!("not", "only", "contain").before!"contain"; 289 290 static if (hasWord!"only") 291 { 292 static if (hasWord!"not") 293 { 294 check( 295 got.any!(a => a != expected), 296 format("array containing values other than %s", expected), 297 format("%s", got), 298 file, line); 299 } 300 else 301 { 302 check( 303 got.all!(a => a == expected), 304 format("array containing only the value %s", expected), 305 format("%s", got), 306 file, line); 307 } 308 } 309 else 310 { 311 static if (hasWord!"not") 312 { 313 check( 314 !got.save.canFind(expected), 315 format("array not containing %s", expected), 316 format("%s", got), 317 file, line); 318 } 319 else 320 { 321 check( 322 got.save.canFind(expected), 323 format("array containing %s", expected), 324 format("%s", got), 325 file, line); 326 } 327 } 328 } 329 else 330 { 331 static if (hasWord!"only") 332 { 333 static if (hasWord!"not") 334 { 335 check( 336 !got.all!(a => expected.save.canFind(a)), 337 format("array containing values other than %s", expected), 338 format("%s", got), 339 file, line); 340 } 341 else 342 { 343 check( 344 got.all!(a => expected.save.canFind(a)), 345 format("array containing only the values %s", expected), 346 format("%s", got), 347 file, line); 348 } 349 } 350 else static if (hasWord!"all") 351 { 352 static if (hasWord!"not") 353 { 354 check( 355 !expected.all!(a => got.save.canFind(a)), 356 format("array not containing every value in %s", expected), 357 format("%s", got), 358 file, line); 359 } 360 else 361 { 362 check( 363 expected.all!(a => got.save.canFind(a)), 364 format("array containing every value in %s", expected), 365 format("%s", got), 366 file, line); 367 } 368 } 369 else static if (hasWord!"any") 370 { 371 static if (hasWord!"not") 372 { 373 check( 374 !expected.any!(a => got.save.canFind(a)), 375 format("array not containing any value in %s", expected), 376 format("%s", got), 377 file, line); 378 } 379 else 380 { 381 check( 382 expected.any!(a => got.save.canFind(a)), 383 format("array containing any value of %s", expected), 384 format("%s", got), 385 file, line); 386 } 387 } 388 else 389 { 390 static assert(false, 391 `bad grammar: expected "contain all", "contain any", "contain only" (or "only contain")`); 392 } 393 } 394 } 395 } 396 397 unittest 398 { 399 const foo = ["foo": "bar"]; 400 401 foo.byKey.should.contain("foo"); 402 foo.byValue.should.contain("bar"); 403 }