@@ -8,9 +8,10 @@ import { Container } from '@theia/core/shared/inversify';
88import { expect } from 'chai' ;
99import { promises as fs } from 'node:fs' ;
1010import { basename , join } from 'node:path' ;
11+ import { rejects } from 'node:assert/strict' ;
1112import { sync as rimrafSync } from 'rimraf' ;
1213import temp from 'temp' ;
13- import { Sketch , SketchesService } from '../../common/protocol' ;
14+ import { Sketch , SketchesError , SketchesService } from '../../common/protocol' ;
1415import {
1516 isAccessibleSketchPath ,
1617 SketchesServiceImpl ,
@@ -138,12 +139,31 @@ describe('sketches-service-impl', () => {
138139
139140 after ( ( ) => toDispose . dispose ( ) ) ;
140141
141- describe ( 'copy' , ( ) => {
142- it ( 'should copy a sketch when the destination does not exist' , async function ( ) {
143- this . timeout ( testTimeout ) ;
142+ describe ( 'copy' , function ( ) {
143+ this . timeout ( testTimeout ) ;
144+ this . slow ( 250 ) ;
145+
146+ it ( 'should error when the destination sketch folder name is invalid' , async ( ) => {
147+ const sketchesService =
148+ container . get < SketchesServiceImpl > ( SketchesService ) ;
149+ const tempDirPath = await sketchesService [ 'createTempFolder' ] ( ) ;
150+ const destinationPath = join ( tempDirPath , 'invalid with spaces' ) ;
151+ const sketch = await sketchesService . createNewSketch ( ) ;
152+ toDispose . push ( disposeSketch ( sketch ) ) ;
153+ await rejects (
154+ sketchesService . copy ( sketch , {
155+ destinationUri : FileUri . create ( destinationPath ) . toString ( ) ,
156+ } ) ,
157+ SketchesError . InvalidFolderName . is
158+ ) ;
159+ } ) ;
160+
161+ it ( 'should copy a sketch when the destination does not exist' , async ( ) => {
144162 const sketchesService =
145163 container . get < SketchesServiceImpl > ( SketchesService ) ;
146- const destinationPath = await sketchesService [ 'createTempFolder' ] ( ) ;
164+ const tempDirPath = await sketchesService [ 'createTempFolder' ] ( ) ;
165+ const destinationPath = join ( tempDirPath , 'Does_Not_Exist_but_valid' ) ;
166+ await rejects ( fs . readdir ( destinationPath ) , ErrnoException . isENOENT ) ;
147167 let sketch = await sketchesService . createNewSketch ( ) ;
148168 toDispose . push ( disposeSketch ( sketch ) ) ;
149169 const sourcePath = FileUri . fsPath ( sketch . uri ) ;
@@ -187,11 +207,11 @@ describe('sketches-service-impl', () => {
187207 ) . to . be . true ;
188208 } ) ;
189209
190- it ( "should copy only sketch files if 'onlySketchFiles' is true" , async function ( ) {
191- this . timeout ( testTimeout ) ;
210+ it ( "should copy only sketch files if 'onlySketchFiles' is true" , async ( ) => {
192211 const sketchesService =
193212 container . get < SketchesServiceImpl > ( SketchesService ) ;
194- const destinationPath = await sketchesService [ 'createTempFolder' ] ( ) ;
213+ const tempDirPath = await sketchesService [ 'createTempFolder' ] ( ) ;
214+ const destinationPath = join ( tempDirPath , 'OnlySketchFiles' ) ;
195215 let sketch = await sketchesService . createNewSketch ( ) ;
196216 toDispose . push ( disposeSketch ( sketch ) ) ;
197217 const sourcePath = FileUri . fsPath ( sketch . uri ) ;
@@ -207,11 +227,25 @@ describe('sketches-service-impl', () => {
207227 const logContent = 'log file content' ;
208228 const logPath = join ( sourcePath , logBasename ) ;
209229 await fs . writeFile ( logPath , logContent , { encoding : 'utf8' } ) ;
230+ const srcPath = join ( sourcePath , 'src' ) ;
231+ await fs . mkdir ( srcPath , { recursive : true } ) ;
232+ const libInSrcBasename = 'lib_in_src.cpp' ;
233+ const libInSrcContent = 'lib in src content' ;
234+ const libInSrcPath = join ( srcPath , libInSrcBasename ) ;
235+ await fs . writeFile ( libInSrcPath , libInSrcContent , { encoding : 'utf8' } ) ;
236+ const logInSrcBasename = 'inols-clangd-err_in_src.log' ;
237+ const logInSrcContent = 'log file content in src' ;
238+ const logInSrcPath = join ( srcPath , logInSrcBasename ) ;
239+ await fs . writeFile ( logInSrcPath , logInSrcContent , { encoding : 'utf8' } ) ;
210240
211241 sketch = await sketchesService . loadSketch ( sketch . uri ) ;
212242 expect ( Sketch . isInSketch ( FileUri . create ( libPath ) , sketch ) ) . to . be . true ;
213243 expect ( Sketch . isInSketch ( FileUri . create ( headerPath ) , sketch ) ) . to . be . true ;
214244 expect ( Sketch . isInSketch ( FileUri . create ( logPath ) , sketch ) ) . to . be . false ;
245+ expect ( Sketch . isInSketch ( FileUri . create ( libInSrcPath ) , sketch ) ) . to . be
246+ . true ;
247+ expect ( Sketch . isInSketch ( FileUri . create ( logInSrcPath ) , sketch ) ) . to . be
248+ . false ;
215249 const reloadedLogContent = await fs . readFile ( logPath , {
216250 encoding : 'utf8' ,
217251 } ) ;
@@ -249,20 +283,25 @@ describe('sketches-service-impl', () => {
249283 copied
250284 )
251285 ) . to . be . false ;
252- try {
253- await fs . readFile ( join ( destinationPath , logBasename ) , {
254- encoding : 'utf8' ,
255- } ) ;
256- expect . fail (
257- 'Log file must not exist in the destination. Expected ENOENT when loading the log file.'
258- ) ;
259- } catch ( err ) {
260- expect ( ErrnoException . isENOENT ( err ) ) . to . be . true ;
261- }
286+ expect (
287+ Sketch . isInSketch (
288+ FileUri . create ( join ( destinationPath , 'src' , libInSrcBasename ) ) ,
289+ copied
290+ )
291+ ) . to . be . true ;
292+ expect (
293+ Sketch . isInSketch (
294+ FileUri . create ( join ( destinationPath , 'src' , logInSrcBasename ) ) ,
295+ copied
296+ )
297+ ) . to . be . false ;
298+ await rejects (
299+ fs . readFile ( join ( destinationPath , logBasename ) ) ,
300+ ErrnoException . isENOENT
301+ ) ;
262302 } ) ;
263303
264- it ( 'should copy sketch inside the sketch folder' , async function ( ) {
265- this . timeout ( testTimeout ) ;
304+ it ( 'should copy sketch inside the sketch folder' , async ( ) => {
266305 const sketchesService =
267306 container . get < SketchesServiceImpl > ( SketchesService ) ;
268307 let sketch = await sketchesService . createNewSketch ( ) ;
@@ -309,6 +348,55 @@ describe('sketches-service-impl', () => {
309348 ) . to . be . true ;
310349 } ) ;
311350
351+ it ( 'should not modify the subfolder structure' , async ( ) => {
352+ const sketchesService =
353+ container . get < SketchesServiceImpl > ( SketchesService ) ;
354+ const tempDirPath = await sketchesService [ 'createTempFolder' ] ( ) ;
355+ const destinationPath = join ( tempDirPath , 'HasSubfolders_copy' ) ;
356+ await fs . mkdir ( destinationPath , { recursive : true } ) ;
357+ let sketch = await sketchesService . createNewSketch ( 'HasSubfolders' ) ;
358+ toDispose . push ( disposeSketch ( sketch ) ) ;
359+
360+ const sourcePath = FileUri . fsPath ( sketch . uri ) ;
361+ const srcPath = join ( sourcePath , 'src' ) ;
362+ await fs . mkdir ( srcPath , { recursive : true } ) ;
363+ const headerPath = join ( srcPath , 'FomSubfolder.h' ) ;
364+ await fs . writeFile ( headerPath , '// empty' , { encoding : 'utf8' } ) ;
365+
366+ sketch = await sketchesService . loadSketch ( sketch . uri ) ;
367+
368+ expect ( sketch . mainFileUri ) . to . be . equal (
369+ FileUri . create ( join ( sourcePath , 'HasSubfolders.ino' ) ) . toString ( )
370+ ) ;
371+ expect ( sketch . additionalFileUris ) . to . be . deep . equal ( [
372+ FileUri . create ( join ( srcPath , 'FomSubfolder.h' ) ) . toString ( ) ,
373+ ] ) ;
374+ expect ( sketch . otherSketchFileUris ) . to . be . empty ;
375+ expect ( sketch . rootFolderFileUris ) . to . be . empty ;
376+
377+ const destinationUri = FileUri . create ( destinationPath ) . toString ( ) ;
378+ const copySketch = await sketchesService . copy ( sketch , { destinationUri } ) ;
379+ toDispose . push ( disposeSketch ( copySketch ) ) ;
380+ expect ( copySketch . mainFileUri ) . to . be . equal (
381+ FileUri . create (
382+ join ( destinationPath , 'HasSubfolders_copy.ino' )
383+ ) . toString ( )
384+ ) ;
385+ expect ( copySketch . additionalFileUris ) . to . be . deep . equal ( [
386+ FileUri . create (
387+ join ( destinationPath , 'src' , 'FomSubfolder.h' )
388+ ) . toString ( ) ,
389+ ] ) ;
390+ expect ( copySketch . otherSketchFileUris ) . to . be . empty ;
391+ expect ( copySketch . rootFolderFileUris ) . to . be . empty ;
392+
393+ const actualHeaderContent = await fs . readFile (
394+ join ( destinationPath , 'src' , 'FomSubfolder.h' ) ,
395+ { encoding : 'utf8' }
396+ ) ;
397+ expect ( actualHeaderContent ) . to . be . equal ( '// empty' ) ;
398+ } ) ;
399+
312400 it ( 'should copy sketch with overwrite when source and destination sketch folder names are the same' , async function ( ) {
313401 this . timeout ( testTimeout ) ;
314402 const sketchesService =
@@ -346,7 +434,7 @@ describe('sketches-service-impl', () => {
346434 [
347435 '<' ,
348436 '>' ,
349- 'chevrons ' ,
437+ 'lt+gt ' ,
350438 {
351439 predicate : ( ) => isWindows ,
352440 why : '< (less than) and > (greater than) are reserved characters on Windows (https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions)' ,
0 commit comments