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