1 /* 2 * Copyright (C) 2023 The Android Open Source Project 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 18 package com.android.internal.systemui.lint 19 20 import com.android.tools.lint.checks.infrastructure.TestFiles 21 import com.android.tools.lint.checks.infrastructure.TestMode 22 import com.android.tools.lint.detector.api.Detector 23 import com.android.tools.lint.detector.api.Issue 24 import org.junit.Test 25 26 class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() { getDetectornull27 override fun getDetector(): Detector { 28 return CleanArchitectureDependencyViolationDetector() 29 } 30 getIssuesnull31 override fun getIssues(): List<Issue> { 32 return listOf( 33 CleanArchitectureDependencyViolationDetector.ISSUE, 34 ) 35 } 36 37 @Test noViolationsnull38 fun noViolations() { 39 lint() 40 .files( 41 *LEGITIMATE_FILES, 42 ) 43 .issues( 44 CleanArchitectureDependencyViolationDetector.ISSUE, 45 ) 46 .run() 47 .expectWarningCount(0) 48 } 49 50 @Test violation_domainDependsOnUinull51 fun violation_domainDependsOnUi() { 52 lint() 53 .files( 54 *LEGITIMATE_FILES, 55 TestFiles.kotlin( 56 """ 57 package test.domain.interactor 58 59 import test.ui.viewmodel.ViewModel 60 61 class BadClass( 62 private val viewModel: ViewModel, 63 ) 64 """ 65 .trimIndent() 66 ) 67 ) 68 .issues( 69 CleanArchitectureDependencyViolationDetector.ISSUE, 70 ) 71 .testModes(TestMode.DEFAULT) 72 .run() 73 .expectWarningCount(1) 74 .expect( 75 expectedText = 76 """ 77 src/test/domain/interactor/BadClass.kt:3: Warning: The domain layer may not depend on the ui layer. [CleanArchitectureDependencyViolation] 78 import test.ui.viewmodel.ViewModel 79 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 80 0 errors, 1 warnings 81 """, 82 ) 83 } 84 85 @Test violation_uiDependsOnDatanull86 fun violation_uiDependsOnData() { 87 lint() 88 .files( 89 *LEGITIMATE_FILES, 90 TestFiles.kotlin( 91 """ 92 package test.ui.viewmodel 93 94 import test.data.repository.Repository 95 96 class BadClass( 97 private val repository: Repository, 98 ) 99 """ 100 .trimIndent() 101 ) 102 ) 103 .issues( 104 CleanArchitectureDependencyViolationDetector.ISSUE, 105 ) 106 .testModes(TestMode.DEFAULT) 107 .run() 108 .expectWarningCount(1) 109 .expect( 110 expectedText = 111 """ 112 src/test/ui/viewmodel/BadClass.kt:3: Warning: The ui layer may not depend on the data layer. [CleanArchitectureDependencyViolation] 113 import test.data.repository.Repository 114 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 115 0 errors, 1 warnings 116 """, 117 ) 118 } 119 120 @Test violation_sharedDependsOnAllOtherLayersnull121 fun violation_sharedDependsOnAllOtherLayers() { 122 lint() 123 .files( 124 *LEGITIMATE_FILES, 125 TestFiles.kotlin( 126 """ 127 package test.shared.model 128 129 import test.data.repository.Repository 130 import test.domain.interactor.Interactor 131 import test.ui.viewmodel.ViewModel 132 133 class BadClass( 134 private val repository: Repository, 135 private val interactor: Interactor, 136 private val viewmodel: ViewModel, 137 ) 138 """ 139 .trimIndent() 140 ) 141 ) 142 .issues( 143 CleanArchitectureDependencyViolationDetector.ISSUE, 144 ) 145 .testModes(TestMode.DEFAULT) 146 .run() 147 .expectWarningCount(3) 148 .expect( 149 expectedText = 150 """ 151 src/test/shared/model/BadClass.kt:3: Warning: The shared layer may not depend on the data layer. [CleanArchitectureDependencyViolation] 152 import test.data.repository.Repository 153 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 154 src/test/shared/model/BadClass.kt:4: Warning: The shared layer may not depend on the domain layer. [CleanArchitectureDependencyViolation] 155 import test.domain.interactor.Interactor 156 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 157 src/test/shared/model/BadClass.kt:5: Warning: The shared layer may not depend on the ui layer. [CleanArchitectureDependencyViolation] 158 import test.ui.viewmodel.ViewModel 159 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 160 0 errors, 3 warnings 161 """, 162 ) 163 } 164 165 @Test violation_dataDependsOnDomainnull166 fun violation_dataDependsOnDomain() { 167 lint() 168 .files( 169 *LEGITIMATE_FILES, 170 TestFiles.kotlin( 171 """ 172 package test.data.repository 173 174 import test.domain.interactor.Interactor 175 176 class BadClass( 177 private val interactor: Interactor, 178 ) 179 """ 180 .trimIndent() 181 ) 182 ) 183 .issues( 184 CleanArchitectureDependencyViolationDetector.ISSUE, 185 ) 186 .testModes(TestMode.DEFAULT) 187 .run() 188 .expectWarningCount(1) 189 .expect( 190 expectedText = 191 """ 192 src/test/data/repository/BadClass.kt:3: Warning: The data layer may not depend on the domain layer. [CleanArchitectureDependencyViolation] 193 import test.domain.interactor.Interactor 194 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 195 0 errors, 1 warnings 196 """, 197 ) 198 } 199 200 companion object { 201 private val MODEL_FILE = 202 TestFiles.kotlin( 203 """ 204 package test.shared.model 205 206 import test.some.other.thing.SomeOtherThing 207 208 data class Model( 209 private val name: String, 210 ) 211 """ 212 .trimIndent() 213 ) 214 private val REPOSITORY_FILE = 215 TestFiles.kotlin( 216 """ 217 package test.data.repository 218 219 import test.shared.model.Model 220 import test.some.other.thing.SomeOtherThing 221 222 class Repository { 223 private val models = listOf( 224 Model("one"), 225 Model("two"), 226 Model("three"), 227 ) 228 getModelsnull229 fun getModels(): List<Model> { 230 return models 231 } 232 } 233 """ 234 .trimIndent() 235 ) 236 private val INTERACTOR_FILE = 237 TestFiles.kotlin( 238 """ 239 package test.domain.interactor 240 241 import test.data.repository.Repository 242 import test.shared.model.Model 243 244 class Interactor( 245 private val repository: Repository, 246 ) { 247 fun getModels(): List<Model> { 248 return repository.getModels() 249 } 250 } 251 """ 252 .trimIndent() 253 ) 254 private val VIEW_MODEL_FILE = 255 TestFiles.kotlin( 256 """ 257 package test.ui.viewmodel 258 259 import test.domain.interactor.Interactor 260 import test.some.other.thing.SomeOtherThing 261 262 class ViewModel( 263 private val interactor: Interactor, 264 ) { 265 fun getNames(): List<String> { 266 return interactor.getModels().map { model -> model.name } 267 } 268 } 269 """ 270 .trimIndent() 271 ) 272 private val NON_CLEAN_ARCHITECTURE_FILE = 273 TestFiles.kotlin( 274 """ 275 package test.some.other.thing 276 277 import test.data.repository.Repository 278 import test.domain.interactor.Interactor 279 import test.ui.viewmodel.ViewModel 280 281 class SomeOtherThing { 282 init { 283 val viewModel = ViewModel( 284 interactor = Interactor( 285 repository = Repository(), 286 ), 287 ) 288 } 289 } 290 """ 291 .trimIndent() 292 ) 293 private val LEGITIMATE_FILES = 294 arrayOf( 295 MODEL_FILE, 296 REPOSITORY_FILE, 297 INTERACTOR_FILE, 298 VIEW_MODEL_FILE, 299 NON_CLEAN_ARCHITECTURE_FILE, 300 ) 301 } 302 } 303