OLD | NEW |
(Empty) | |
| 1 library di.test.injector_generator_spec; |
| 2 |
| 3 import 'dart:async'; |
| 4 |
| 5 import 'package:barback/barback.dart'; |
| 6 import 'package:code_transformers/resolver.dart'; |
| 7 import 'package:code_transformers/tests.dart' as tests; |
| 8 import 'package:di/transformer/injector_generator.dart'; |
| 9 import 'package:di/transformer/options.dart'; |
| 10 |
| 11 import 'fixed-unittest.dart'; |
| 12 |
| 13 main() { |
| 14 describe('generator', () { |
| 15 var injectableAnnotations = []; |
| 16 var options = new TransformOptions( |
| 17 injectableAnnotations: injectableAnnotations, |
| 18 sdkDirectory: dartSdkDirectory); |
| 19 |
| 20 var resolvers = new Resolvers(dartSdkDirectory); |
| 21 |
| 22 var phases = [ |
| 23 [new InjectorGenerator(options, resolvers)] |
| 24 ]; |
| 25 |
| 26 it('transforms imports', () { |
| 27 return generates(phases, |
| 28 inputs: { |
| 29 'a|web/main.dart': 'import "package:a/car.dart"; main() {}', |
| 30 'a|lib/car.dart': ''' |
| 31 import 'package:inject/inject.dart'; |
| 32 import 'package:a/engine.dart'; |
| 33 import 'package:a/seat.dart' as seat; |
| 34 |
| 35 class Car { |
| 36 @inject |
| 37 Car(Engine e, seat.Seat s) {} |
| 38 } |
| 39 ''', |
| 40 'a|lib/engine.dart': CLASS_ENGINE, |
| 41 'a|lib/seat.dart': ''' |
| 42 import 'package:inject/inject.dart'; |
| 43 class Seat { |
| 44 @inject |
| 45 Seat(); |
| 46 } |
| 47 ''', |
| 48 }, |
| 49 imports: [ |
| 50 "import 'package:a/car.dart' as import_0;", |
| 51 "import 'package:a/engine.dart' as import_1;", |
| 52 "import 'package:a/seat.dart' as import_2;", |
| 53 ], |
| 54 generators: [ |
| 55 'import_0.Car: (f) => new import_0.Car(f(import_1.Engine), ' |
| 56 'f(import_2.Seat)),', |
| 57 'import_1.Engine: (f) => new import_1.Engine(),', |
| 58 'import_2.Seat: (f) => new import_2.Seat(),', |
| 59 ]); |
| 60 }); |
| 61 |
| 62 it('warns about parameterized classes', () { |
| 63 return generates(phases, |
| 64 inputs: { |
| 65 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', |
| 66 'a|lib/a.dart': ''' |
| 67 import 'package:inject/inject.dart'; |
| 68 class Parameterized<T> { |
| 69 @inject |
| 70 Parameterized(); |
| 71 } |
| 72 ''' |
| 73 }, |
| 74 imports: [ |
| 75 "import 'package:a/a.dart' as import_0;", |
| 76 ], |
| 77 generators: [ |
| 78 'import_0.Parameterized: (f) => new import_0.Parameterized(),', |
| 79 ], |
| 80 messages: [ |
| 81 'warning: Parameterized is a parameterized type. ' |
| 82 '(package:a/a.dart 1 16)', |
| 83 ]); |
| 84 }); |
| 85 |
| 86 it('skips and warns about parameterized constructor parameters', () { |
| 87 return generates(phases, |
| 88 inputs: { |
| 89 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', |
| 90 'a|lib/a.dart': ''' |
| 91 import 'package:inject/inject.dart'; |
| 92 class Foo<T> {} |
| 93 class Bar { |
| 94 @inject |
| 95 Bar(Foo<bool> f); |
| 96 } |
| 97 ''' |
| 98 }, |
| 99 messages: [ |
| 100 'warning: Bar cannot be injected because Foo<bool> is a ' |
| 101 'parameterized type. (package:a/a.dart 3 18)' |
| 102 ]); |
| 103 }); |
| 104 |
| 105 it('allows un-parameterized parameters', () { |
| 106 return generates(phases, |
| 107 inputs: { |
| 108 'a|web/main.dart': ''' |
| 109 import 'package:inject/inject.dart'; |
| 110 class Foo<T> {} |
| 111 class Bar { |
| 112 @inject |
| 113 Bar(Foo f); |
| 114 } |
| 115 main() {} |
| 116 ''' |
| 117 }, |
| 118 imports: [ |
| 119 "import 'main.dart' as import_0;", |
| 120 ], |
| 121 generators: [ |
| 122 'import_0.Bar: (f) => new import_0.Bar(f(import_0.Foo)),', |
| 123 ]); |
| 124 }); |
| 125 |
| 126 it('follows exports', () { |
| 127 return generates(phases, |
| 128 inputs: { |
| 129 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', |
| 130 'a|lib/a.dart': 'export "package:a/b.dart";', |
| 131 'a|lib/b.dart': CLASS_ENGINE |
| 132 }, |
| 133 imports: [ |
| 134 "import 'package:a/b.dart' as import_0;", |
| 135 ], |
| 136 generators: [ |
| 137 'import_0.Engine: (f) => new import_0.Engine(),', |
| 138 ]); |
| 139 }); |
| 140 |
| 141 it('handles parts', () { |
| 142 return generates(phases, |
| 143 inputs: { |
| 144 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', |
| 145 'a|lib/a.dart': |
| 146 'import "package:inject/inject.dart";\n' |
| 147 'part "b.dart";', |
| 148 'a|lib/b.dart': ''' |
| 149 part of a.a; |
| 150 class Engine { |
| 151 @inject |
| 152 Engine(); |
| 153 } |
| 154 ''' |
| 155 }, |
| 156 imports: [ |
| 157 "import 'package:a/a.dart' as import_0;", |
| 158 ], |
| 159 generators: [ |
| 160 'import_0.Engine: (f) => new import_0.Engine(),', |
| 161 ]); |
| 162 }); |
| 163 |
| 164 it('follows relative imports', () { |
| 165 return generates(phases, |
| 166 inputs: { |
| 167 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', |
| 168 'a|lib/a.dart': 'import "b.dart";', |
| 169 'a|lib/b.dart': CLASS_ENGINE |
| 170 }, |
| 171 imports: [ |
| 172 "import 'package:a/b.dart' as import_0;", |
| 173 ], |
| 174 generators: [ |
| 175 'import_0.Engine: (f) => new import_0.Engine(),', |
| 176 ]); |
| 177 }); |
| 178 |
| 179 it('handles relative imports', () { |
| 180 return generates(phases, |
| 181 inputs: { |
| 182 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', |
| 183 'a|lib/a.dart': ''' |
| 184 import "package:inject/inject.dart"; |
| 185 import 'b.dart'; |
| 186 class Car { |
| 187 @inject |
| 188 Car(Engine engine); |
| 189 } |
| 190 ''', |
| 191 'a|lib/b.dart': CLASS_ENGINE |
| 192 }, |
| 193 imports: [ |
| 194 "import 'package:a/a.dart' as import_0;", |
| 195 "import 'package:a/b.dart' as import_1;", |
| 196 ], |
| 197 generators: [ |
| 198 'import_0.Car: (f) => new import_0.Car(f(import_1.Engine)),', |
| 199 'import_1.Engine: (f) => new import_1.Engine(),', |
| 200 ]); |
| 201 }); |
| 202 |
| 203 it('handles web imports beside main', () { |
| 204 return generates(phases, |
| 205 inputs: { |
| 206 'a|web/main.dart': 'import "a.dart"; main() {}', |
| 207 'a|web/a.dart': CLASS_ENGINE |
| 208 }, |
| 209 imports: [ |
| 210 "import 'a.dart' as import_0;", |
| 211 ], |
| 212 generators: [ |
| 213 'import_0.Engine: (f) => new import_0.Engine(),', |
| 214 ]); |
| 215 }); |
| 216 |
| 217 it('handles imports in main', () { |
| 218 return generates(phases, |
| 219 inputs: { |
| 220 'a|web/main.dart': ''' |
| 221 $CLASS_ENGINE |
| 222 main() {} |
| 223 ''' |
| 224 }, |
| 225 imports: [ |
| 226 "import 'main.dart' as import_0;", |
| 227 ], |
| 228 generators: [ |
| 229 'import_0.Engine: (f) => new import_0.Engine(),', |
| 230 ]); |
| 231 }); |
| 232 |
| 233 it('skips and warns on named constructors', () { |
| 234 return generates(phases, |
| 235 inputs: { |
| 236 'a|web/main.dart': ''' |
| 237 import "package:inject/inject.dart"; |
| 238 class Engine { |
| 239 @inject |
| 240 Engine.foo(); |
| 241 } |
| 242 |
| 243 main() {} |
| 244 ''' |
| 245 }, |
| 246 messages: ['warning: Named constructors cannot be injected. ' |
| 247 '(web/main.dart 2 20)']); |
| 248 }); |
| 249 |
| 250 it('handles inject on classes', () { |
| 251 return generates(phases, |
| 252 inputs: { |
| 253 'a|web/main.dart': ''' |
| 254 import "package:inject/inject.dart"; |
| 255 @inject |
| 256 class Engine {} |
| 257 |
| 258 main() {} |
| 259 ''' |
| 260 }, |
| 261 imports: [ |
| 262 "import 'main.dart' as import_0;", |
| 263 ], |
| 264 generators: [ |
| 265 'import_0.Engine: (f) => new import_0.Engine(),', |
| 266 ]); |
| 267 }); |
| 268 |
| 269 it('skips and warns when no default constructor', () { |
| 270 return generates(phases, |
| 271 inputs: { |
| 272 'a|web/main.dart': ''' |
| 273 import "package:inject/inject.dart"; |
| 274 @inject |
| 275 class Engine { |
| 276 Engine.foo(); |
| 277 } |
| 278 main() {} |
| 279 ''' |
| 280 }, |
| 281 messages: ['warning: Engine cannot be injected because it does not ' |
| 282 'have a default constructor. (web/main.dart 1 18)']); |
| 283 }); |
| 284 |
| 285 it('skips and warns on abstract types with no factory constructor', () { |
| 286 return generates(phases, |
| 287 inputs: { |
| 288 'a|web/main.dart': ''' |
| 289 import "package:inject/inject.dart"; |
| 290 @inject |
| 291 abstract class Engine { } |
| 292 |
| 293 main() {} |
| 294 ''' |
| 295 }, |
| 296 messages: ['warning: Engine cannot be injected because it is an ' |
| 297 'abstract type with no factory constructor. ' |
| 298 '(web/main.dart 1 18)']); |
| 299 }); |
| 300 |
| 301 it('skips and warns on abstract types with implicit constructor', () { |
| 302 return generates(phases, |
| 303 inputs: { |
| 304 'a|web/main.dart': ''' |
| 305 import "package:inject/inject.dart"; |
| 306 @inject |
| 307 abstract class Engine { |
| 308 Engine(); |
| 309 } |
| 310 main() {} |
| 311 ''' |
| 312 }, |
| 313 messages: ['warning: Engine cannot be injected because it is an ' |
| 314 'abstract type with no factory constructor. ' |
| 315 '(web/main.dart 1 18)']); |
| 316 }); |
| 317 |
| 318 it('injects abstract types with factory constructors', () { |
| 319 return generates(phases, |
| 320 inputs: { |
| 321 'a|web/main.dart': ''' |
| 322 import "package:inject/inject.dart"; |
| 323 @inject |
| 324 abstract class Engine { |
| 325 factory Engine() => new ConcreteEngine(); |
| 326 } |
| 327 |
| 328 class ConcreteEngine implements Engine {} |
| 329 |
| 330 main() {} |
| 331 ''' |
| 332 }, |
| 333 imports: [ |
| 334 "import 'main.dart' as import_0;", |
| 335 ], |
| 336 generators: [ |
| 337 'import_0.Engine: (f) => new import_0.Engine(),', |
| 338 ]); |
| 339 }); |
| 340 |
| 341 it('injects this parameters', () { |
| 342 return generates(phases, |
| 343 inputs: { |
| 344 'a|web/main.dart': ''' |
| 345 import "package:inject/inject.dart"; |
| 346 class Engine { |
| 347 final Fuel fuel; |
| 348 @inject |
| 349 Engine(this.fuel); |
| 350 } |
| 351 |
| 352 class Fuel {} |
| 353 |
| 354 main() {} |
| 355 ''' |
| 356 }, |
| 357 imports: [ |
| 358 "import 'main.dart' as import_0;", |
| 359 ], |
| 360 generators: [ |
| 361 'import_0.Engine: (f) => new import_0.Engine(f(import_0.Fuel)),', |
| 362 ]); |
| 363 }); |
| 364 |
| 365 it('narrows this parameters', () { |
| 366 return generates(phases, |
| 367 inputs: { |
| 368 'a|web/main.dart': ''' |
| 369 import "package:inject/inject.dart"; |
| 370 class Engine { |
| 371 final Fuel fuel; |
| 372 @inject |
| 373 Engine(JetFuel this.fuel); |
| 374 } |
| 375 |
| 376 class Fuel {} |
| 377 class JetFuel implements Fuel {} |
| 378 |
| 379 main() {} |
| 380 ''' |
| 381 }, |
| 382 imports: [ |
| 383 "import 'main.dart' as import_0;", |
| 384 ], |
| 385 generators: [ |
| 386 'import_0.Engine: (f) => ' |
| 387 'new import_0.Engine(f(import_0.JetFuel)),', |
| 388 ]); |
| 389 }); |
| 390 |
| 391 it('skips and warns on unresolved types', () { |
| 392 return generates(phases, |
| 393 inputs: { |
| 394 'a|web/main.dart': ''' |
| 395 import "package:inject/inject.dart"; |
| 396 @inject |
| 397 class Engine { |
| 398 Engine(foo); |
| 399 } |
| 400 |
| 401 @inject |
| 402 class Car { |
| 403 var foo; |
| 404 Car(this.foo); |
| 405 } |
| 406 |
| 407 main() {} |
| 408 ''' |
| 409 }, |
| 410 messages: ['warning: Engine cannot be injected because parameter ' |
| 411 'type foo cannot be resolved. (web/main.dart 3 20)', |
| 412 'warning: Car cannot be injected because parameter type ' |
| 413 'foo cannot be resolved. (web/main.dart 9 20)']); |
| 414 }); |
| 415 |
| 416 it('supports custom annotations', () { |
| 417 injectableAnnotations.add('angular.NgInjectableService'); |
| 418 return generates(phases, |
| 419 inputs: { |
| 420 'angular|lib/angular.dart': PACKAGE_ANGULAR, |
| 421 'a|web/main.dart': ''' |
| 422 import 'package:angular/angular.dart'; |
| 423 @NgInjectableService() |
| 424 class Engine { |
| 425 Engine(); |
| 426 } |
| 427 |
| 428 class Car { |
| 429 @NgInjectableService() |
| 430 Car(); |
| 431 } |
| 432 |
| 433 main() {} |
| 434 ''' |
| 435 }, |
| 436 imports: [ |
| 437 "import 'main.dart' as import_0;", |
| 438 ], |
| 439 generators: [ |
| 440 'import_0.Engine: (f) => new import_0.Engine(),', |
| 441 'import_0.Car: (f) => new import_0.Car(),', |
| 442 ]).whenComplete(() { |
| 443 injectableAnnotations.clear(); |
| 444 }); |
| 445 }); |
| 446 |
| 447 it('supports default formal parameters', () { |
| 448 return generates(phases, |
| 449 inputs: { |
| 450 'a|web/main.dart': ''' |
| 451 import "package:inject/inject.dart"; |
| 452 class Engine { |
| 453 final Car car; |
| 454 |
| 455 @inject |
| 456 Engine([Car this.car]); |
| 457 } |
| 458 |
| 459 class Car { |
| 460 @inject |
| 461 Car(); |
| 462 } |
| 463 |
| 464 main() {} |
| 465 ''' |
| 466 }, |
| 467 imports: [ |
| 468 "import 'main.dart' as import_0;", |
| 469 ], |
| 470 generators: [ |
| 471 'import_0.Engine: (f) => new import_0.Engine(f(import_0.Car)),', |
| 472 'import_0.Car: (f) => new import_0.Car(),', |
| 473 ]); |
| 474 }); |
| 475 |
| 476 it('supports injectableTypes argument', () { |
| 477 return generates(phases, |
| 478 inputs: { |
| 479 'di|lib/annotations.dart': PACKAGE_DI, |
| 480 'a|web/main.dart': ''' |
| 481 @Injectables(const[Engine]) |
| 482 library a; |
| 483 |
| 484 import 'package:di/annotations.dart'; |
| 485 |
| 486 class Engine { |
| 487 Engine(); |
| 488 } |
| 489 |
| 490 main() {} |
| 491 ''' |
| 492 }, |
| 493 imports: [ |
| 494 "import 'main.dart' as import_0;", |
| 495 ], |
| 496 generators: [ |
| 497 'import_0.Engine: (f) => new import_0.Engine(),', |
| 498 ]); |
| 499 }); |
| 500 |
| 501 it('does not generate dart:core imports', () { |
| 502 return generates(phases, |
| 503 inputs: { |
| 504 'a|web/main.dart': ''' |
| 505 import 'package:inject/inject.dart'; |
| 506 |
| 507 class Engine { |
| 508 @inject |
| 509 Engine(int i); |
| 510 } |
| 511 main() {} |
| 512 ''' |
| 513 }, |
| 514 imports: [ |
| 515 "import 'main.dart' as import_0;", |
| 516 ], |
| 517 generators: [ |
| 518 'import_0.Engine: (f) => new import_0.Engine(f(int)),', |
| 519 ]); |
| 520 }); |
| 521 |
| 522 it('warns on private types', () { |
| 523 return generates(phases, |
| 524 inputs: { |
| 525 'a|web/main.dart': ''' |
| 526 import "package:inject/inject.dart"; |
| 527 @inject |
| 528 class _Engine { |
| 529 _Engine(); |
| 530 } |
| 531 |
| 532 main() {} |
| 533 ''' |
| 534 }, |
| 535 messages: ['warning: _Engine cannot be injected because it is a ' |
| 536 'private type. (web/main.dart 1 18)']); |
| 537 }); |
| 538 |
| 539 it('warns on multiple constructors', () { |
| 540 return generates(phases, |
| 541 inputs: { |
| 542 'a|web/main.dart': ''' |
| 543 import "package:inject/inject.dart"; |
| 544 |
| 545 @inject |
| 546 class Engine { |
| 547 Engine(); |
| 548 |
| 549 @inject |
| 550 Engine.foo(); |
| 551 } |
| 552 |
| 553 main() {} |
| 554 ''' |
| 555 }, |
| 556 messages: ['warning: Engine has more than one constructor ' |
| 557 'annotated for injection. (web/main.dart 2 18)']); |
| 558 }); |
| 559 |
| 560 it('transforms main', () { |
| 561 return tests.applyTransformers(phases, |
| 562 inputs: { |
| 563 'a|web/main.dart': ''' |
| 564 library main; |
| 565 import 'package:di/auto_injector.dart'; |
| 566 import 'package:di/auto_injector.dart' as ai; |
| 567 |
| 568 main() { |
| 569 var module = defaultInjector(modules: null, name: 'foo'); |
| 570 print(module); |
| 571 |
| 572 var module2 = ai.defaultInjector(modules: null, name: 'foo'); |
| 573 print(module2); |
| 574 }''', |
| 575 'di|lib/auto_injector.dart': PACKAGE_AUTO |
| 576 }, |
| 577 results: { |
| 578 'a|web/main.dart': ''' |
| 579 library main; |
| 580 import 'main_static_injector.dart' as generated_static_injector; |
| 581 import 'package:di/auto_injector.dart'; |
| 582 import 'package:di/auto_injector.dart' as ai; |
| 583 |
| 584 main() { |
| 585 var module = generated_static_injector.createStaticInjector(modules: null, nam
e: 'foo'); |
| 586 print(module); |
| 587 |
| 588 var module2 = generated_static_injector.createStaticInjector(modules: null, na
me: 'foo'); |
| 589 print(module2); |
| 590 }''' |
| 591 |
| 592 }); |
| 593 }); |
| 594 |
| 595 it('handles annotated dependencies', () { |
| 596 return generates(phases, |
| 597 inputs: { |
| 598 'a|web/main.dart': ''' |
| 599 import "package:inject/inject.dart"; |
| 600 |
| 601 class Turbo { |
| 602 const Turbo(); |
| 603 } |
| 604 |
| 605 @inject |
| 606 class Engine {} |
| 607 |
| 608 @inject |
| 609 class Car { |
| 610 Car(@Turbo() Engine engine); |
| 611 } |
| 612 |
| 613 main() {} |
| 614 ''' |
| 615 }, |
| 616 imports: [ |
| 617 "import 'main.dart' as import_0;", |
| 618 ], |
| 619 generators: [ |
| 620 'import_0.Engine: (f) => new import_0.Engine(),', |
| 621 'import_0.Car: (f) => new import_0.Car(f(import_0.Engine, import_0
.Turbo)),', |
| 622 ]); |
| 623 }); |
| 624 }); |
| 625 } |
| 626 |
| 627 Future generates(List<List<Transformer>> phases, |
| 628 {Map<String, String> inputs, Iterable<String> imports: const [], |
| 629 Iterable<String> generators: const [], |
| 630 Iterable<String> messages: const []}) { |
| 631 |
| 632 inputs['inject|lib/inject.dart'] = PACKAGE_INJECT; |
| 633 |
| 634 imports = imports.map((i) => '$i\n'); |
| 635 generators = generators.map((t) => ' $t\n'); |
| 636 |
| 637 return tests.applyTransformers(phases, |
| 638 inputs: inputs, |
| 639 results: { |
| 640 'a|web/main_static_injector.dart': ''' |
| 641 $IMPORTS |
| 642 ${imports.join('')}$BOILER_PLATE |
| 643 ${generators.join('')}$FOOTER |
| 644 ''', |
| 645 }, |
| 646 messages: messages); |
| 647 } |
| 648 |
| 649 const String IMPORTS = ''' |
| 650 library a.web.main.generated_static_injector; |
| 651 |
| 652 import 'package:di/di.dart'; |
| 653 import 'package:di/static_injector.dart'; |
| 654 '''; |
| 655 |
| 656 const String BOILER_PLATE = ''' |
| 657 Injector createStaticInjector({List<Module> modules, String name, |
| 658 bool allowImplicitInjection: false}) => |
| 659 new StaticInjector(modules: modules, name: name, |
| 660 allowImplicitInjection: allowImplicitInjection, |
| 661 typeFactories: factories); |
| 662 |
| 663 final Map<Type, TypeFactory> factories = <Type, TypeFactory>{'''; |
| 664 |
| 665 const String FOOTER = ''' |
| 666 };'''; |
| 667 |
| 668 const String CLASS_ENGINE = ''' |
| 669 import 'package:inject/inject.dart'; |
| 670 class Engine { |
| 671 @inject |
| 672 Engine(); |
| 673 }'''; |
| 674 |
| 675 const String PACKAGE_ANGULAR = ''' |
| 676 library angular; |
| 677 |
| 678 class NgInjectableService { |
| 679 const NgInjectableService(); |
| 680 } |
| 681 '''; |
| 682 |
| 683 const String PACKAGE_INJECT = ''' |
| 684 library inject; |
| 685 |
| 686 class InjectAnnotation { |
| 687 const InjectAnnotation._(); |
| 688 } |
| 689 const inject = const InjectAnnotation._(); |
| 690 '''; |
| 691 |
| 692 const String PACKAGE_DI = ''' |
| 693 library di.annotations; |
| 694 |
| 695 class Injectables { |
| 696 final List<Type> types; |
| 697 const Injectables(this.types); |
| 698 } |
| 699 '''; |
| 700 |
| 701 const String PACKAGE_AUTO = ''' |
| 702 library di.auto_injector; |
| 703 |
| 704 defaultInjector({List modules, String name, |
| 705 bool allowImplicitInjection: false}) => null; |
| 706 '''; |
OLD | NEW |