@@ -273,7 +273,7 @@ describe("afterSaveMIDI", () => {
273273 global . activity = {
274274 logo : {
275275 _midiData : {
276- "0" : [
276+ 0 : [
277277 {
278278 note : [ "G4" ] ,
279279 duration : 4 ,
@@ -514,8 +514,13 @@ describe("saveWAV & saveABC methods", () => {
514514
515515describe ( "saveLilypond Methods" , ( ) => {
516516 let activity , saveInterface , mockActivity , mockDocById ;
517+ let originalClipboard ;
518+ let originalSecureContext ;
517519
518520 beforeEach ( ( ) => {
521+ jest . useRealTimers ( ) ;
522+ originalClipboard = navigator . clipboard ;
523+ originalSecureContext = window . isSecureContext ;
519524 // Set up the DOM structure
520525 document . body . innerHTML = `
521526 <div id="lilypondModal" style="display: none;">
@@ -553,6 +558,7 @@ describe("saveLilypond Methods", () => {
553558 return { on : jest . fn ( ) } ;
554559 }
555560 return {
561+ 0 : { setSelectionRange : jest . fn ( ) } ,
556562 appendTo : jest . fn ( ) . mockReturnThis ( ) ,
557563 val : jest . fn ( ) . mockReturnThis ( ) ,
558564 select : jest . fn ( ) ,
@@ -661,6 +667,15 @@ describe("saveLilypond Methods", () => {
661667
662668 afterEach ( ( ) => {
663669 jest . clearAllMocks ( ) ;
670+ if ( originalClipboard === undefined ) {
671+ delete navigator . clipboard ;
672+ } else {
673+ navigator . clipboard = originalClipboard ;
674+ }
675+ Object . defineProperty ( window , "isSecureContext" , {
676+ value : originalSecureContext ,
677+ configurable : true
678+ } ) ;
664679 } ) ;
665680
666681 it ( "should save a Lilypond file with default settings" , ( ) => {
@@ -749,6 +764,58 @@ describe("saveLilypond Methods", () => {
749764 // Verify activity.save.download is not called
750765 expect ( activity . save . download ) . not . toHaveBeenCalled ( ) ;
751766 } ) ;
767+
768+ it ( "should show copied message when Clipboard API succeeds" , async ( ) => {
769+ const writeText = jest . fn ( ) . mockResolvedValue ( ) ;
770+ navigator . clipboard = { writeText } ;
771+ Object . defineProperty ( window , "isSecureContext" , { value : true , configurable : true } ) ;
772+
773+ saveInterface . afterSaveLilypondLY ( lydata , filename ) ;
774+ await writeText . mock . results [ 0 ] . value ;
775+
776+ expect ( writeText ) . toHaveBeenCalledWith ( lydata ) ;
777+ expect ( document . execCommand ) . not . toHaveBeenCalled ( ) ;
778+ expect ( mockActivity . textMsg ) . toHaveBeenCalled ( ) ;
779+ } ) ;
780+
781+ it ( "should fall back to legacy copy when Clipboard API fails" , async ( ) => {
782+ const writeText = jest . fn ( ) . mockRejectedValue ( new Error ( "denied" ) ) ;
783+ navigator . clipboard = { writeText } ;
784+ Object . defineProperty ( window , "isSecureContext" , { value : true , configurable : true } ) ;
785+ document . execCommand . mockReturnValue ( true ) ;
786+
787+ saveInterface . afterSaveLilypondLY ( lydata , filename ) ;
788+ await writeText . mock . results [ 0 ] . value . catch ( ( ) => { } ) ;
789+
790+ expect ( writeText ) . toHaveBeenCalledWith ( lydata ) ;
791+ expect ( document . execCommand ) . toHaveBeenCalledWith ( "copy" ) ;
792+ expect ( mockActivity . textMsg ) . toHaveBeenCalled ( ) ;
793+ } ) ;
794+
795+ it ( "should not show copied message when all copy methods fail" , async ( ) => {
796+ const writeText = jest . fn ( ) . mockRejectedValue ( new Error ( "denied" ) ) ;
797+ navigator . clipboard = { writeText } ;
798+ Object . defineProperty ( window , "isSecureContext" , { value : true , configurable : true } ) ;
799+ document . execCommand . mockReturnValue ( false ) ;
800+
801+ saveInterface . afterSaveLilypondLY ( lydata , filename ) ;
802+ await writeText . mock . results [ 0 ] . value . catch ( ( ) => { } ) ;
803+
804+ expect ( writeText ) . toHaveBeenCalledWith ( lydata ) ;
805+ expect ( document . execCommand ) . toHaveBeenCalledWith ( "copy" ) ;
806+ expect ( mockActivity . textMsg ) . not . toHaveBeenCalled ( ) ;
807+ } ) ;
808+
809+ it ( "should use legacy copy when Clipboard API is unavailable" , ( ) => {
810+ delete navigator . clipboard ;
811+ Object . defineProperty ( window , "isSecureContext" , { value : true , configurable : true } ) ;
812+ document . execCommand . mockReturnValue ( true ) ;
813+
814+ saveInterface . afterSaveLilypondLY ( lydata , filename ) ;
815+
816+ expect ( document . execCommand ) . toHaveBeenCalledWith ( "copy" ) ;
817+ expect ( mockActivity . textMsg ) . toHaveBeenCalled ( ) ;
818+ } ) ;
752819} ) ;
753820
754821describe ( "MXML Methods" , ( ) => {
0 commit comments