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