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