• 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.ImmutableList
22 import dagger.hilt.android.testing.compile.HiltCompilerTests
23 import dagger.internal.codegen.ComponentProcessor
24 import dagger.internal.codegen.KspComponentProcessor
25 import org.junit.Test
26 import org.junit.runner.RunWith
27 import org.junit.runners.JUnit4
28 
29 @ExperimentalProcessingApi
30 @RunWith(JUnit4::class)
31 class ViewModelValidationPluginTest {
32 
33   private fun testCompiler(vararg sources: Source): HiltCompilerTests.HiltCompiler =
34     HiltCompilerTests.hiltCompiler(ImmutableList.copyOf(sources))
35       .withAdditionalJavacProcessors(
36         ComponentProcessor.withTestPlugins(ViewModelValidationPlugin()),
37         ViewModelProcessor()
38       )
39       .withAdditionalKspProcessors(
40         KspComponentProcessor.Provider.withTestPlugins(ViewModelValidationPlugin()),
41         KspViewModelProcessor.Provider()
42       )
43 
44   private val hiltAndroidApp =
45     """
46       package test;
47 
48       import android.app.Application;
49       import dagger.hilt.android.HiltAndroidApp;
50 
51       @HiltAndroidApp(Application.class)
52       public class TestApplication extends Hilt_TestApplication {}
53       """
54       .toJFO("test.TestApplication")
55 
56   @Test
57   fun injectViewModelIsProhibited() {
58     val hiltActivity =
59       """
60       package test;
61 
62       import androidx.fragment.app.FragmentActivity;
63       import dagger.hilt.android.AndroidEntryPoint;
64       import javax.inject.Inject;
65 
66       @AndroidEntryPoint(FragmentActivity.class)
67       public class TestActivity extends Hilt_TestActivity {
68         @Inject Foo foo;
69       }
70       """
71         .toJFO("test.TestActivity")
72     val hiltViewModel =
73       """
74         package test;
75 
76         import androidx.lifecycle.ViewModel;
77         import dagger.hilt.android.lifecycle.HiltViewModel;
78         import javax.inject.Inject;
79 
80         @HiltViewModel
81         class MyViewModel extends ViewModel {
82             @Inject MyViewModel() { }
83         }
84         """
85         .toJFO("test.MyViewModel")
86     val foo =
87       """
88         package test;
89 
90         import javax.inject.Inject;
91 
92         final class Foo {
93             @Inject Foo(MyViewModel viewModel) {}
94         }
95     """
96         .toJFO("test.Foo")
97 
98     testCompiler(foo, hiltViewModel, hiltAndroidApp, hiltActivity).compile() { subject ->
99       subject.compilationDidFail()
100       subject.hasErrorCount(1)
101       subject.hasErrorContaining("Injection of an @HiltViewModel class is prohibited")
102     }
103   }
104 
105   @Test
106   fun fieldInjectedViewModelIsProhibited() {
107     val hiltActivity =
108       """
109       package test;
110 
111       import androidx.fragment.app.FragmentActivity;
112       import dagger.hilt.android.AndroidEntryPoint;
113       import javax.inject.Inject;
114 
115       @AndroidEntryPoint(FragmentActivity.class)
116       public class TestActivity extends Hilt_TestActivity {
117         @Inject MyViewModel viewModel;
118       }
119       """
120         .toJFO("test.TestActivity")
121     val hiltViewModel =
122       """
123         package test;
124 
125         import androidx.lifecycle.ViewModel;
126         import dagger.hilt.android.lifecycle.HiltViewModel;
127         import javax.inject.Inject;
128 
129         @HiltViewModel
130         class MyViewModel extends ViewModel {
131             @Inject MyViewModel() { }
132         }
133         """
134         .toJFO("test.MyViewModel")
135 
136     testCompiler(hiltViewModel, hiltAndroidApp, hiltActivity).compile() { subject ->
137       subject.compilationDidFail()
138       subject.hasErrorCount(1)
139       subject.hasErrorContaining("Injection of an @HiltViewModel class is prohibited")
140     }
141   }
142 
143   @Test
144   fun injectViewModelFromViewModelComponentIsProhibited() {
145     // Use an @HiltViewModel that injects a Foo to get the binding inside the ViewModelComponent
146     val hiltViewModel =
147       """
148         package test;
149 
150         import androidx.lifecycle.ViewModel;
151         import dagger.hilt.android.lifecycle.HiltViewModel;
152         import javax.inject.Inject;
153 
154         @HiltViewModel
155         class MyViewModel extends ViewModel {
156             @Inject MyViewModel(Foo foo) { }
157         }
158         """
159         .toJFO("test.MyViewModel")
160 
161     val foo =
162       """
163         package test;
164 
165         import javax.inject.Inject;
166         import javax.inject.Provider;
167 
168         final class Foo {
169             @Inject Foo(Provider<MyViewModel> viewModelProvider) {}
170         }
171     """
172         .toJFO("test.Foo")
173 
174     testCompiler(foo, hiltViewModel, hiltAndroidApp).compile() { subject ->
175       subject.compilationDidFail()
176       subject.hasErrorCount(1)
177       subject.hasErrorContaining("Injection of an @HiltViewModel class is prohibited")
178     }
179   }
180 
181   @Test
182   fun injectOverriddenViewModelBindingIsAllowed() {
183     val hiltActivity =
184       """
185       package test;
186 
187       import androidx.fragment.app.FragmentActivity;
188       import dagger.hilt.android.AndroidEntryPoint;
189       import javax.inject.Inject;
190 
191       @AndroidEntryPoint(FragmentActivity.class)
192       public class TestActivity extends Hilt_TestActivity {
193         @Inject Foo foo;
194       }
195       """
196         .toJFO("test.TestActivity")
197     val hiltViewModel =
198       """
199         package test;
200 
201         import androidx.lifecycle.ViewModel;
202         import dagger.hilt.android.lifecycle.HiltViewModel;
203         import javax.inject.Inject;
204 
205         @HiltViewModel
206         class MyViewModel extends ViewModel {
207             @Inject MyViewModel() { }
208         }
209         """
210         .toJFO("test.MyViewModel")
211     val foo =
212       """
213         package test;
214 
215         import javax.inject.Inject;
216 
217         final class Foo {
218             @Inject Foo(MyViewModel viewModel) {}
219         }
220     """
221         .toJFO("test.Foo")
222     val activityModule =
223       """
224         package test;
225 
226         import dagger.Module;
227         import dagger.Provides;
228         import dagger.hilt.InstallIn;
229         import dagger.hilt.android.components.ActivityComponent;
230 
231         @InstallIn(ActivityComponent.class)
232         @Module
233         public final class ActivityModule {
234           @Provides static MyViewModel provideMyViewModel() {
235             // Normally you'd expect this to use a ViewModelProvider or something but
236             // since this test is just testing the binding graph, for simplicity just return
237             // null.
238             return null;
239           }
240         }
241     """
242         .toJFO("test.ActivityModule")
243 
244     testCompiler(foo, activityModule, hiltViewModel, hiltAndroidApp, hiltActivity).compile() {
245       subject ->
246       subject.hasErrorCount(0)
247     }
248   }
249 
250   @Test
251   fun injectQualifiedViewModelBindingIsAllowed() {
252     val hiltActivity =
253       """
254       package test;
255 
256       import androidx.fragment.app.FragmentActivity;
257       import dagger.hilt.android.AndroidEntryPoint;
258       import javax.inject.Inject;
259 
260       @AndroidEntryPoint(FragmentActivity.class)
261       public class TestActivity extends Hilt_TestActivity {
262         @Inject Foo foo;
263       }
264       """
265         .toJFO("test.TestActivity")
266     val hiltViewModel =
267       """
268         package test;
269 
270         import androidx.lifecycle.ViewModel;
271         import dagger.hilt.android.lifecycle.HiltViewModel;
272         import javax.inject.Inject;
273 
274         @HiltViewModel
275         class MyViewModel extends ViewModel {
276             @Inject MyViewModel() { }
277         }
278         """
279         .toJFO("test.MyViewModel")
280     val foo =
281       """
282         package test;
283 
284         import javax.inject.Inject;
285 
286         final class Foo {
287             @Inject Foo(@ActivityModule.MyQualifier MyViewModel viewModel) {}
288         }
289     """
290         .toJFO("test.Foo")
291     val activityModule =
292       """
293         package test;
294 
295         import dagger.Module;
296         import dagger.Provides;
297         import dagger.hilt.InstallIn;
298         import dagger.hilt.android.components.ActivityComponent;
299         import javax.inject.Qualifier;
300 
301         @InstallIn(ActivityComponent.class)
302         @Module
303         public final class ActivityModule {
304           @Qualifier
305           public @interface MyQualifier {}
306 
307           @Provides
308           @MyQualifier
309           static MyViewModel provideMyViewModel() {
310             // Normally you'd expect this to use a ViewModelProvider or something but
311             // since this test is just testing the binding graph, for simplicity just return
312             // null.
313             return null;
314           }
315         }
316     """
317         .toJFO("test.ActivityModule")
318 
319     testCompiler(foo, activityModule, hiltViewModel, hiltAndroidApp, hiltActivity).compile() {
320       subject ->
321       subject.hasErrorCount(0)
322     }
323   }
324 
325   // Regression test for not handling array types properly
326   @Test
327   fun correctlyAllowsOtherBindings() {
328     val hiltActivity =
329       """
330       package test;
331 
332       import androidx.fragment.app.FragmentActivity;
333       import dagger.hilt.android.AndroidEntryPoint;
334       import javax.inject.Inject;
335 
336       @AndroidEntryPoint(FragmentActivity.class)
337       public class TestActivity extends Hilt_TestActivity {
338         @Inject Foo foo;
339       }
340       """
341         .toJFO("test.TestActivity")
342     val hiltViewModel =
343       """
344         package test;
345 
346         import androidx.lifecycle.ViewModel;
347         import dagger.hilt.android.lifecycle.HiltViewModel;
348         import javax.inject.Inject;
349 
350         @HiltViewModel
351         class MyViewModel extends ViewModel {
352             @Inject MyViewModel() { }
353         }
354         """
355         .toJFO("test.MyViewModel")
356     val foo =
357       """
358         package test;
359 
360         import javax.inject.Inject;
361 
362         final class Foo {
363             @Inject Foo(Long[] longArray) {}
364         }
365     """
366         .toJFO("test.Foo")
367     val activityModule =
368       """
369         package test;
370 
371         import dagger.Module;
372         import dagger.Provides;
373         import dagger.hilt.InstallIn;
374         import dagger.hilt.android.components.ActivityComponent;
375 
376         @InstallIn(ActivityComponent.class)
377         @Module
378         public final class ActivityModule {
379           @Provides
380           static Long[] provideLongArray() {
381             return null;
382           }
383         }
384     """
385         .toJFO("test.ActivityModule")
386 
387     testCompiler(foo, activityModule, hiltViewModel, hiltAndroidApp, hiltActivity).compile() {
388       subject ->
389       subject.hasErrorCount(0)
390     }
391   }
392 }
393