View Javadoc
1   /*
2    * SPDX-License-Identifier: LGPL-2.1-or-later
3    * See LICENSE file for details.
4    *
5    * Copyright 2012-2026 Hazendaz.
6    */
7   package net.alchim31.maven.yuicompressor;
8   
9   import java.nio.file.Path;
10  
11  import org.apache.maven.plugin.logging.Log;
12  import org.codehaus.plexus.build.BuildContext;
13  import org.junit.jupiter.api.Assertions;
14  import org.junit.jupiter.api.Test;
15  import org.junit.jupiter.api.extension.ExtendWith;
16  import org.mockito.ArgumentCaptor;
17  import org.mockito.Mock;
18  import org.mockito.Mockito;
19  import org.mockito.junit.jupiter.MockitoExtension;
20  import org.mozilla.javascript.EvaluatorException;
21  
22  /**
23   * Tests for {@link ErrorReporter4Mojo}.
24   */
25  @ExtendWith(MockitoExtension.class)
26  class ErrorReporter4MojoTest {
27  
28      /** Maven plugin log (mocked). */
29      @Mock
30      private Log log;
31  
32      /** Plexus build context (mocked). */
33      @Mock
34      private BuildContext buildContext;
35  
36      // ------------------------------------------------------------------ error
37  
38      /**
39       * Test that {@code error()} increments the error counter.
40       */
41      @Test
42      void testError_incrementsErrorCount() {
43          final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
44          reporter.error("msg", "src.js", 1, "lineSource", 0);
45          Assertions.assertEquals(1, reporter.getErrorCnt());
46      }
47  
48      /**
49       * Test that {@code error()} delegates to the Maven log.
50       */
51      @Test
52      void testError_logsToMavenLog() {
53          final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
54          reporter.error("msg", "src.js", 2, "lineSource", 5);
55          Mockito.verify(log).error(Mockito.anyString());
56      }
57  
58      /**
59       * Test that {@code error()} notifies the build context with SEVERITY_ERROR.
60       */
61      @Test
62      void testError_addsBuildContextMessage() {
63          final var file = Path.of("src.js").toFile();
64          final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
65          reporter.setFile(file);
66          reporter.error("msg", "src.js", 3, "lineSource", 1);
67          Mockito.verify(buildContext).addMessage(file, 3, 1, "msg", BuildContext.SEVERITY_ERROR, null);
68      }
69  
70      /**
71       * Test that multiple {@code error()} calls accumulate the error count.
72       */
73      @Test
74      void testError_multipleCallsAccumulateCount() {
75          final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
76          reporter.error("e1", null, 1, null, 0);
77          reporter.error("e2", null, 2, null, 0);
78          reporter.error("e3", null, 3, null, 0);
79          Assertions.assertEquals(3, reporter.getErrorCnt());
80      }
81  
82      // --------------------------------------------------------------- warning
83  
84      /**
85       * Test that {@code warning()} increments the warning counter when warnings are accepted.
86       */
87      @Test
88      void testWarning_whenAcceptWarnTrue_incrementsWarningCount() {
89          final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
90          reporter.warning("warn", "src.js", 1, "lineSource", 0);
91          Assertions.assertEquals(1, reporter.getWarningCnt());
92      }
93  
94      /**
95       * Test that {@code warning()} notifies the log when warnings are accepted.
96       */
97      @Test
98      void testWarning_whenAcceptWarnTrue_logsWarning() {
99          final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
100         reporter.warning("warn", "src.js", 1, "lineSource", 0);
101         Mockito.verify(log).warn(Mockito.anyString());
102     }
103 
104     /**
105      * Test that {@code warning()} notifies the build context when warnings are accepted.
106      */
107     @Test
108     void testWarning_whenAcceptWarnTrue_addsBuildContextMessage() {
109         final var file = Path.of("src.js").toFile();
110         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
111         reporter.setFile(file);
112         reporter.warning("warn", "src.js", 7, "lineSource", 2);
113         Mockito.verify(buildContext).addMessage(file, 7, 2, "warn", BuildContext.SEVERITY_WARNING, null);
114     }
115 
116     /**
117      * Test that {@code warning()} does NOT increment the counter when warnings are suppressed.
118      */
119     @Test
120     void testWarning_whenAcceptWarnFalse_doesNotIncrementCount() {
121         final var reporter = new ErrorReporter4Mojo(log, false, buildContext);
122         reporter.warning("warn", "src.js", 1, "lineSource", 0);
123         Assertions.assertEquals(0, reporter.getWarningCnt());
124     }
125 
126     /**
127      * Test that {@code warning()} does NOT log anything when warnings are suppressed.
128      */
129     @Test
130     void testWarning_whenAcceptWarnFalse_doesNotLog() {
131         final var reporter = new ErrorReporter4Mojo(log, false, buildContext);
132         reporter.warning("warn", "src.js", 1, "lineSource", 0);
133         Mockito.verify(log, Mockito.never()).warn(Mockito.anyString());
134     }
135 
136     /**
137      * Test that {@code warning()} does NOT notify the build context when warnings are suppressed.
138      */
139     @Test
140     void testWarning_whenAcceptWarnFalse_doesNotNotifyBuildContext() {
141         final var reporter = new ErrorReporter4Mojo(log, false, buildContext);
142         reporter.warning("warn", "src.js", 1, "lineSource", 0);
143         Mockito.verify(buildContext, Mockito.never()).addMessage(Mockito.any(), Mockito.anyInt(), Mockito.anyInt(),
144                 Mockito.anyString(), Mockito.anyInt(), Mockito.any());
145     }
146 
147     // ---------------------------------------------------------- runtimeError
148 
149     /**
150      * Test that {@code runtimeError()} throws an {@link EvaluatorException}.
151      */
152     @Test
153     void testRuntimeError_throwsEvaluatorException() {
154         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
155         Assertions.assertThrows(EvaluatorException.class,
156                 () -> reporter.runtimeError("runtime", "src.js", 1, "lineSource", 0));
157     }
158 
159     /**
160      * Test that {@code runtimeError()} also increments the error counter before throwing.
161      */
162     @Test
163     void testRuntimeError_incrementsErrorCount() {
164         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
165         try {
166             reporter.runtimeError("runtime", "src.js", 1, "lineSource", 0);
167         } catch (EvaluatorException ignored) {
168             // expected
169         }
170         Assertions.assertEquals(1, reporter.getErrorCnt());
171     }
172 
173     // ---------------------------------------------------- setDefaultFileName
174 
175     /**
176      * Test that {@code setDefaultFileName()} retains a non-empty name.
177      */
178     @Test
179     void testSetDefaultFileName_nonEmpty_usedInMessage() {
180         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
181         reporter.setDefaultFileName("myFile.js");
182         // trigger an error with null sourceName so the default is used
183         final var captor = ArgumentCaptor.forClass(String.class);
184         reporter.error("oops", null, 1, null, 0);
185         Mockito.verify(log).error(captor.capture());
186         Assertions.assertTrue(captor.getValue().contains("myFile.js"),
187                 "Expected default filename to appear in the error message");
188     }
189 
190     /**
191      * Test that {@code setDefaultFileName("")} effectively clears the filename.
192      */
193     @Test
194     void testSetDefaultFileName_empty_clearsDefaultName() {
195         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
196         reporter.setDefaultFileName("myFile.js");
197         reporter.setDefaultFileName(""); // empty should set to null
198         final var captor = ArgumentCaptor.forClass(String.class);
199         reporter.error("oops", null, 1, null, 0);
200         Mockito.verify(log).error(captor.capture());
201         // With null default filename the message contains only the error text
202         Assertions.assertFalse(captor.getValue().contains("myFile.js"),
203                 "Expected cleared filename to not appear in the error message");
204     }
205 
206     // -------------------------------------------------------- message format
207 
208     /**
209      * Test that the formatted message includes sourceName, line, column and message text.
210      */
211     @Test
212     void testMessageFormat_includesSourceLineColumn() {
213         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
214         final var captor = ArgumentCaptor.forClass(String.class);
215         reporter.error("bad syntax", "app.js", 10, "var x =", 4);
216         Mockito.verify(log).error(captor.capture());
217         final var msg = captor.getValue();
218         Assertions.assertTrue(msg.contains("app.js"), "Expected source name in message");
219         Assertions.assertTrue(msg.contains("10"), "Expected line number in message");
220         Assertions.assertTrue(msg.contains("4"), "Expected column offset in message");
221         Assertions.assertTrue(msg.contains("bad syntax"), "Expected error text in message");
222     }
223 
224     /**
225      * Test that the formatted message includes the lineSource on a new line when non-empty.
226      */
227     @Test
228     void testMessageFormat_includesLineSource() {
229         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
230         final var captor = ArgumentCaptor.forClass(String.class);
231         reporter.error("msg", "a.js", 1, "var x = $bad;", 0);
232         Mockito.verify(log).error(captor.capture());
233         Assertions.assertTrue(captor.getValue().contains("var x = $bad;"),
234                 "Expected lineSource to be appended to message");
235     }
236 
237     /**
238      * Test that a null or empty message is replaced with "unknown error".
239      */
240     @Test
241     void testMessageFormat_nullMessage_showsUnknownError() {
242         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
243         final var captor = ArgumentCaptor.forClass(String.class);
244         reporter.error(null, "a.js", 1, null, 0);
245         Mockito.verify(log).error(captor.capture());
246         Assertions.assertTrue(captor.getValue().contains("unknown error"),
247                 "Expected 'unknown error' placeholder for null message");
248     }
249 
250     /**
251      * Test that a null sourceName falls back to the default filename (if set).
252      */
253     @Test
254     void testMessageFormat_nullSourceName_usesDefaultFilename() {
255         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
256         reporter.setDefaultFileName("fallback.js");
257         final var captor = ArgumentCaptor.forClass(String.class);
258         reporter.error("err", null, 5, null, 0);
259         Mockito.verify(log).error(captor.capture());
260         Assertions.assertTrue(captor.getValue().contains("fallback.js"),
261                 "Expected default filename to be used when sourceName is null");
262     }
263 
264     /**
265      * Test that when both sourceName and defaultFilename are null/empty, no location prefix is added.
266      */
267     @Test
268     void testMessageFormat_noSourceAtAll_onlyMessageText() {
269         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
270         final var captor = ArgumentCaptor.forClass(String.class);
271         reporter.error("just the error", null, 1, null, 0);
272         Mockito.verify(log).error(captor.capture());
273         // The message should just be the error text; no "null" string
274         Assertions.assertFalse(captor.getValue().contains("null"),
275                 "Message should not contain the literal string 'null'");
276         Assertions.assertTrue(captor.getValue().contains("just the error"), "Expected the error text to be present");
277     }
278 
279     // ---------------------------------------------------------------- setFile
280 
281     /**
282      * Test that {@code setFile()} updates the source file used when calling {@code addMessage}.
283      */
284     @Test
285     void testSetFile_updatesSourceFileForBuildContext() {
286         final var file1 = Path.of("first.js").toFile();
287         final var file2 = Path.of("second.js").toFile();
288         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
289 
290         reporter.setFile(file1);
291         reporter.error("e", null, 1, null, 0);
292         Mockito.verify(buildContext).addMessage(Mockito.eq(file1), Mockito.anyInt(), Mockito.anyInt(),
293                 Mockito.anyString(), Mockito.anyInt(), Mockito.isNull());
294 
295         reporter.setFile(file2);
296         reporter.error("e", null, 2, null, 0);
297         Mockito.verify(buildContext).addMessage(Mockito.eq(file2), Mockito.anyInt(), Mockito.anyInt(),
298                 Mockito.anyString(), Mockito.anyInt(), Mockito.isNull());
299     }
300 
301     // --------------------------------------------------------------- counters
302 
303     /**
304      * Test that both error and warning counts start at zero.
305      */
306     @Test
307     void testInitialCountersAreZero() {
308         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
309         Assertions.assertEquals(0, reporter.getErrorCnt());
310         Assertions.assertEquals(0, reporter.getWarningCnt());
311     }
312 
313     /**
314      * Test that error and warning counters are independent.
315      */
316     @Test
317     void testErrorAndWarningCountersAreIndependent() {
318         final var reporter = new ErrorReporter4Mojo(log, true, buildContext);
319         reporter.error("e", null, 1, null, 0);
320         reporter.warning("w", null, 2, null, 0);
321         Assertions.assertEquals(1, reporter.getErrorCnt());
322         Assertions.assertEquals(1, reporter.getWarningCnt());
323     }
324 }