2222package com .kingsrook .qqq .backend .core .model .metadata ;
2323
2424
25+ import java .util .ArrayList ;
26+ import java .util .List ;
2527import com .kingsrook .qqq .backend .core .exceptions .QException ;
2628import com .kingsrook .qqq .backend .core .model .dashboard .widgets .WidgetType ;
2729import com .kingsrook .qqq .backend .core .model .metadata .dashboard .QWidgetMetaDataInterface ;
4042import com .kingsrook .qqq .backend .core .model .metadata .producers .TestNoInterfacesExtendsObject ;
4143import com .kingsrook .qqq .backend .core .model .metadata .producers .TestNoValidConstructorMetaDataProducer ;
4244import com .kingsrook .qqq .backend .core .model .metadata .tables .QTableMetaData ;
45+ import org .junit .jupiter .api .AfterEach ;
4346import org .junit .jupiter .api .Test ;
4447import static org .junit .jupiter .api .Assertions .assertEquals ;
4548import static org .junit .jupiter .api .Assertions .assertFalse ;
5255 *******************************************************************************/
5356class MetaDataProducerHelperTest
5457{
58+ private static final String DISABLE_NAME_TIEBREAKER_PROPERTY = "qqq.MetaDataProducerHelper.disableNameTiebreaker" ;
59+
60+
61+
62+ /***************************************************************************
63+ *
64+ ***************************************************************************/
65+ @ AfterEach
66+ void afterEach ()
67+ {
68+ System .clearProperty (DISABLE_NAME_TIEBREAKER_PROPERTY );
69+ }
70+
71+
5572
5673 /*******************************************************************************
5774 **
@@ -122,4 +139,180 @@ void test() throws QException
122139
123140 }
124141
142+
143+
144+ /*******************************************************************************
145+ ** Test that producers with the same sortOrder and type are sorted by class
146+ ** simple name (alphabetically), then by full class name, for deterministic ordering.
147+ *******************************************************************************/
148+ @ Test
149+ void testSortByClassSimpleNameThenFullName ()
150+ {
151+ ///////////////////////////////////////////////////////////////////////////
152+ // create producers with same sortOrder - they should sort by class name //
153+ ///////////////////////////////////////////////////////////////////////////
154+ MetaDataProducerInterface <?> producerZ = new TestProducerZebra ();
155+ MetaDataProducerInterface <?> producerA = new TestProducerAlpha ();
156+ MetaDataProducerInterface <?> producerM = new TestProducerMango ();
157+
158+ ///////////////////////////////////////////////////////
159+ // put them in an "unsorted" order and sort the list //
160+ ///////////////////////////////////////////////////////
161+ List <MetaDataProducerInterface <?>> producers = new ArrayList <>(List .of (producerZ , producerA , producerM ));
162+ MetaDataProducerHelper .sortMetaDataProducers (producers );
163+
164+ //////////////////////////////////////////////////////////////
165+ // verify they are now sorted alphabetically by simple name //
166+ //////////////////////////////////////////////////////////////
167+ assertEquals ("TestProducerAlpha" , producers .get (0 ).getClass ().getSimpleName ());
168+ assertEquals ("TestProducerMango" , producers .get (1 ).getClass ().getSimpleName ());
169+ assertEquals ("TestProducerZebra" , producers .get (2 ).getClass ().getSimpleName ());
170+ }
171+
172+
173+
174+ /*******************************************************************************
175+ ** Test that the system property disables the class name tiebreaker, restoring
176+ ** the previous (undefined) behavior.
177+ *******************************************************************************/
178+ @ Test
179+ void testDisableNameTiebreakerSystemProperty ()
180+ {
181+ System .setProperty (DISABLE_NAME_TIEBREAKER_PROPERTY , "true" );
182+
183+ MetaDataProducerInterface <?> producerZ = new TestProducerZebra ();
184+ MetaDataProducerInterface <?> producerA = new TestProducerAlpha ();
185+
186+ /////////////////////////////////////////////////////////////////////////////
187+ // with tiebreaker disabled, order should be based on insertion order //
188+ // (since sortOrder and type are equal, and no further comparator applied) //
189+ /////////////////////////////////////////////////////////////////////////////
190+ List <MetaDataProducerInterface <?>> producers = new ArrayList <>(List .of (producerZ , producerA ));
191+ MetaDataProducerHelper .sortMetaDataProducers (producers );
192+
193+ //////////////////////////////////////////////////////////////////////////////
194+ // the order should remain as inserted (Z, A) since there's no tie-breaker. //
195+ // note: this relies on stable sort behavior in Java //
196+ //////////////////////////////////////////////////////////////////////////////
197+ assertEquals ("TestProducerZebra" , producers .get (0 ).getClass ().getSimpleName ());
198+ assertEquals ("TestProducerAlpha" , producers .get (1 ).getClass ().getSimpleName ());
199+ }
200+
201+
202+
203+ /*******************************************************************************
204+ ** Test that sortOrder still takes precedence over class name.
205+ *******************************************************************************/
206+ @ Test
207+ void testSortOrderTakesPrecedenceOverClassName ()
208+ {
209+ ///////////////////////////////////////////////////////////////////////////
210+ // producerZ has lower sortOrder, so should come first despite "Z" > "A" //
211+ ///////////////////////////////////////////////////////////////////////////
212+ MetaDataProducerInterface <?> producerZ = new TestProducerZebra ()
213+ {
214+ @ Override
215+ public int getSortOrder ()
216+ {
217+ return 100 ;
218+ }
219+ };
220+ MetaDataProducerInterface <?> producerA = new TestProducerAlpha ()
221+ {
222+ @ Override
223+ public int getSortOrder ()
224+ {
225+ return 200 ;
226+ }
227+ };
228+
229+ List <MetaDataProducerInterface <?>> producers = new ArrayList <>(List .of (producerA , producerZ ));
230+ MetaDataProducerHelper .sortMetaDataProducers (producers );
231+
232+ //////////////////////////////////////////////////////////////
233+ // Z should come first because it has lower sortOrder (100) //
234+ //////////////////////////////////////////////////////////////
235+ assertEquals (100 , producers .get (0 ).getSortOrder ());
236+ assertEquals (200 , producers .get (1 ).getSortOrder ());
237+ }
238+
239+
240+
241+ /*******************************************************************************
242+ ** Test that when simple names are equal, full class name is used as tiebreaker.
243+ ** This tests the scenario mentioned in the review: com.foo.MyProducer vs com.bar.MyProducer
244+ *******************************************************************************/
245+ @ Test
246+ void testFullClassNameTiebreakerWhenSimpleNamesMatch ()
247+ {
248+ /////////////////////////////////////////////////////////////////////////////////
249+ // create two producers from different inner classes that have the same simple //
250+ // name pattern (anonymous classes extending the same base) //
251+ // We'll use the outer class structure to create predictable full names //
252+ /////////////////////////////////////////////////////////////////////////////////
253+ MetaDataProducerInterface <?> producerFromAlpha = new TestProducerAlpha () {};
254+ MetaDataProducerInterface <?> producerFromZebra = new TestProducerZebra () {};
255+
256+ //////////////////////////////////////////////////////////////////////////////
257+ // both are anonymous classes, so their simple names will be empty strings. //
258+ // the full name will include the outer class and a number suffix. //
259+ // this exercises the full-name tiebreaker when simple names are equal. //
260+ //////////////////////////////////////////////////////////////////////////////
261+ assertEquals (producerFromAlpha .getClass ().getSimpleName (), producerFromZebra .getClass ().getSimpleName ());
262+
263+ List <MetaDataProducerInterface <?>> producers = new ArrayList <>(List .of (producerFromZebra , producerFromAlpha ));
264+ MetaDataProducerHelper .sortMetaDataProducers (producers );
265+
266+ /////////////////////////////////////////////////////////////////////////////////////
267+ // after sorting, they should be in a deterministic order based on full class name //
268+ // the key assertion is that sorting is stable and deterministic //
269+ /////////////////////////////////////////////////////////////////////////////////////
270+ String firstName = producers .get (0 ).getClass ().getName ();
271+ String secondName = producers .get (1 ).getClass ().getName ();
272+ assertTrue (firstName .compareTo (secondName ) <= 0 ,
273+ "Expected first producer's full name [" + firstName + "] to sort before or equal to second [" + secondName + "]" );
274+ }
275+
276+
277+
278+ /***************************************************************************
279+ * Test producer class - named to sort first alphabetically
280+ ***************************************************************************/
281+ private static class TestProducerAlpha implements MetaDataProducerInterface <QTableMetaData >
282+ {
283+ @ Override
284+ public QTableMetaData produce (QInstance qInstance )
285+ {
286+ return new QTableMetaData ().withName ("alpha" );
287+ }
288+ }
289+
290+
291+
292+ /***************************************************************************
293+ * Test producer class - named to sort in the middle alphabetically
294+ ***************************************************************************/
295+ private static class TestProducerMango implements MetaDataProducerInterface <QTableMetaData >
296+ {
297+ @ Override
298+ public QTableMetaData produce (QInstance qInstance )
299+ {
300+ return new QTableMetaData ().withName ("mango" );
301+ }
302+ }
303+
304+
305+
306+ /***************************************************************************
307+ * Test producer class - named to sort last alphabetically
308+ ***************************************************************************/
309+ private static class TestProducerZebra implements MetaDataProducerInterface <QTableMetaData >
310+ {
311+ @ Override
312+ public QTableMetaData produce (QInstance qInstance )
313+ {
314+ return new QTableMetaData ().withName ("zebra" );
315+ }
316+ }
317+
125318}
0 commit comments