diff --git a/app/tests/test-file-ops-unification.js b/app/tests/test-file-ops-unification.js new file mode 100644 index 0000000..3d98a28 --- /dev/null +++ b/app/tests/test-file-ops-unification.js @@ -0,0 +1,72 @@ +const { describe, it } = require('node:test'); +const assert = require('node:assert'); +const fs = require('node:fs'); +const path = require('node:path'); + +const indexHtml = path.join(__dirname, '..', 'index.html'); + +describe('file-ops public entry points', () => { + it('built index.html declares doStuff as a top-level function', () => { + const html = fs.readFileSync(indexHtml, 'utf8'); + assert.ok( + /function\s+doStuff\s*\(/.test(html), + 'must contain function doStuff(...)' + ); + }); + + it('built index.html declares undoStuff as a top-level function', () => { + const html = fs.readFileSync(indexHtml, 'utf8'); + assert.ok( + /function\s+undoStuff\s*\(/.test(html), + 'must contain function undoStuff(...)' + ); + }); +}); + +describe('FILE_OPS config table (source-level)', () => { + const fileOpsSrc = fs.readFileSync( + path.join(__dirname, '..', '..', 'src', 'js', 'ui', 'file-ops.js'), + 'utf8' + ); + + it('defines FILE_OPS with enc and dec entries', () => { + assert.ok( + /const\s+FILE_OPS\s*=/.test(fileOpsSrc), + 'must declare FILE_OPS' + ); + assert.ok(fileOpsSrc.includes("enc:"), 'FILE_OPS must have enc entry'); + assert.ok(fileOpsSrc.includes("dec:"), 'FILE_OPS must have dec entry'); + }); + + it('enc entry uses encNewMsg and .filekey suffix', () => { + assert.ok(fileOpsSrc.includes('work: encNewMsg')); + assert.ok(fileOpsSrc.includes("'.filekey'") || fileOpsSrc.includes('".filekey"')); + }); + + it('dec entry uses decMsg and .filekey replacement', () => { + assert.ok(fileOpsSrc.includes('work: decMsg')); + assert.ok(/replace\s*\(\s*['"]\.filekey['"]/.test(fileOpsSrc)); + }); + + it('defines processFileBatch driver', () => { + assert.ok( + /function\s+processFileBatch\s*\(/.test(fileOpsSrc), + 'must declare processFileBatch' + ); + }); +}); + +describe('FILE_OPS filename transforms (extracted behavior)', () => { + const encSuffix = (name) => name + '.filekey'; + const decSuffix = (name) => name.replace('.filekey', ''); + + it('enc then dec restores original filename', () => { + assert.strictEqual(decSuffix(encSuffix('foo.txt')), 'foo.txt'); + assert.strictEqual(decSuffix(encSuffix('report.pdf')), 'report.pdf'); + assert.strictEqual(decSuffix(encSuffix('no-extension')), 'no-extension'); + }); + + it('dec is a no-op on names without .filekey', () => { + assert.strictEqual(decSuffix('plain.txt'), 'plain.txt'); + }); +}); diff --git a/src/js/ui/file-ops.js b/src/js/ui/file-ops.js index 6b22ec0..72ccc73 100644 --- a/src/js/ui/file-ops.js +++ b/src/js/ui/file-ops.js @@ -248,40 +248,58 @@ function handleSharedFile(file_obj) { )(file_array[fc++]); } } -function undoStuff() { - var file_array = current_active_file_array; - if (file_array.length > 0) { - let fc = 0; - let encrypt_status = false; - let status_obj; - status_obj = setStatusMsg(encrypt_status); - status_obj.animator = new set3dotStatusAnimation(status_obj); - (function nextFile(file) { - getFlatFile(file, function(file_contents, filename) { - decMsg(file_contents, function(ret) { - if (ret === null) { - status_obj.animator.clearStatus(); - var params = getErrorParams(); - var html_string = "Failed to unlock file with this key. Please try again."; - htmlWriter(params, html_string, main_inner); - } else { - var new_filename = filename.replace(".filekey", ""); - newDownloadObj(new_filename, ret.decrypted_buff, { - encrypt_status - }, function(ret) { - scrollForFirstFile(fc); - if (fc < file_array.length) - nextFile(file_array[fc++]); - else - status_obj.animator.triggerStatusFinish(status_obj); - }); - } +const FILE_OPS = { + enc: { + work: encNewMsg, + transform: function(ret) { return combineArrayBuffers(ret.salt, ret.encrypted_buff); }, + filename: function(name) { return name + '.filekey'; }, + errorMsg: 'Failed to encrypt file. Please try again.', + encrypt_status: true, + }, + dec: { + work: decMsg, + transform: function(ret) { return ret.decrypted_buff; }, + filename: function(name) { return name.replace('.filekey', ''); }, + errorMsg: 'Failed to unlock file with this key. Please try again.', + encrypt_status: false, + }, +}; + +function processFileBatch(direction) { + const config = FILE_OPS[direction]; + const file_array = current_active_file_array; + if (file_array.length === 0) return; + let fc = 0; + let status_obj = setStatusMsg(config.encrypt_status); + status_obj.animator = new set3dotStatusAnimation(status_obj); + (function nextFile(file) { + getFlatFile(file, function(file_contents, filename) { + config.work(file_contents, function(ret) { + if (ret === null) { + status_obj.animator.clearStatus(); + var html_string = '' + config.errorMsg + ''; + htmlWriter(getErrorParams(), html_string, main_inner); + return; + } + var out_buff = config.transform(ret); + var out_name = config.filename(filename); + newDownloadObj(out_name, out_buff, { + encrypt_status: config.encrypt_status + }, function(ret) { + scrollForFirstFile(fc); + if (fc < file_array.length) + nextFile(file_array[fc++]); + else + status_obj.animator.triggerStatusFinish(status_obj); }); }); - } - )(file_array[fc++]); - } + }); + })(file_array[fc++]); } + +function undoStuff() { processFileBatch('dec'); } + +function doStuff() { processFileBatch('enc'); } function getWarningParams() { return { char_speed: 2, @@ -339,37 +357,3 @@ function set3dotStatusAnimation(status_obj) { } } } -function doStuff() { - var file_array = current_active_file_array; - if (file_array.length > 0) { - let fc = 0; - let encrypt_status = true; - let status_obj; - status_obj = setStatusMsg(encrypt_status); - status_obj.animator = new set3dotStatusAnimation(status_obj); - (function nextFile(file) { - getFlatFile(file, function(file_contents, filename) { - encNewMsg(file_contents, function(ret) { - if (ret === null) { - status_obj.animator.clearStatus(); - var html_string = "Failed to encrypt file. Please try again."; - htmlWriter(getErrorParams(), html_string, main_inner); - return; - } - var combined_buff = combineArrayBuffers(ret.salt, ret.encrypted_buff); - var new_filename = filename + ".filekey"; - newDownloadObj(new_filename, combined_buff, { - encrypt_status - }, function(ret) { - scrollForFirstFile(fc); - if (fc < file_array.length) - nextFile(file_array[fc++]); - else - status_obj.animator.triggerStatusFinish(status_obj); - }); - }); - }); - } - )(file_array[fc++]); - } -}