1 /*
2  * Copyright 2024 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 @file:Suppress("UnstableApiUsage")
18 
19 package androidx.wear.protolayout.lint
20 
21 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
22 import org.junit.Test
23 import org.junit.runner.RunWith
24 import org.junit.runners.JUnit4
25 
26 @RunWith(JUnit4::class)
27 class PrimaryLayoutResponsiveDetectorTest : LintDetectorTest() {
getDetectornull28     override fun getDetector() = ResponsiveLayoutDetector()
29 
30     override fun getIssues() = mutableListOf(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
31 
32     private val deviceParametersStub =
33         java(
34                 """
35                 package androidx.wear.protolayout;
36                 public class DeviceParameters {}
37             """
38             )
39             .indented()
40 
41     private val primaryLayoutStub =
42         java(
43                 """
44                 package androidx.wear.protolayout.material.layouts;
45 
46                 import androidx.wear.protolayout.DeviceParameters;
47 
48                 public class PrimaryLayout {
49                     public static class Builder {
50                         public Builder(DeviceParameters deviceParameters) {}
51                         public Builder() {}
52 
53                         public Builder setResponsiveContentInsetEnabled(boolean enabled) {
54                             return this;
55                         }
56 
57                         public PrimaryLayout build() {
58                             return new PrimaryLayout();
59                         }
60                     }
61                 }
62             """
63             )
64             .indented()
65 
66     @Test
67     fun `primaryLayout with responsiveness doesn't report`() {
68         lint()
69             .files(
70                 deviceParametersStub,
71                 primaryLayoutStub,
72                 kotlin(
73                         """
74                         package foo
75                         import androidx.wear.protolayout.material.layouts.PrimaryLayout
76 
77                         val layout = PrimaryLayout.Builder(null)
78                                 .setResponsiveContentInsetEnabled(true)
79                                 .build()
80 
81                         class Bar {
82                          val layout = PrimaryLayout.Builder(null)
83                                     .setResponsiveContentInsetEnabled(true)
84                                     .build()
85 
86                             fun build() {
87                                 val l = PrimaryLayout.Builder(null)
88                                     .setResponsiveContentInsetEnabled(true)
89                                 return l.build()
90                             }
91 
92                             fun update() {
93                                 return PrimaryLayout.Builder()
94                                 .setResponsiveContentInsetEnabled(true)
95                             }
96 
97                             fun build2() {
98                                 update().build()
99                             }
100                         }
101                     """
102                     )
103                     .indented()
104             )
105             .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
106             .run()
107             .expectClean()
108     }
109 
110     @Test
111     fun `primaryLayout without responsiveness requires and fixes setter`() {
112         lint()
113             .files(
114                 deviceParametersStub,
115                 primaryLayoutStub,
116                 kotlin(
117                         """
118                         package foo
119                         import androidx.wear.protolayout.material.layouts.PrimaryLayout
120 
121                         val layout = PrimaryLayout.Builder(null)
122                                 .setResponsiveContentInsetEnabled(false)
123                                 .build()
124 
125                         class Bar {
126                          val layout = PrimaryLayout.Builder(null)
127                                 .build()
128 
129                          val layoutFalse = PrimaryLayout.Builder(null)
130                                     .setResponsiveContentInsetEnabled(false)
131                                 .build()
132 
133                             fun buildFalse() {
134                                 val l = PrimaryLayout.Builder(null)
135                                     .setResponsiveContentInsetEnabled(false)
136                                 return l.build()
137                             }
138 
139                             fun update() {
140                                 val enabled = false
141                                 PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled)
142                             }
143 
144                             fun build() {
145                                 update().build()
146                             }
147 
148                             fun build2() {
149                                 return PrimaryLayout.Builder().build()
150                             }
151 
152                             fun doubleFalse() {
153                                 PrimaryLayout.Builder()
154                                     .setResponsiveContentInsetEnabled(true)
155                                     .setResponsiveContentInsetEnabled(false)
156                             }
157                         }
158                     """
159                     )
160                     .indented()
161             )
162             .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
163             .run()
164             .expect(
165                 """
166                     src/foo/Bar.kt:4: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
167                     setResponsiveContentInsetEnabled(true) for the best results across
168                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
169                     val layout = PrimaryLayout.Builder(null)
170                                  ~~~~~~~~~~~~~~~~~~~~~
171                     src/foo/Bar.kt:9: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
172                     setResponsiveContentInsetEnabled(true) for the best results across
173                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
174                      val layout = PrimaryLayout.Builder(null)
175                                   ~~~~~~~~~~~~~~~~~~~~~
176                     src/foo/Bar.kt:12: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
177                     setResponsiveContentInsetEnabled(true) for the best results across
178                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
179                      val layoutFalse = PrimaryLayout.Builder(null)
180                                        ~~~~~~~~~~~~~~~~~~~~~
181                     src/foo/Bar.kt:17: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
182                     setResponsiveContentInsetEnabled(true) for the best results across
183                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
184                             val l = PrimaryLayout.Builder(null)
185                                     ~~~~~~~~~~~~~~~~~~~~~
186                     src/foo/Bar.kt:24: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
187                     setResponsiveContentInsetEnabled(true) for the best results across
188                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
189                             PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled)
190                             ~~~~~~~~~~~~~~~~~~~~~
191                     src/foo/Bar.kt:32: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
192                     setResponsiveContentInsetEnabled(true) for the best results across
193                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
194                             return PrimaryLayout.Builder().build()
195                                    ~~~~~~~~~~~~~~~~~~~~~
196                     src/foo/Bar.kt:36: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
197                     setResponsiveContentInsetEnabled(true) for the best results across
198                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
199                             PrimaryLayout.Builder()
200                             ~~~~~~~~~~~~~~~~~~~~~
201                     0 errors, 7 warnings
202                 """
203                     .trimIndent()
204             )
205             .expectFixDiffs(
206                 """
207                     Fix for src/foo/Bar.kt line 4: Call setResponsiveContentInsetEnabled(true) on layouts:
208                     @@ -5 +5
209                     -         .setResponsiveContentInsetEnabled(false)
210                     +         .setResponsiveContentInsetEnabled(true)
211                     Fix for src/foo/Bar.kt line 9: Call setResponsiveContentInsetEnabled(true) on layouts:
212                     @@ -9 +9
213                     -  val layout = PrimaryLayout.Builder(null)
214                     +  val layout = PrimaryLayout.Builder(null).setResponsiveContentInsetEnabled(true)
215                     Fix for src/foo/Bar.kt line 12: Call setResponsiveContentInsetEnabled(true) on layouts:
216                     @@ -13 +13
217                     -             .setResponsiveContentInsetEnabled(false)
218                     +             .setResponsiveContentInsetEnabled(true)
219                     Fix for src/foo/Bar.kt line 17: Call setResponsiveContentInsetEnabled(true) on layouts:
220                     @@ -18 +18
221                     -             .setResponsiveContentInsetEnabled(false)
222                     +             .setResponsiveContentInsetEnabled(true)
223                     Fix for src/foo/Bar.kt line 24: Call setResponsiveContentInsetEnabled(true) on layouts:
224                     @@ -24 +24
225                     -         PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled)
226                     +         PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true)
227                     Fix for src/foo/Bar.kt line 32: Call setResponsiveContentInsetEnabled(true) on layouts:
228                     @@ -32 +32
229                     -         return PrimaryLayout.Builder().build()
230                     +         return PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true).build()
231                     Fix for src/foo/Bar.kt line 36: Call setResponsiveContentInsetEnabled(true) on layouts:
232                     @@ -38 +38
233                     -             .setResponsiveContentInsetEnabled(false)
234                     +             .setResponsiveContentInsetEnabled(true)
235                 """
236                     .trimIndent()
237             )
238     }
239 
240     @Test
241     fun `primaryLayout with responsiveness doesn't (Java) report`() {
242         lint()
243             .files(
244                 deviceParametersStub,
245                 primaryLayoutStub,
246                 java(
247                         """
248                         package foo;
249                         import androidx.wear.protolayout.material.layouts.PrimaryLayout;
250 
251                         class Bar {
252                             PrimaryLayout layout = new PrimaryLayout.Builder(null)
253                                     .setResponsiveContentInsetEnabled(true)
254                                     .build();
255 
256                             PrimaryLayout build() {
257                                 PrimaryLayout l = new PrimaryLayout.Builder(null)
258                                     .setResponsiveContentInsetEnabled(true);
259                                 return l.build();
260                             }
261 
262                             PrimaryLayout update() {
263                                 return new PrimaryLayout.Builder()
264                                     .setResponsiveContentInsetEnabled(true);
265                             }
266 
267                             PrimaryLayout build2() {
268                                 update().build();
269                             }
270                         }
271                     """
272                     )
273                     .indented()
274             )
275             .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
276             .run()
277             .expectClean()
278     }
279 
280     @Test
primaryLayout without responsiveness requires and fixes setter (Java)null281     fun `primaryLayout without responsiveness requires and fixes setter (Java)`() {
282         lint()
283             .files(
284                 deviceParametersStub,
285                 primaryLayoutStub,
286                 java(
287                         """
288                         package foo;
289                         import androidx.wear.protolayout.material.layouts.PrimaryLayout;
290 
291                         class Bar {
292                             PrimaryLayout layout = new PrimaryLayout.Builder(null)
293                                 .build();
294 
295                             PrimaryLayout layoutFalse = new PrimaryLayout.Builder(null)
296                                     .setResponsiveContentInsetEnabled(false)
297                                 .build();
298 
299                             PrimaryLayout buildFalse() {
300                                 PrimaryLayout l = new PrimaryLayout.Builder(null)
301                                     .setResponsiveContentInsetEnabled(false);
302                                 return l.build();
303                             }
304 
305                             void update() {
306                                 boolean enabled = false;
307                                 new PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled);
308                             }
309 
310                             void build() {
311                                 update().build();
312                             }
313 
314                             PrimaryLayout build2() {
315                                 return new PrimaryLayout.Builder().build();
316                             }
317 
318                             void doubleFalse() {
319                                 new PrimaryLayout.Builder()
320                                     .setResponsiveContentInsetEnabled(true)
321                                     .setResponsiveContentInsetEnabled(false);
322                             }
323                         }
324                     """
325                     )
326                     .indented()
327             )
328             .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
329             .run()
330             .expect(
331                 """
332                     src/foo/Bar.java:5: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
333                     setResponsiveContentInsetEnabled(true) for the best results across
334                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
335                         PrimaryLayout layout = new PrimaryLayout.Builder(null)
336                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
337                     src/foo/Bar.java:8: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
338                     setResponsiveContentInsetEnabled(true) for the best results across
339                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
340                         PrimaryLayout layoutFalse = new PrimaryLayout.Builder(null)
341                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
342                     src/foo/Bar.java:13: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
343                     setResponsiveContentInsetEnabled(true) for the best results across
344                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
345                             PrimaryLayout l = new PrimaryLayout.Builder(null)
346                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
347                     src/foo/Bar.java:20: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
348                     setResponsiveContentInsetEnabled(true) for the best results across
349                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
350                             new PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled);
351                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~
352                     src/foo/Bar.java:28: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
353                     setResponsiveContentInsetEnabled(true) for the best results across
354                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
355                             return new PrimaryLayout.Builder().build();
356                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
357                     src/foo/Bar.java:32: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
358                     setResponsiveContentInsetEnabled(true) for the best results across
359                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
360                             new PrimaryLayout.Builder()
361                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~
362                     0 errors, 6 warnings
363                 """
364                     .trimIndent()
365             )
366             .expectFixDiffs(
367                 """
368                     Fix for src/foo/Bar.java line 5: Call setResponsiveContentInsetEnabled(true) on layouts:
369                     @@ -5 +5
370                     -     PrimaryLayout layout = new PrimaryLayout.Builder(null)
371                     +     PrimaryLayout layout = new PrimaryLayout.Builder(null).setResponsiveContentInsetEnabled(true)
372                     Fix for src/foo/Bar.java line 8: Call setResponsiveContentInsetEnabled(true) on layouts:
373                     @@ -9 +9
374                     -             .setResponsiveContentInsetEnabled(false)
375                     +             .setResponsiveContentInsetEnabled(true)
376                     Fix for src/foo/Bar.java line 13: Call setResponsiveContentInsetEnabled(true) on layouts:
377                     @@ -14 +14
378                     -             .setResponsiveContentInsetEnabled(false);
379                     +             .setResponsiveContentInsetEnabled(true);
380                     Fix for src/foo/Bar.java line 20: Call setResponsiveContentInsetEnabled(true) on layouts:
381                     @@ -20 +20
382                     -         new PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled);
383                     +         new PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true);
384                     Fix for src/foo/Bar.java line 28: Call setResponsiveContentInsetEnabled(true) on layouts:
385                     @@ -28 +28
386                     -         return new PrimaryLayout.Builder().build();
387                     +         return new PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true).build();
388                     Fix for src/foo/Bar.java line 32: Call setResponsiveContentInsetEnabled(true) on layouts:
389                     @@ -34 +34
390                     -             .setResponsiveContentInsetEnabled(false);
391                     +             .setResponsiveContentInsetEnabled(true);
392                 """
393                     .trimIndent()
394             )
395     }
396 
397     @Test
primaryLayout with responsiveness false positive reportsnull398     fun `primaryLayout with responsiveness false positive reports`() {
399         lint()
400             .files(
401                 deviceParametersStub,
402                 primaryLayoutStub,
403                 kotlin(
404                         """
405                         package foo
406                         import androidx.wear.protolayout.material.layouts.PrimaryLayout
407 
408                         class Bar {
409                             fun create(): PrimaryLayout.Builder {
410                               return PrimaryLayout.Builder()
411                             }
412                             fun build() {
413                               create().setResponsiveContentInsetEnabled(true).build()
414                             }
415                         }
416                     """
417                     )
418                     .indented()
419             )
420             .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
421             .run()
422             .expect(
423                 """
424                     src/foo/Bar.kt:6: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
425                     setResponsiveContentInsetEnabled(true) for the best results across
426                     different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
427                           return PrimaryLayout.Builder()
428                                  ~~~~~~~~~~~~~~~~~~~~~
429                     0 errors, 1 warnings
430                 """
431                     .trimIndent()
432             )
433             .expectFixDiffs(
434                 """
435                     Fix for src/foo/Bar.kt line 6: Call setResponsiveContentInsetEnabled(true) on layouts:
436                     @@ -6 +6
437                     -       return PrimaryLayout.Builder()
438                     +       return PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true)
439                 """
440                     .trimIndent()
441             )
442     }
443 
444     @Test
primaryLayout with responsiveness false doesn't reportnull445     fun `primaryLayout with responsiveness false doesn't report`() {
446         lint()
447             .files(
448                 deviceParametersStub,
449                 primaryLayoutStub,
450                 kotlin(
451                         """
452                         package foo
453                         import androidx.wear.protolayout.material.layouts.PrimaryLayout
454 
455                         class Bar {
456                             fun create(): PrimaryLayout.Builder {
457                               return PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true)
458                             }
459                             fun build() {
460                               create().setResponsiveContentInsetEnabled(false).build()
461                             }
462                         }
463                     """
464                     )
465                     .indented()
466             )
467             .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
468             .run()
469             .expectClean()
470     }
471 }
472