11import * as assert from 'assert' ;
2- import { CommandTreeItem , CommandsProvider , MAGEFORGE_COMMANDS } from '../../commandsProvider' ;
2+ import mockRequire = require( 'mock-require' ) ;
3+ import type { MageforgeCommand } from '../../commandsProvider' ;
4+
5+ type MockMagento = {
6+ getMagentoRoot : ( ) => string | undefined ;
7+ execMageforge : ( root : string , command : string , args ?: string [ ] ) => Promise < string > ;
8+ } ;
9+
10+ function loadCommandsProvider ( magentoMock : MockMagento ) {
11+ mockRequire ( '../../magento' , magentoMock ) ;
12+ return mockRequire . reRequire (
13+ '../../commandsProvider' ,
14+ ) as typeof import ( '../../commandsProvider' ) ;
15+ }
316
417suite ( 'commandsProvider.ts unit tests' , ( ) => {
18+ teardown ( ( ) => {
19+ mockRequire . stop ( '../../magento' ) ;
20+ } ) ;
21+
522 suite ( 'MAGEFORGE_COMMANDS' , ( ) => {
623 test ( 'contains expected commands' , ( ) => {
24+ const { MAGEFORGE_COMMANDS } = loadCommandsProvider ( {
25+ getMagentoRoot : ( ) => undefined ,
26+ execMageforge : async ( ) => '' ,
27+ } ) ;
28+
729 const ids = MAGEFORGE_COMMANDS . map ( ( cmd ) => cmd . id ) ;
830 assert . ok ( ids . includes ( 'mageforge.theme.build' ) ) ;
931 assert . ok ( ids . includes ( 'mageforge.theme.watch' ) ) ;
@@ -12,28 +34,142 @@ suite('commandsProvider.ts unit tests', () => {
1234 } ) ;
1335
1436 test ( 'theme commands accept themes' , ( ) => {
37+ const { MAGEFORGE_COMMANDS } = loadCommandsProvider ( {
38+ getMagentoRoot : ( ) => undefined ,
39+ execMageforge : async ( ) => '' ,
40+ } ) ;
41+
1542 const themeCommands = MAGEFORGE_COMMANDS . filter ( ( cmd ) => cmd . acceptsThemes ) ;
1643 const ids = themeCommands . map ( ( cmd ) => cmd . id ) ;
1744 assert . deepStrictEqual ( ids . sort ( ) , [ 'mageforge.theme.build' , 'mageforge.theme.watch' ] ) ;
1845 } ) ;
1946
2047 test ( 'watch command is marked as watch' , ( ) => {
48+ const { MAGEFORGE_COMMANDS } = loadCommandsProvider ( {
49+ getMagentoRoot : ( ) => undefined ,
50+ execMageforge : async ( ) => '' ,
51+ } ) ;
52+
2153 const watch = MAGEFORGE_COMMANDS . find ( ( cmd ) => cmd . id === 'mageforge.theme.watch' ) ;
2254 assert . strictEqual ( watch ?. isWatch , true ) ;
2355 } ) ;
2456 } ) ;
2557
58+ suite ( 'getAvailableMageforgeCommands' , ( ) => {
59+ test ( 'parses raw command list output' , async ( ) => {
60+ const { getAvailableMageforgeCommands } = loadCommandsProvider ( {
61+ getMagentoRoot : ( ) => '/magento' ,
62+ execMageforge : async ( ) =>
63+ 'mageforge:theme:build Builds a theme\nmageforge:theme:list\nmageforge:system:version' ,
64+ } ) ;
65+
66+ const available = await getAvailableMageforgeCommands ( '/magento' ) ;
67+
68+ assert . deepStrictEqual ( available . sort ( ) , [
69+ 'mageforge:system:version' ,
70+ 'mageforge:theme:build' ,
71+ 'mageforge:theme:list' ,
72+ ] ) ;
73+ } ) ;
74+ } ) ;
75+
2676 suite ( 'CommandsProvider' , ( ) => {
27- test ( 'returns all commands as tree items' , ( ) => {
77+ test ( 'shows only commands reported by the CLI' , async ( ) => {
78+ const { CommandsProvider, MAGEFORGE_COMMANDS } = loadCommandsProvider ( {
79+ getMagentoRoot : ( ) => '/magento' ,
80+ execMageforge : async ( ) =>
81+ 'mageforge:theme:build\nmageforge:theme:watch\nmageforge:system:version' ,
82+ } ) ;
83+
84+ const provider = new CommandsProvider ( ) ;
85+ const children = await provider . getChildren ( ) ;
86+
87+ assert . strictEqual ( children . length , 3 ) ;
88+ const ids = children . map ( ( child ) => child . mageforgeCommand ?. id ) ;
89+ assert . deepStrictEqual ( ids . sort ( ) , [
90+ 'mageforge.system.version' ,
91+ 'mageforge.theme.build' ,
92+ 'mageforge.theme.watch' ,
93+ ] ) ;
94+ } ) ;
95+
96+ test ( 'shows info message when no Magento root is found' , async ( ) => {
97+ const { CommandsProvider } = loadCommandsProvider ( {
98+ getMagentoRoot : ( ) => undefined ,
99+ execMageforge : async ( ) => '' ,
100+ } ) ;
101+
102+ const provider = new CommandsProvider ( ) ;
103+ const children = await provider . getChildren ( ) ;
104+
105+ assert . strictEqual ( children . length , 1 ) ;
106+ assert . strictEqual ( children [ 0 ] . mageforgeCommand , undefined ) ;
107+ assert . ok ( children [ 0 ] . label ?. toString ( ) . includes ( 'Magento workspace' ) ) ;
108+ } ) ;
109+
110+ test ( 'shows user-friendly error when CLI call fails' , async ( ) => {
111+ const { CommandsProvider } = loadCommandsProvider ( {
112+ getMagentoRoot : ( ) => '/magento' ,
113+ execMageforge : async ( ) => {
114+ throw new Error ( 'ddev is not running' ) ;
115+ } ,
116+ } ) ;
117+
118+ const provider = new CommandsProvider ( ) ;
119+ const children = await provider . getChildren ( ) ;
120+
121+ assert . strictEqual ( children . length , 1 ) ;
122+ assert . strictEqual ( children [ 0 ] . mageforgeCommand , undefined ) ;
123+ assert . ok ( children [ 0 ] . label ?. toString ( ) . includes ( 'DDEV' ) ) ;
124+ } ) ;
125+
126+ test ( 'caches available commands across multiple getChildren calls' , async ( ) => {
127+ let calls = 0 ;
128+ const { CommandsProvider } = loadCommandsProvider ( {
129+ getMagentoRoot : ( ) => '/magento' ,
130+ execMageforge : async ( ) => {
131+ calls ++ ;
132+ return 'mageforge:theme:build' ;
133+ } ,
134+ } ) ;
135+
28136 const provider = new CommandsProvider ( ) ;
29- const children = provider . getChildren ( ) ;
137+ await provider . getChildren ( ) ;
138+ await provider . getChildren ( ) ;
30139
31- assert . strictEqual ( children . length , MAGEFORGE_COMMANDS . length ) ;
140+ assert . strictEqual ( calls , 1 ) ;
141+ } ) ;
142+
143+ test ( 'refresh clears cache and reloads commands' , async ( ) => {
144+ let calls = 0 ;
145+ const { CommandsProvider } = loadCommandsProvider ( {
146+ getMagentoRoot : ( ) => '/magento' ,
147+ execMageforge : async ( ) => {
148+ calls ++ ;
149+ return calls === 1
150+ ? 'mageforge:theme:build'
151+ : 'mageforge:theme:build\nmageforge:theme:watch' ;
152+ } ,
153+ } ) ;
154+
155+ const provider = new CommandsProvider ( ) ;
156+ const first = await provider . getChildren ( ) ;
157+ assert . strictEqual ( first . length , 1 ) ;
158+
159+ provider . refresh ( ) ;
160+ const second = await provider . getChildren ( ) ;
161+ assert . strictEqual ( second . length , 2 ) ;
162+ assert . strictEqual ( calls , 2 ) ;
32163 } ) ;
33164 } ) ;
34165
35166 suite ( 'CommandTreeItem' , ( ) => {
36167 test ( 'sets label, description and command' , ( ) => {
168+ const { CommandTreeItem, MAGEFORGE_COMMANDS } = loadCommandsProvider ( {
169+ getMagentoRoot : ( ) => undefined ,
170+ execMageforge : async ( ) => '' ,
171+ } ) ;
172+
37173 const cmd = MAGEFORGE_COMMANDS [ 0 ] ;
38174 const item = new CommandTreeItem ( cmd ) ;
39175
@@ -43,5 +179,18 @@ suite('commandsProvider.ts unit tests', () => {
43179 assert . strictEqual ( item . command ?. title , cmd . label ) ;
44180 assert . strictEqual ( item . contextValue , 'mageforgeCommand' ) ;
45181 } ) ;
182+
183+ test ( 'renders info item without command' , ( ) => {
184+ const { CommandTreeItem } = loadCommandsProvider ( {
185+ getMagentoRoot : ( ) => undefined ,
186+ execMageforge : async ( ) => '' ,
187+ } ) ;
188+
189+ const item = new CommandTreeItem ( undefined , 'Info message' ) ;
190+
191+ assert . strictEqual ( item . label , 'Info message' ) ;
192+ assert . strictEqual ( item . command , undefined ) ;
193+ assert . strictEqual ( item . contextValue , undefined ) ;
194+ } ) ;
46195 } ) ;
47196} ) ;
0 commit comments