1 /* <lambda>null2 * Copyright (C) 2020 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.hilt.android.processor.internal.viewmodel 18 19 import androidx.room.compiler.processing.ExperimentalProcessingApi 20 import androidx.room.compiler.processing.util.Source 21 import com.google.common.collect.ImmutableMap 22 import dagger.hilt.android.testing.compile.HiltCompilerTests 23 import org.junit.Test 24 import org.junit.runner.RunWith 25 import org.junit.runners.JUnit4 26 27 @OptIn(ExperimentalProcessingApi::class) 28 @RunWith(JUnit4::class) 29 class ViewModelProcessorTest { 30 @Test 31 fun validViewModel() { 32 val myViewModel = 33 Source.java( 34 "dagger.hilt.android.test.MyViewModel", 35 """ 36 package dagger.hilt.android.test; 37 38 import androidx.lifecycle.ViewModel; 39 import dagger.hilt.android.lifecycle.HiltViewModel; 40 import javax.inject.Inject; 41 42 @HiltViewModel 43 class MyViewModel extends ViewModel { 44 @Inject MyViewModel() { } 45 } 46 """ 47 .trimIndent() 48 ) 49 HiltCompilerTests.hiltCompiler(myViewModel) 50 .withAdditionalJavacProcessors(ViewModelProcessor()) 51 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 52 .compile { subject -> subject.hasErrorCount(0) } 53 } 54 55 @Test 56 fun verifyEnclosingElementExtendsViewModel() { 57 val myViewModel = 58 Source.java( 59 "dagger.hilt.android.test.MyViewModel", 60 """ 61 package dagger.hilt.android.test; 62 63 import dagger.hilt.android.lifecycle.HiltViewModel; 64 import javax.inject.Inject; 65 66 @HiltViewModel 67 class MyViewModel { 68 @Inject 69 MyViewModel() { } 70 } 71 """ 72 .trimIndent() 73 ) 74 75 HiltCompilerTests.hiltCompiler(myViewModel) 76 .withAdditionalJavacProcessors(ViewModelProcessor()) 77 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 78 .compile { subject -> 79 subject.compilationDidFail() 80 subject.hasErrorCount(1) 81 subject.hasErrorContainingMatch( 82 "@HiltViewModel is only supported on types that subclass androidx.lifecycle.ViewModel." 83 ) 84 } 85 } 86 87 @Test 88 fun verifyNoAssistedInjectViewModels() { 89 val myViewModel = 90 Source.java( 91 "dagger.hilt.android.test.MyViewModel", 92 """ 93 package dagger.hilt.android.test; 94 95 import dagger.assisted.AssistedInject; 96 import dagger.assisted.Assisted; 97 import androidx.lifecycle.ViewModel; 98 import dagger.hilt.android.lifecycle.HiltViewModel; 99 import javax.inject.Inject; 100 101 @HiltViewModel 102 class MyViewModel extends ViewModel { 103 @AssistedInject 104 MyViewModel(String s, @Assisted int i) { } 105 } 106 """ 107 .trimIndent() 108 ) 109 110 HiltCompilerTests.hiltCompiler(myViewModel) 111 .withAdditionalJavacProcessors(ViewModelProcessor()) 112 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 113 .withProcessorOptions(ImmutableMap.of("dagger.hilt.enableAssistedInjectViewModels", "false")) 114 .compile { subject -> 115 subject.compilationDidFail() 116 subject.hasErrorCount(1) 117 subject.hasErrorContaining( 118 "ViewModel constructor should be annotated with @Inject instead of @AssistedInject." 119 ) 120 } 121 } 122 123 @Test 124 fun verifySingleAnnotatedConstructor() { 125 val myViewModel = 126 Source.java( 127 "dagger.hilt.android.test.MyViewModel", 128 """ 129 package dagger.hilt.android.test; 130 131 import androidx.lifecycle.ViewModel; 132 import dagger.hilt.android.lifecycle.HiltViewModel; 133 import javax.inject.Inject; 134 135 @HiltViewModel 136 class MyViewModel extends ViewModel { 137 @Inject 138 MyViewModel() { } 139 140 @Inject 141 MyViewModel(String s) { } 142 } 143 """ 144 .trimIndent() 145 ) 146 147 listOf(false, true).forEach { enableAssistedInjectViewModels -> 148 HiltCompilerTests.hiltCompiler(myViewModel) 149 .withAdditionalJavacProcessors(ViewModelProcessor()) 150 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 151 .withProcessorOptions( 152 ImmutableMap.of( 153 "dagger.hilt.enableAssistedInjectViewModels", 154 enableAssistedInjectViewModels.toString() 155 ) 156 ) 157 .compile { subject -> 158 subject.compilationDidFail() 159 subject.hasErrorCount(2) 160 subject.hasErrorContaining( 161 "Type dagger.hilt.android.test.MyViewModel may only contain one injected constructor. Found: [@Inject dagger.hilt.android.test.MyViewModel(), @Inject dagger.hilt.android.test.MyViewModel(String)]" 162 ) 163 subject.hasErrorContaining( 164 if (enableAssistedInjectViewModels) { 165 "@HiltViewModel annotated class should contain exactly one @Inject or @AssistedInject annotated constructor." 166 } else { 167 "@HiltViewModel annotated class should contain exactly one @Inject annotated constructor." 168 } 169 ) 170 } 171 } 172 } 173 174 @Test 175 fun verifyNonPrivateConstructor() { 176 val myViewModel = 177 Source.java( 178 "dagger.hilt.android.test.MyViewModel", 179 """ 180 package dagger.hilt.android.test; 181 182 import androidx.lifecycle.ViewModel; 183 import dagger.hilt.android.lifecycle.HiltViewModel; 184 import javax.inject.Inject; 185 186 @HiltViewModel 187 class MyViewModel extends ViewModel { 188 @Inject 189 private MyViewModel() { } 190 } 191 """ 192 .trimIndent() 193 ) 194 195 listOf(false, true).forEach { enableAssistedInjectViewModels -> 196 HiltCompilerTests.hiltCompiler(myViewModel) 197 .withAdditionalJavacProcessors(ViewModelProcessor()) 198 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 199 .withProcessorOptions( 200 ImmutableMap.of( 201 "dagger.hilt.enableAssistedInjectViewModels", 202 enableAssistedInjectViewModels.toString() 203 ) 204 ) 205 .compile { subject -> 206 subject.compilationDidFail() 207 subject.hasErrorCount(2) 208 subject.hasErrorContaining("Dagger does not support injection into private constructors") 209 subject.hasErrorContaining( 210 if (enableAssistedInjectViewModels) { 211 "@Inject or @AssistedInject annotated constructors must not be private." 212 } else { 213 "@Inject annotated constructors must not be private." 214 } 215 ) 216 } 217 } 218 } 219 220 @Test 221 fun verifyInnerClassIsStatic() { 222 val myViewModel = 223 Source.java( 224 "dagger.hilt.android.test.Outer", 225 """ 226 package dagger.hilt.android.test; 227 228 import androidx.lifecycle.ViewModel; 229 import dagger.hilt.android.lifecycle.HiltViewModel; 230 import javax.inject.Inject; 231 232 class Outer { 233 @HiltViewModel 234 class MyViewModel extends ViewModel { 235 @Inject 236 MyViewModel() { } 237 } 238 } 239 """ 240 .trimIndent() 241 ) 242 243 HiltCompilerTests.hiltCompiler(myViewModel) 244 .withAdditionalJavacProcessors(ViewModelProcessor()) 245 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 246 .compile { subject -> 247 subject.compilationDidFail() 248 subject.hasErrorCount(2) 249 subject.hasErrorContaining( 250 "@Inject constructors are invalid on inner classes. Did you mean to make the class static?" 251 ) 252 subject.hasErrorContaining( 253 "@HiltViewModel may only be used on inner classes if they are static." 254 ) 255 } 256 } 257 258 @Test 259 fun verifyNoScopeAnnotation() { 260 val myViewModel = 261 Source.java( 262 "dagger.hilt.android.test.MyViewModel", 263 """ 264 package dagger.hilt.android.test; 265 266 import androidx.lifecycle.ViewModel; 267 import dagger.hilt.android.lifecycle.HiltViewModel; 268 import javax.inject.Inject; 269 import javax.inject.Singleton; 270 271 @Singleton 272 @HiltViewModel 273 class MyViewModel extends ViewModel { 274 @Inject MyViewModel() { } 275 } 276 """ 277 .trimIndent() 278 ) 279 280 HiltCompilerTests.hiltCompiler(myViewModel) 281 .withAdditionalJavacProcessors(ViewModelProcessor()) 282 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 283 .compile { subject -> 284 subject.compilationDidFail() 285 subject.hasErrorCount(1) 286 subject.hasErrorContainingMatch( 287 "@HiltViewModel classes should not be scoped. Found: @javax.inject.Singleton" 288 ) 289 } 290 } 291 292 @Test 293 fun verifyAssistedFlagIsEnabled() { 294 val myViewModel = 295 Source.java( 296 "dagger.hilt.android.test.MyViewModel", 297 """ 298 package dagger.hilt.android.test; 299 300 import dagger.assisted.Assisted; 301 import dagger.assisted.AssistedInject; 302 import androidx.lifecycle.ViewModel; 303 import dagger.hilt.android.lifecycle.HiltViewModel; 304 305 @HiltViewModel(assistedFactory = MyFactory.class) 306 class MyViewModel extends ViewModel { 307 @AssistedInject 308 MyViewModel(String s, @Assisted int i) { } 309 } 310 """ 311 .trimIndent() 312 ) 313 val myFactory = 314 Source.java( 315 "dagger.hilt.android.test.MyFactory", 316 """ 317 package dagger.hilt.android.test; 318 import dagger.assisted.AssistedFactory; 319 @AssistedFactory 320 interface MyFactory { 321 MyViewModel create(int i); 322 } 323 """ 324 ) 325 326 HiltCompilerTests.hiltCompiler(myViewModel, myFactory) 327 .withAdditionalJavacProcessors(ViewModelProcessor()) 328 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 329 .withProcessorOptions(ImmutableMap.of("dagger.hilt.enableAssistedInjectViewModels", "false")) 330 .compile { subject -> 331 subject.compilationDidFail() 332 subject.hasErrorCount(1) 333 subject.hasErrorContaining( 334 "Specified assisted factory dagger.hilt.android.test.MyFactory for dagger.hilt.android.test.MyViewModel in @HiltViewModel but compiler option 'enableAssistedInjectViewModels' was not enabled." 335 ) 336 } 337 } 338 339 @Test 340 fun verifyAssistedFactoryHasMethod() { 341 val myViewModel = 342 Source.java( 343 "dagger.hilt.android.test.MyViewModel", 344 """ 345 package dagger.hilt.android.test; 346 347 import dagger.assisted.Assisted; 348 import dagger.assisted.AssistedInject; 349 import androidx.lifecycle.ViewModel; 350 import dagger.hilt.android.lifecycle.HiltViewModel; 351 352 @HiltViewModel(assistedFactory = MyFactory.class) 353 class MyViewModel extends ViewModel { 354 @AssistedInject 355 MyViewModel(String s, @Assisted int i) { } 356 } 357 """ 358 .trimIndent() 359 ) 360 val myFactory = 361 Source.java( 362 "dagger.hilt.android.test.MyFactory", 363 """ 364 package dagger.hilt.android.test; 365 import dagger.assisted.AssistedFactory; 366 @AssistedFactory 367 interface MyFactory {} 368 """ 369 ) 370 371 HiltCompilerTests.hiltCompiler(myViewModel, myFactory) 372 .withAdditionalJavacProcessors(ViewModelProcessor()) 373 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 374 .withProcessorOptions(ImmutableMap.of("dagger.hilt.enableAssistedInjectViewModels", "true")) 375 .compile { subject -> 376 subject.compilationDidFail() 377 subject.hasErrorCount(2) 378 subject.hasErrorContaining( 379 "The @AssistedFactory-annotated type is missing an abstract, non-default method whose return type matches the assisted injection type." 380 ) 381 subject.hasErrorContaining( 382 "Cannot find assisted factory method in dagger.hilt.android.test.MyFactory." 383 ) 384 } 385 } 386 387 @Test 388 fun verifyAssistedFactoryHasOnlyOneMethod() { 389 val myViewModel = 390 Source.java( 391 "dagger.hilt.android.test.MyViewModel", 392 """ 393 package dagger.hilt.android.test; 394 395 import dagger.assisted.Assisted; 396 import dagger.assisted.AssistedInject; 397 import androidx.lifecycle.ViewModel; 398 import dagger.hilt.android.lifecycle.HiltViewModel; 399 400 @HiltViewModel(assistedFactory = MyFactory.class) 401 class MyViewModel extends ViewModel { 402 @AssistedInject 403 MyViewModel(String s, @Assisted int i) { } 404 } 405 """ 406 .trimIndent() 407 ) 408 val myFactory = 409 Source.java( 410 "dagger.hilt.android.test.MyFactory", 411 """ 412 package dagger.hilt.android.test; 413 import dagger.assisted.AssistedFactory; 414 @AssistedFactory 415 interface MyFactory { 416 MyViewModel create(int i); 417 String createString(int i); 418 Integer createInteger(int i); 419 } 420 """ 421 ) 422 423 HiltCompilerTests.hiltCompiler(myViewModel, myFactory) 424 .withAdditionalJavacProcessors(ViewModelProcessor()) 425 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 426 .withProcessorOptions(ImmutableMap.of("dagger.hilt.enableAssistedInjectViewModels", "true")) 427 .compile { subject -> 428 subject.compilationDidFail() 429 subject.hasErrorCount(4) 430 subject.hasErrorContaining( 431 "The @AssistedFactory-annotated type should contain a single abstract, non-default method but found multiple: [dagger.hilt.android.test.MyFactory.create(int), dagger.hilt.android.test.MyFactory.createString(int), dagger.hilt.android.test.MyFactory.createInteger(int)]" 432 ) 433 subject.hasErrorContaining( 434 "Invalid return type: java.lang.String. An assisted factory's abstract method must return a type with an @AssistedInject-annotated constructor." 435 ) 436 subject.hasErrorContaining( 437 "Invalid return type: java.lang.Integer. An assisted factory's abstract method must return a type with an @AssistedInject-annotated constructor." 438 ) 439 subject.hasErrorContaining( 440 "Cannot find assisted factory method in dagger.hilt.android.test.MyFactory." 441 ) 442 } 443 } 444 445 @Test 446 fun verifyAssistedFactoryIsAnnotatedWithAssistedFactory() { 447 val myViewModel = 448 Source.java( 449 "dagger.hilt.android.test.MyViewModel", 450 """ 451 package dagger.hilt.android.test; 452 453 import dagger.assisted.Assisted; 454 import dagger.assisted.AssistedInject; 455 import androidx.lifecycle.ViewModel; 456 import dagger.hilt.android.lifecycle.HiltViewModel; 457 458 @HiltViewModel(assistedFactory = Integer.class) 459 class MyViewModel extends ViewModel { 460 @AssistedInject 461 MyViewModel(String s, @Assisted int i) { } 462 } 463 """ 464 .trimIndent() 465 ) 466 467 HiltCompilerTests.hiltCompiler(myViewModel) 468 .withAdditionalJavacProcessors(ViewModelProcessor()) 469 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 470 .withProcessorOptions(ImmutableMap.of("dagger.hilt.enableAssistedInjectViewModels", "true")) 471 .compile { subject -> 472 subject.compilationDidFail() 473 subject.hasErrorCount(1) 474 subject.hasErrorContaining( 475 "Class java.lang.Integer is not annotated with @AssistedFactory." 476 ) 477 } 478 } 479 480 @Test 481 fun verifyFactoryMethodHasCorrectReturnType() { 482 val myViewModel = 483 Source.java( 484 "dagger.hilt.android.test.MyViewModel", 485 """ 486 package dagger.hilt.android.test; 487 488 import dagger.assisted.Assisted; 489 import dagger.assisted.AssistedInject; 490 import androidx.lifecycle.ViewModel; 491 import dagger.hilt.android.lifecycle.HiltViewModel; 492 493 @HiltViewModel(assistedFactory = MyFactory.class) 494 class MyViewModel extends ViewModel { 495 @AssistedInject 496 MyViewModel(String s, @Assisted int i) { } 497 } 498 """ 499 .trimIndent() 500 ) 501 val myFactory = 502 Source.java( 503 "dagger.hilt.android.test.MyFactory", 504 """ 505 package dagger.hilt.android.test; 506 import dagger.assisted.AssistedFactory; 507 @AssistedFactory 508 interface MyFactory { 509 String create(int i); 510 } 511 """ 512 ) 513 514 HiltCompilerTests.hiltCompiler(myViewModel, myFactory) 515 .withAdditionalJavacProcessors(ViewModelProcessor()) 516 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 517 .withProcessorOptions(ImmutableMap.of("dagger.hilt.enableAssistedInjectViewModels", "true")) 518 .compile { subject -> 519 subject.compilationDidFail() 520 subject.hasErrorCount(2) 521 subject.hasErrorContaining( 522 "Invalid return type: java.lang.String. An assisted factory's abstract method must return a type with an @AssistedInject-annotated constructor." 523 ) 524 subject.hasErrorContaining( 525 "Class dagger.hilt.android.test.MyFactory must have a factory method that returns a dagger.hilt.android.test.MyViewModel. Found java.lang.String." 526 ) 527 } 528 } 529 530 @Test 531 fun verifyAssistedFactoryIsSpecified() { 532 val myViewModel = 533 Source.java( 534 "dagger.hilt.android.test.MyViewModel", 535 """ 536 package dagger.hilt.android.test; 537 538 import dagger.assisted.Assisted; 539 import dagger.assisted.AssistedInject; 540 import androidx.lifecycle.ViewModel; 541 import dagger.hilt.android.lifecycle.HiltViewModel; 542 543 @HiltViewModel 544 class MyViewModel extends ViewModel { 545 @AssistedInject 546 MyViewModel(String s, @Assisted int i) { } 547 } 548 """ 549 .trimIndent() 550 ) 551 val myFactory = 552 Source.java( 553 "dagger.hilt.android.test.MyFactory", 554 """ 555 package dagger.hilt.android.test; 556 import dagger.assisted.AssistedFactory; 557 @AssistedFactory 558 interface MyFactory { 559 MyViewModel create(int i); 560 } 561 """ 562 ) 563 564 HiltCompilerTests.hiltCompiler(myViewModel, myFactory) 565 .withAdditionalJavacProcessors(ViewModelProcessor()) 566 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 567 .withProcessorOptions(ImmutableMap.of("dagger.hilt.enableAssistedInjectViewModels", "true")) 568 .compile { subject -> 569 subject.compilationDidFail() 570 subject.hasErrorCount(1) 571 subject.hasErrorContaining( 572 "dagger.hilt.android.test.MyViewModel must have a valid assisted factory specified in @HiltViewModel when used with assisted injection. Found java.lang.Object." 573 ) 574 } 575 } 576 577 @Test 578 fun verifyConstructorHasRightInjectAnnotation() { 579 val myViewModel = 580 Source.java( 581 "dagger.hilt.android.test.MyViewModel", 582 """ 583 package dagger.hilt.android.test; 584 585 import dagger.assisted.Assisted; 586 import dagger.assisted.AssistedInject; 587 import androidx.lifecycle.ViewModel; 588 import dagger.hilt.android.lifecycle.HiltViewModel; 589 import javax.inject.Inject; 590 591 @HiltViewModel(assistedFactory = MyFactory.class) 592 class MyViewModel extends ViewModel { 593 @Inject 594 MyViewModel(String s, int i) { } 595 } 596 """ 597 .trimIndent() 598 ) 599 val myFactory = 600 Source.java( 601 "dagger.hilt.android.test.MyFactory", 602 """ 603 package dagger.hilt.android.test; 604 import dagger.assisted.AssistedFactory; 605 @AssistedFactory 606 interface MyFactory { 607 MyViewModel create(int i); 608 } 609 """ 610 ) 611 612 HiltCompilerTests.hiltCompiler(myViewModel, myFactory) 613 .withAdditionalJavacProcessors(ViewModelProcessor()) 614 .withAdditionalKspProcessors(KspViewModelProcessor.Provider()) 615 .withProcessorOptions(ImmutableMap.of("dagger.hilt.enableAssistedInjectViewModels", "true")) 616 .compile { subject -> 617 subject.compilationDidFail() 618 subject.hasErrorCount(2) 619 subject.hasErrorContaining( 620 "Invalid return type: dagger.hilt.android.test.MyViewModel. An assisted factory's abstract method must return a type with an @AssistedInject-annotated constructor." 621 ) 622 subject.hasErrorContaining( 623 "Found assisted factory dagger.hilt.android.test.MyFactory in @HiltViewModel but the constructor was annotated with @Inject instead of @AssistedInject." 624 ) 625 } 626 } 627 } 628