From a3c74d04d623d09579dc2448080ac373e36edf49 Mon Sep 17 00:00:00 2001 From: Prakash Meena Date: Thu, 17 Sep 2026 04:29:50 +0530 Subject: [PATCH] fix(io): honor CRLF option when saving text files in save() --- src/io/files.js | 8 ++++++-- test/unit/io/files.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/io/files.js b/src/io/files.js index 9a55d6121f..53d9920001 100644 --- a/src/io/files.js +++ b/src/io/files.js @@ -1669,13 +1669,13 @@ function files(p5, fn) { fn.saveJSON(args[0], args[1], args[2]); return; case 'txt': - fn.saveStrings(args[0], args[1], args[2]); + fn.saveStrings(args[0], args[1], args[2], args[3]); return; // ================================================= // OPTION 3: decide based on object... default: if (args[0] instanceof Array) { - fn.saveStrings(args[0], args[1], args[2]); + fn.saveStrings(args[0], args[1], args[2], args[3]); } else if (args[0] instanceof p5.Table) { fn.saveTable(args[0], args[1], args[2]); } else if (args[0] instanceof p5.Image) { @@ -1938,6 +1938,10 @@ function files(p5, fn) { */ fn.saveStrings = function (list, filename, extension, isCRLF) { // p5._validateParameters('saveStrings', arguments); + if (typeof extension === 'boolean') { + isCRLF = extension; + extension = undefined; + } const ext = extension || 'txt'; const pWriter = new p5.PrintWriter(filename, ext); for (let item of list) { diff --git a/test/unit/io/files.js b/test/unit/io/files.js index 1c30776b8c..33803076d6 100644 --- a/test/unit/io/files.js +++ b/test/unit/io/files.js @@ -91,6 +91,17 @@ suite('Files', function () { expect(URL.createObjectURL).toHaveBeenCalledWith(saveData); assert.equal(mockAnchorElement.download, 'myfile.txt'); }); + + test('should download a file with expected contents with CRLF when extension is omitted', async () => { + const strings = ['some', 'words']; + mockP5Prototype.saveStrings(strings, 'myfile.txt', true); + + const saveData = new Blob([strings.join('\r\n')]); + expect(document.createElement).toHaveBeenCalledTimes(1); + expect(mockAnchorElement.click).toHaveBeenCalledTimes(1); + expect(URL.createObjectURL).toHaveBeenCalledWith(saveData); + assert.equal(mockAnchorElement.download, 'myfile.txt'); + }); }); // saveJSON() @@ -191,6 +202,28 @@ suite('Files', function () { assert.equal(mockAnchorElement.download, 'filename.txt'); }); + test('should download a text file with CRLF when isCRLF is passed as 4th arg', async () => { + const myStrings = ['aaa', 'bbb']; + mockP5Prototype.save(myStrings, 'filename', 'txt', true); + + const saveData = new Blob([myStrings.join('\r\n')]); + expect(document.createElement).toHaveBeenCalledTimes(1); + expect(mockAnchorElement.click).toHaveBeenCalledTimes(1); + expect(URL.createObjectURL).toHaveBeenCalledWith(saveData); + assert.equal(mockAnchorElement.download, 'filename.txt'); + }); + + test('should download a text file with CRLF when isCRLF is passed as 3rd arg with extension in filename', async () => { + const myStrings = ['aaa', 'bbb']; + mockP5Prototype.save(myStrings, 'filename.txt', true); + + const saveData = new Blob([myStrings.join('\r\n')]); + expect(document.createElement).toHaveBeenCalledTimes(1); + expect(mockAnchorElement.click).toHaveBeenCalledTimes(1); + expect(URL.createObjectURL).toHaveBeenCalledWith(saveData); + assert.equal(mockAnchorElement.download, 'filename.txt'); + }); + test('should download a json file', async () => { const myObj = { hi: 'hello' }; mockP5Prototype.save(myObj, 'filename.json');