@@ -13,10 +13,22 @@ const debugMock = jest.fn()
1313const infoMock = jest . fn ( )
1414const warningMock = jest . fn ( )
1515const errorMock = jest . fn ( )
16+ const noticeMock = jest . fn ( )
1617const addPathMock = jest . fn ( )
18+ const groupMock = jest . fn ( async ( _name : string , fn : ( ) => Promise < any > ) => fn ( ) )
19+ const summaryMock = {
20+ addHeading : jest . fn ( ) . mockReturnThis ( ) ,
21+ addTable : jest . fn ( ) . mockReturnThis ( ) ,
22+ addCodeBlock : jest . fn ( ) . mockReturnThis ( ) ,
23+ addLink : jest . fn ( ) . mockReturnThis ( ) ,
24+ write : jest . fn ( ) . mockResolvedValue ( undefined )
25+ }
1726const downloadToolMock = jest . fn ( ) . mockResolvedValue ( '/tmp/install.sh' )
1827const elideInfoMock = jest . fn ( ) . mockResolvedValue ( undefined )
1928const obtainVersionMock = jest . fn ( ) . mockResolvedValue ( '1.0.0' )
29+ const initTelemetryMock = jest . fn ( )
30+ const reportErrorMock = jest . fn ( )
31+ const flushTelemetryMock = jest . fn ( ) . mockResolvedValue ( undefined )
2032
2133// Mock modules before any project imports
2234mock . module ( '@actions/exec' , ( ) => ( {
@@ -35,11 +47,14 @@ mock.module('@actions/core', () => ({
3547 debug : debugMock ,
3648 error : errorMock ,
3749 warning : warningMock ,
50+ notice : noticeMock ,
3851 getInput : getInputMock ,
3952 getBooleanInput : jest . fn ( ) . mockReturnValue ( true ) ,
4053 setFailed : setFailedMock ,
4154 setOutput : setOutputMock ,
42- addPath : addPathMock
55+ addPath : addPathMock ,
56+ group : groupMock ,
57+ summary : summaryMock
4358} ) )
4459mock . module ( '@actions/tool-cache' , ( ) => ( {
4560 downloadTool : downloadToolMock ,
@@ -54,6 +69,11 @@ mock.module('../src/command', () => ({
5469 ElideCommand : { RUN : 'run' , INFO : 'info' } ,
5570 ElideArgument : { VERSION : '--version' }
5671} ) )
72+ mock . module ( '../src/telemetry' , ( ) => ( {
73+ initTelemetry : initTelemetryMock ,
74+ reportError : reportErrorMock ,
75+ flushTelemetry : flushTelemetryMock
76+ } ) )
5777
5878const main = await import ( '../src/main' )
5979const { default : buildOptions , OptionName } = await import ( '../src/options' )
@@ -78,7 +98,6 @@ const setupMocks = () => {
7898
7999describe ( 'action' , ( ) => {
80100 beforeEach ( ( ) => {
81- // Clear all mock state
82101 execMock . mockClear ( )
83102 getExecOutputMock . mockClear ( )
84103 whichMock . mockClear ( )
@@ -89,14 +108,25 @@ describe('action', () => {
89108 infoMock . mockClear ( )
90109 warningMock . mockClear ( )
91110 errorMock . mockClear ( )
111+ noticeMock . mockClear ( )
92112 addPathMock . mockClear ( )
113+ groupMock . mockClear ( )
93114 downloadToolMock . mockClear ( )
94115 elideInfoMock . mockClear ( )
95116 obtainVersionMock . mockClear ( )
96- // Default: getInput returns empty
97- getInputMock . mockReturnValue ( '' )
117+ initTelemetryMock . mockClear ( )
118+ reportErrorMock . mockClear ( )
119+ flushTelemetryMock . mockClear ( )
120+ summaryMock . addHeading . mockClear ( )
121+ summaryMock . addTable . mockClear ( )
122+ summaryMock . write . mockClear ( )
123+ summaryMock . addCodeBlock . mockClear ( )
124+ summaryMock . addLink . mockClear ( )
98125
99- // Default: install script path succeeds
126+ getInputMock . mockReturnValue ( '' )
127+ groupMock . mockImplementation (
128+ async ( _name : string , fn : ( ) => Promise < any > ) => fn ( )
129+ )
100130 downloadToolMock . mockResolvedValue ( '/tmp/install.sh' )
101131 execMock . mockResolvedValue ( 0 )
102132 whichMock . mockResolvedValue ( '/mock/bin/elide' )
@@ -107,6 +137,7 @@ describe('action', () => {
107137 } )
108138 elideInfoMock . mockResolvedValue ( undefined )
109139 obtainVersionMock . mockResolvedValue ( '1.0.0' )
140+ flushTelemetryMock . mockResolvedValue ( undefined )
110141 } )
111142
112143 it ( 'reads option inputs' , async ( ) => {
@@ -135,15 +166,73 @@ describe('action', () => {
135166 )
136167 } )
137168
138- it ( 'should fail for unhandled exceptions' , async ( ) => {
169+ it ( 'sets cached and installer outputs' , async ( ) => {
170+ setupMocks ( )
171+ await main . run ( { force : true , installer : 'shell' } )
172+ expect ( setOutputMock ) . toHaveBeenCalledWith ( ActionOutputName . CACHED , 'false' )
173+ expect ( setOutputMock ) . toHaveBeenCalledWith (
174+ ActionOutputName . INSTALLER ,
175+ 'shell'
176+ )
177+ } )
178+
179+ it ( 'should initialize telemetry' , async ( ) => {
180+ setupMocks ( )
181+ await main . run ( )
182+ expect ( initTelemetryMock ) . toHaveBeenCalled ( )
183+ } )
184+
185+ it ( 'should flush telemetry in finally block' , async ( ) => {
186+ setupMocks ( )
187+ await main . run ( )
188+ expect ( flushTelemetryMock ) . toHaveBeenCalled ( )
189+ } )
190+
191+ it ( 'should report errors to telemetry on failure' , async ( ) => {
139192 setupMocks ( )
140- infoMock . mockImplementationOnce ( ( ) => {
141- throw new Error ( 'oh noes' )
193+ infoMock . mockImplementation ( ( msg : string ) => {
194+ if ( msg . includes ( 'Options:' ) ) throw new Error ( 'oh noes' )
142195 } )
143196 await main . run ( )
197+ expect ( reportErrorMock ) . toHaveBeenCalled ( )
144198 expect ( setFailedMock ) . toHaveBeenCalled ( )
145199 } )
146200
201+ it ( 'should use grouped output' , async ( ) => {
202+ setupMocks ( )
203+ await main . run ( { force : true , installer : 'shell' } )
204+ expect ( groupMock ) . toHaveBeenCalledWith (
205+ '⚙️ Resolving options' ,
206+ expect . any ( Function )
207+ )
208+ expect ( groupMock ) . toHaveBeenCalledWith (
209+ '📦 Installing Elide via shell' ,
210+ expect . any ( Function )
211+ )
212+ expect ( groupMock ) . toHaveBeenCalledWith (
213+ '✅ Verifying installation' ,
214+ expect . any ( Function )
215+ )
216+ } )
217+
218+ it ( 'should write job summary on success' , async ( ) => {
219+ setupMocks ( )
220+ await main . run ( { force : true , installer : 'shell' } )
221+ expect ( summaryMock . addHeading ) . toHaveBeenCalledWith ( 'Elide Installed' , 2 )
222+ expect ( summaryMock . write ) . toHaveBeenCalled ( )
223+ } )
224+
225+ it ( 'should write error summary on failure' , async ( ) => {
226+ setupMocks ( )
227+ infoMock . mockImplementation ( ( msg : string ) => {
228+ if ( msg . includes ( 'Options:' ) ) throw new Error ( 'install boom' )
229+ } )
230+ await main . run ( )
231+ expect ( summaryMock . addHeading ) . toHaveBeenCalledWith ( 'Setup Elide Failed' , 2 )
232+ expect ( summaryMock . addCodeBlock ) . toHaveBeenCalled ( )
233+ expect ( summaryMock . write ) . toHaveBeenCalled ( )
234+ } )
235+
147236 it ( 'should properly detect existing elide binary' , async ( ) => {
148237 whichMock . mockResolvedValueOnce ( '/some/path/to/an/elide/bin' )
149238 const existing = await main . resolveExistingBinary ( )
@@ -161,7 +250,7 @@ describe('action', () => {
161250
162251 it ( 'should use archive installer by default' , async ( ) => {
163252 setupMocks ( )
164- await main . run ( { force : true } )
253+ await main . run ( { force : true , installer : 'shell' } )
165254 expect ( setFailedMock ) . not . toHaveBeenCalled ( )
166255 expect ( setOutputMock ) . toHaveBeenCalledWith (
167256 ActionOutputName . PATH ,
@@ -190,17 +279,6 @@ describe('action', () => {
190279 expect ( infoMock ) . toHaveBeenCalledWith ( expect . stringContaining ( 'apt' ) )
191280 } )
192281
193- it ( 'should use archive download for windows with archive installer' , async ( ) => {
194- setupMocks ( )
195- await main . run ( {
196- force : true ,
197- os : 'windows' ,
198- arch : 'amd64' ,
199- installer : 'archive'
200- } )
201- expect ( setFailedMock ) . not . toHaveBeenCalled ( )
202- } )
203-
204282 it ( 'should use msi installer on windows' , async ( ) => {
205283 setupMocks ( )
206284 await main . run ( {
@@ -245,24 +323,12 @@ describe('action', () => {
245323 force : true ,
246324 os : 'linux' ,
247325 arch : 'amd64' ,
326+ version : '1.0.0' ,
248327 installer : 'msi'
249328 } )
250329 expect ( warningMock ) . toHaveBeenCalledWith (
251- expect . stringContaining ( "Installer 'msi' is not supported on linux" )
252- )
253- expect ( setFailedMock ) . not . toHaveBeenCalled ( )
254- } )
255-
256- it ( 'should warn and fall back for pkg on linux' , async ( ) => {
257- setupMocks ( )
258- await main . run ( {
259- force : true ,
260- os : 'linux' ,
261- arch : 'amd64' ,
262- installer : 'pkg'
263- } )
264- expect ( warningMock ) . toHaveBeenCalledWith (
265- expect . stringContaining ( "Installer 'pkg' is not supported on linux" )
330+ expect . stringContaining ( "Installer 'msi' is not supported on linux" ) ,
331+ expect . objectContaining ( { title : 'Installer Fallback' } )
266332 )
267333 expect ( setFailedMock ) . not . toHaveBeenCalled ( )
268334 } )
@@ -277,16 +343,12 @@ describe('action', () => {
277343 ActionOutputName . PATH ,
278344 expect . anything ( )
279345 )
280- expect ( setOutputMock ) . toHaveBeenCalledWith (
281- ActionOutputName . VERSION ,
282- expect . anything ( )
283- )
284346 } )
285347
286348 it ( 'should gracefully handle post-install info failure' , async ( ) => {
287349 setupMocks ( )
288350 elideInfoMock . mockRejectedValueOnce ( new Error ( 'info boom' ) )
289- await main . run ( { force : true } )
351+ await main . run ( { force : true , installer : 'shell' } )
290352 expect ( setFailedMock ) . not . toHaveBeenCalled ( )
291353 expect ( debugMock ) . toHaveBeenCalledWith (
292354 expect . stringContaining ( 'Post-install info failed; proceeding anyway' )
@@ -306,19 +368,19 @@ describe('action', () => {
306368 ActionOutputName . VERSION ,
307369 '1.0.0'
308370 )
309- expect ( infoMock ) . toHaveBeenCalledWith (
310- expect . stringContaining ( 'was preserved' )
371+ expect ( noticeMock ) . toHaveBeenCalledWith (
372+ expect . stringContaining ( 'preserved' ) ,
373+ expect . objectContaining ( { title : 'Already Installed' } )
311374 )
312375 } )
313376
314377 it ( 'should warn on version mismatch' , async ( ) => {
315378 setupMocks ( )
316- // First call (inside installViaShell) returns '1.0.0' as the installed version,
317- // second call (main.run verification) returns a different version.
318379 obtainVersionMock . mockResolvedValueOnce ( '1.0.0' ) . mockResolvedValue ( '9.9.9' )
319380 await main . run ( { force : true , installer : 'shell' } )
320381 expect ( warningMock ) . toHaveBeenCalledWith (
321- expect . stringContaining ( 'Elide version mismatch' )
382+ expect . stringContaining ( 'Elide version mismatch' ) ,
383+ expect . objectContaining ( { title : 'Version Mismatch' } )
322384 )
323385 } )
324386
@@ -338,7 +400,7 @@ describe('action', () => {
338400
339401 it ( 'should not export to path when export_path is false' , async ( ) => {
340402 setupMocks ( )
341- await main . run ( { force : true , export_path : false } )
403+ await main . run ( { force : true , export_path : false , installer : 'shell' } )
342404 expect ( addPathMock ) . not . toHaveBeenCalled ( )
343405 expect ( setFailedMock ) . not . toHaveBeenCalled ( )
344406 } )
0 commit comments