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++]);
- }
-}