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