From 4c7a7d6d4c49ba2929a998d80faae8c21d56be97 Mon Sep 17 00:00:00 2001 From: Pranava Pai N Date: Sat, 5 Sep 2026 23:20:02 +0530 Subject: [PATCH 1/3] Feat: Added extra argument and test for the save option for text files --- src/io/files.js | 2 +- test/unit/io/files.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/io/files.js b/src/io/files.js index 8dce5b6839..b161541e47 100644 --- a/src/io/files.js +++ b/src/io/files.js @@ -1668,7 +1668,7 @@ 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]); // Takes the third argument return; // ================================================= // OPTION 3: decide based on object... diff --git a/test/unit/io/files.js b/test/unit/io/files.js index 1c30776b8c..35481ab7b2 100644 --- a/test/unit/io/files.js +++ b/test/unit/io/files.js @@ -5,7 +5,7 @@ import { vi } from 'vitest'; const mockAnchorElement = vi.mockObject({ href: null, download: null, - click: () => {} + click: () => { } }); const originalCreateElement = document.createElement; vi.spyOn(document, 'createElement').mockImplementation((...args) => { @@ -191,6 +191,17 @@ suite('Files', function () { assert.equal(mockAnchorElement.download, 'filename.txt'); }); + test('should preserve CRLF when saving a text file', 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'); From 2e331fd69107fd8882ba9b579901aa9f172720b9 Mon Sep 17 00:00:00 2001 From: Pranava Pai N Date: Sat, 19 Sep 2026 12:19:17 +0530 Subject: [PATCH 2/3] Fix: Added an extra argument for txt files for windows along with the tests and the ffedback received --- src/io/files.js | 4 ++-- test/unit/io/files.js | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/io/files.js b/src/io/files.js index b161541e47..fd259a7ea8 100644 --- a/src/io/files.js +++ b/src/io/files.js @@ -1668,13 +1668,13 @@ function files(p5, fn) { fn.saveJSON(args[0], args[1], args[2]); return; case 'txt': - fn.saveStrings(args[0], args[1], args[2], args[3]); // Takes the third argument + 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) { diff --git a/test/unit/io/files.js b/test/unit/io/files.js index 35481ab7b2..99aeb28bc9 100644 --- a/test/unit/io/files.js +++ b/test/unit/io/files.js @@ -191,6 +191,17 @@ suite('Files', function () { assert.equal(mockAnchorElement.download, 'filename.txt'); }); + test('should preserve CRLF when saving a text file without an extension', async () => { + const myStrings = ['aaa', 'bbb']; + mockP5Prototype.save(myStrings, 'filename', undefined, 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 preserve CRLF when saving a text file', async () => { const myStrings = ['aaa', 'bbb']; mockP5Prototype.save(myStrings, 'filename', 'txt', true); From 3474203189745d2c17e2fd51ac4dabedea439638 Mon Sep 17 00:00:00 2001 From: Pranava Pai N Date: Sat, 19 Sep 2026 20:00:45 +0530 Subject: [PATCH 3/3] Feat: Removed fourth parameter for save as per the maintainer requests and redirected to saveStrings --- src/io/files.js | 4 ++-- test/unit/io/files.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/io/files.js b/src/io/files.js index 831f138a0b..612f44b905 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], args[3]); + fn.saveStrings(args[0], args[1], 'txt', args[2]); return; // ================================================= // OPTION 3: decide based on object... default: if (args[0] instanceof Array) { - fn.saveStrings(args[0], args[1], args[2], args[3]); + fn.saveStrings(args[0], args[1], undefined, args[2]); } else if (args[0] instanceof p5.Table) { fn.saveTable(args[0], args[1], args[2]); } else if (args[0] instanceof p5.Image) { diff --git a/test/unit/io/files.js b/test/unit/io/files.js index 99aeb28bc9..5d2af4ea66 100644 --- a/test/unit/io/files.js +++ b/test/unit/io/files.js @@ -193,7 +193,7 @@ suite('Files', function () { test('should preserve CRLF when saving a text file without an extension', async () => { const myStrings = ['aaa', 'bbb']; - mockP5Prototype.save(myStrings, 'filename', undefined, true); + mockP5Prototype.save(myStrings, 'filename', true); const saveData = new Blob([myStrings.join('\r\n')]); expect(document.createElement).toHaveBeenCalledTimes(1); @@ -204,7 +204,7 @@ suite('Files', function () { test('should preserve CRLF when saving a text file', async () => { const myStrings = ['aaa', 'bbb']; - mockP5Prototype.save(myStrings, 'filename', 'txt', true); + mockP5Prototype.save(myStrings, 'filename.txt', true); const saveData = new Blob([myStrings.join('\r\n')]); expect(document.createElement).toHaveBeenCalledTimes(1);