• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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