@@ -9,7 +9,36 @@ describe('Admin bar', function () {
99 // EXISTENCE
1010 /// ///
1111
12- it ( 'should allow a group reversing the current order' , async function ( ) {
12+ it ( 'should respect `last` and `after` options when no groups or order specified' , async function ( ) {
13+ let apos ;
14+ try {
15+ apos = await t . create ( {
16+ root : module ,
17+ modules : {
18+ 'test-natural-options' : {
19+ init ( self ) {
20+ self . apos . adminBar . add ( 'normal-item' , 'Normal Item' , null ) ;
21+ self . apos . adminBar . add ( 'last-item' , 'Last Item' , null , { last : true } ) ;
22+ self . apos . adminBar . add ( 'after-item' , 'After Item' , null , { after : 'normal-item' } ) ;
23+ }
24+ }
25+ // No groups or order - let last/after work naturally
26+ }
27+ } ) ;
28+
29+ const normalIndex = apos . adminBar . items . findIndex ( item => item . name === 'normal-item' ) ;
30+ const afterIndex = apos . adminBar . items . findIndex ( item => item . name === 'after-item' ) ;
31+ const lastIndex = apos . adminBar . items . findIndex ( item => item . name === 'last-item' ) ;
32+
33+ // When no higher-priority options, last/after should work
34+ assert ( afterIndex === normalIndex + 1 , 'After item should immediately follow target' ) ;
35+ assert ( lastIndex === apos . adminBar . items . length - 1 , 'Last item should be at the end' ) ;
36+ } finally {
37+ t . destroy ( apos ) ;
38+ }
39+ } ) ;
40+
41+ it ( 'should create groups with proper menuLeader assignment' , async function ( ) {
1342 let apos ;
1443 try {
1544 apos = await t . create ( {
@@ -22,65 +51,224 @@ describe('Admin bar', function () {
2251 label : 'Media' ,
2352 items : [
2453 '@apostrophecms/image' ,
25- '@apostrophecms/image-tag' ,
26- '@apostrophecms/file' ,
27- '@apostrophecms/file-tag'
28- ]
29- } ,
30- {
31- label : 'Content' ,
32- items : [
33- '@apostrophecms/file' ,
34- '@apostrophecms/image'
54+ '@apostrophecms/file'
3555 ]
3656 }
3757 ]
3858 }
3959 }
4060 }
4161 } ) ;
62+
4263 assert ( apos . modules [ '@apostrophecms/admin-bar' ] ) ;
4364 assert ( apos . adminBar ) ;
44- assert . strictEqual ( apos . adminBar . items . length , 7 ) ;
45- assert ( apos . adminBar . items [ 2 ] . name === '@apostrophecms/file' ) ;
46- assert ( apos . adminBar . items [ 3 ] . name === '@apostrophecms/image' ) ;
65+
66+ const imageItem = apos . adminBar . items . find ( item => item . name === '@apostrophecms/image' ) ;
67+ const fileItem = apos . adminBar . items . find ( item => item . name === '@apostrophecms/file' ) ;
68+
69+ // Both items should have the same menuLeader (the first item in the group)
70+ assert ( imageItem . menuLeader === '@apostrophecms/image' ) ;
71+ assert ( fileItem . menuLeader === '@apostrophecms/image' ) ;
72+
73+ // Group label should be stored
74+ assert ( apos . adminBar . groupLabels [ '@apostrophecms/image' ] === 'Media' ) ;
75+
76+ // Items should be consecutive
77+ const imageIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/image' ) ;
78+ const fileIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/file' ) ;
79+ assert ( Math . abs ( imageIndex - fileIndex ) === 1 ) ;
4780 } finally {
4881 t . destroy ( apos ) ;
4982 }
5083 } ) ;
5184
52- it ( 'should allow a group obeying the current order ' , async function ( ) {
85+ it ( 'should handle duplicates in multi-item groups correctly ' , async function ( ) {
5386 let apos ;
87+ const warnings = [ ] ;
88+
5489 try {
5590 apos = await t . create ( {
91+ root : module ,
5692 modules : {
5793 '@apostrophecms/admin-bar' : {
5894 options : {
5995 addGroups : [
6096 {
61- label : 'Media' ,
97+ label : 'Media Group ' ,
6298 items : [
6399 '@apostrophecms/image' ,
64100 '@apostrophecms/file'
65101 ]
66102 } ,
67103 {
68- label : 'Content' ,
104+ label : 'Content Group ' ,
69105 items : [
70- '@apostrophecms/file' ,
71- '@apostrophecms/image'
106+ '@apostrophecms/file' , // Duplicate!
107+ '@apostrophecms/user' ,
108+ '@apostrophecms/image-tag'
72109 ]
73110 }
74111 ]
75112 }
76113 }
77114 }
78115 } ) ;
79- assert ( apos . modules [ '@apostrophecms/admin-bar' ] ) ;
80- assert ( apos . adminBar ) ;
81- assert ( apos . adminBar . items . length === 7 ) ;
82- assert ( apos . adminBar . items [ 1 ] . name === '@apostrophecms/file' ) ;
83- assert ( apos . adminBar . items [ 2 ] . name === '@apostrophecms/image' ) ;
116+
117+ // Mock warn function
118+ const originalWarn = apos . util . warn ;
119+ apos . util . warn = ( ...args ) => {
120+ warnings . push ( args . join ( ' ' ) ) ;
121+ originalWarn . apply ( apos . util , args ) ;
122+ } ;
123+
124+ apos . adminBar . orderItems ( ) ;
125+ apos . adminBar . groupItems ( ) ;
126+ apos . util . warn = originalWarn ;
127+
128+ // Should warn about duplicate
129+ const duplicateWarning = warnings . find ( w =>
130+ w . includes ( '@apostrophecms/file' ) && w . includes ( 'multiple groups' )
131+ ) ;
132+ assert ( duplicateWarning , 'Should warn about duplicate item' ) ;
133+
134+ // File should be in first group only
135+ const fileItem = apos . adminBar . items . find ( item => item . name === '@apostrophecms/file' ) ;
136+ assert ( fileItem . menuLeader === '@apostrophecms/image' , 'File should be in Media Group' ) ;
137+
138+ // Second group should still exist with user as leader (since file was skipped)
139+ const userItem = apos . adminBar . items . find ( item => item . name === '@apostrophecms/user' ) ;
140+ const imageTagItem = apos . adminBar . items . find ( item => item . name === '@apostrophecms/image-tag' ) ;
141+
142+ if ( userItem && imageTagItem ) {
143+ // Both remaining items should be grouped together
144+ assert ( userItem . menuLeader === '@apostrophecms/user' , 'User should be leader of Content Group' ) ;
145+ assert ( imageTagItem . menuLeader === '@apostrophecms/user' , 'Image-tag should be in Content Group' ) ;
146+
147+ // They should be consecutive
148+ const userIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/user' ) ;
149+ const imageTagIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/image-tag' ) ;
150+ assert ( Math . abs ( userIndex - imageTagIndex ) === 1 , 'Content Group items should be consecutive' ) ;
151+ }
152+
153+ } finally {
154+ t . destroy ( apos ) ;
155+ }
156+ } ) ;
157+
158+ it ( 'should handle groups in registration order without explicit order' , async function ( ) {
159+ let apos ;
160+ try {
161+ apos = await t . create ( {
162+ root : module ,
163+ modules : {
164+ '@apostrophecms/admin-bar' : {
165+ options : {
166+ addGroups : [
167+ {
168+ label : 'Alpha Group' ,
169+ items : [ '@apostrophecms/image-tag' , '@apostrophecms/file-tag' ]
170+ } ,
171+ {
172+ label : 'Beta Group' ,
173+ items : [ '@apostrophecms/image' , '@apostrophecms/file' ]
174+ }
175+ ]
176+ }
177+ }
178+ }
179+ } ) ;
180+
181+ const iTagIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/image-tag' ) ;
182+ const fTagIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/file-tag' ) ;
183+ const imageIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/image' ) ;
184+ const fileIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/file' ) ;
185+ // Alpha group should come first in registration order
186+ assert ( iTagIndex < imageIndex , 'First registered group should appear first' ) ;
187+ assert ( fTagIndex < imageIndex , 'First registered group should appear first' ) ;
188+
189+ // Groups should be internally ordered and contiguous
190+
191+ assert ( fTagIndex === iTagIndex + 1 , 'First group should be contiguous' ) ;
192+ assert ( fileIndex === imageIndex + 1 , 'Second group should be contiguous' ) ;
193+ } finally {
194+ t . destroy ( apos ) ;
195+ }
196+ } ) ;
197+
198+ it ( 'should prioritize groups over individual `last` and `after` options' , async function ( ) {
199+ let apos ;
200+ try {
201+ apos = await t . create ( {
202+ root : module ,
203+ modules : {
204+ 'test-precedence' : {
205+ init ( self ) {
206+ self . apos . adminBar . add ( 'item-a' , 'Item A' , null ) ;
207+ self . apos . adminBar . add ( 'item-b' , 'Item B' , null , { last : true } ) ;
208+ self . apos . adminBar . add ( 'item-c' , 'Item C' , null , { after : '@apostrophecms/user' } ) ;
209+ }
210+ } ,
211+ '@apostrophecms/admin-bar' : {
212+ options : {
213+ addGroups : [
214+ {
215+ label : 'Test Group' ,
216+ items : [ 'item-a' , 'item-b' , 'item-c' ] // All grouped despite last/after
217+ }
218+ ]
219+ }
220+ }
221+ }
222+ } ) ;
223+
224+ const itemAIndex = apos . adminBar . items . findIndex ( item => item . name === 'item-a' ) ;
225+ const itemBIndex = apos . adminBar . items . findIndex ( item => item . name === 'item-b' ) ;
226+ const itemCIndex = apos . adminBar . items . findIndex ( item => item . name === 'item-c' ) ;
227+
228+ // All items should be grouped together, ignoring last/after
229+ assert ( Math . abs ( itemAIndex - itemBIndex ) <= 2 , 'Items should be grouped despite last option' ) ;
230+ assert ( Math . abs ( itemAIndex - itemCIndex ) <= 2 , 'Items should be grouped despite after option' ) ;
231+ assert ( Math . abs ( itemBIndex - itemCIndex ) <= 2 , 'Items should be grouped despite individual options' ) ;
232+
233+ // All should have the same menuLeader
234+ assert ( apos . adminBar . items [ itemAIndex ] . menuLeader === 'item-a' ) ;
235+ assert ( apos . adminBar . items [ itemBIndex ] . menuLeader === 'item-a' ) ;
236+ assert ( apos . adminBar . items [ itemCIndex ] . menuLeader === 'item-a' ) ;
237+ } finally {
238+ t . destroy ( apos ) ;
239+ }
240+ } ) ;
241+
242+ it ( 'should prioritize order array over individual `last` and `after` options' , async function ( ) {
243+ let apos ;
244+ try {
245+ apos = await t . create ( {
246+ root : module ,
247+ modules : {
248+ 'test-order-precedence' : {
249+ init ( self ) {
250+ self . apos . adminBar . add ( 'item-x' , 'Item X' , null , { last : true } ) ;
251+ self . apos . adminBar . add ( 'item-y' , 'Item Y' , null , { after : '@apostrophecms/file' } ) ;
252+ }
253+ } ,
254+ '@apostrophecms/admin-bar' : {
255+ options : {
256+ order : [ 'item-x' , '@apostrophecms/user' , 'item-y' , '@apostrophecms/image' ]
257+ }
258+ }
259+ }
260+ } ) ;
261+
262+ const itemXIndex = apos . adminBar . items . findIndex ( item => item . name === 'item-x' ) ;
263+ const userIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/user' ) ;
264+ const itemYIndex = apos . adminBar . items . findIndex ( item => item . name === 'item-y' ) ;
265+ const imageIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/image' ) ;
266+
267+ // Should follow order array, ignoring last/after
268+ assert ( itemXIndex === 0 , 'item-x should be first per order array, not last' ) ;
269+ assert ( userIndex === 1 , 'user should be second per order array' ) ;
270+ assert ( itemYIndex === 2 , 'item-y should be third per order array, not after file' ) ;
271+ assert ( imageIndex === 3 , 'image should be fourth per order array' ) ;
84272 } finally {
85273 t . destroy ( apos ) ;
86274 }
@@ -226,34 +414,36 @@ describe('Admin bar', function () {
226414 }
227415 } ) ;
228416
229- it ( 'should override last:true when item appears in order array ' , async function ( ) {
417+ it ( 'should prioritize order array over individual `last` and `after` options ' , async function ( ) {
230418 let apos ;
231419 try {
232420 apos = await t . create ( {
233421 root : module ,
234422 modules : {
235- '@apostrophecms/admin-bar' : {
236- options : {
237- order : [ '@apostrophecms/user' , '@apostrophecms/image' ]
423+ 'test-order-precedence' : {
424+ init ( self ) {
425+ self . apos . adminBar . add ( 'item-x' , 'Item X' , null , { last : true } ) ;
426+ self . apos . adminBar . add ( 'item-y' , 'Item Y' , null , { after : '@apostrophecms/file' } ) ;
238427 }
239428 } ,
240- 'test-module ' : {
241- init ( self ) {
242- self . apos . adminBar . add ( 'test-module ', 'Test ' , null , { last : true } ) ;
429+ '@apostrophecms/admin-bar ' : {
430+ options : {
431+ order : [ 'item-x ', '@apostrophecms/user ' , 'item-y' , '@apostrophecms/image' ]
243432 }
244433 }
245434 }
246435 } ) ;
247436
437+ const itemXIndex = apos . adminBar . items . findIndex ( item => item . name === 'item-x' ) ;
248438 const userIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/user' ) ;
439+ const itemYIndex = apos . adminBar . items . findIndex ( item => item . name === 'item-y' ) ;
249440 const imageIndex = apos . adminBar . items . findIndex ( item => item . name === '@apostrophecms/image' ) ;
250- const testIndex = apos . adminBar . items . findIndex ( item => item . name === 'test-module' ) ;
251441
252- // Even if image had last:true, it should respect order position
253- assert ( userIndex === 0 ) ;
254- assert ( imageIndex === 1 ) ;
255- // test-module should still be last since it's not in order
256- assert ( testIndex === apos . adminBar . items . length - 1 ) ;
442+ // Should follow order array, ignoring last/after
443+ assert ( itemXIndex === 0 , 'item-x should be first per order array, not last' ) ;
444+ assert ( userIndex === 1 , 'user should be second per order array' ) ;
445+ assert ( itemYIndex === 2 , 'item-y should be third per order array, not after file' ) ;
446+ assert ( imageIndex === 3 , 'image should be fourth per order array' ) ;
257447 } finally {
258448 t . destroy ( apos ) ;
259449 }
@@ -385,4 +575,5 @@ describe('Admin bar', function () {
385575 t . destroy ( apos ) ;
386576 }
387577 } ) ;
578+
388579} ) ;
0 commit comments