diff --git a/src/renderer/controllers/torrent-list-controller.js b/src/renderer/controllers/torrent-list-controller.js index 9aeb06cb..8bfc2b48 100644 --- a/src/renderer/controllers/torrent-list-controller.js +++ b/src/renderer/controllers/torrent-list-controller.js @@ -215,11 +215,41 @@ module.exports = class TorrentListController { menu.append(new electron.remote.MenuItem({ label: 'Save Torrent File As...', - click: () => saveTorrentFileAs(torrentSummary) + click: () => dispatch('saveTorrentFileAs', torrentSummary.torrentKey) })) menu.popup(electron.remote.getCurrentWindow()) } + + // Takes a torrentSummary or torrentKey + // Shows a Save File dialog, then saves the .torrent file wherever the user requests + saveTorrentFileAs (torrentKey) { + const torrentSummary = TorrentSummary.getByKey(this.state, torrentKey) + if (!torrentSummary) throw new Error('Missing torrentKey: ' + torrentKey) + const downloadPath = this.state.saved.prefs.downloadPath + const newFileName = path.parse(torrentSummary.name).name + '.torrent' + const win = electron.remote.getCurrentWindow() + const opts = { + title: 'Save Torrent File', + defaultPath: path.join(downloadPath, newFileName), + filters: [ + { name: 'Torrent Files', extensions: ['torrent'] }, + { name: 'All Files', extensions: ['*'] } + ] + } + + electron.remote.dialog.showSaveDialog(win, opts, function (savePath) { + console.log('Saving torrent ' + torrentKey + ' to ' + savePath) + if (!savePath) return // They clicked Cancel + const torrentPath = TorrentSummary.getTorrentPath(torrentSummary) + fs.readFile(torrentPath, function (err, torrentFile) { + if (err) return dispatch('error', err) + fs.writeFile(savePath, torrentFile, function (err) { + if (err) return dispatch('error', err) + }) + }) + }) + } } // Recursively finds {name, path, size} for all files in a folder @@ -280,27 +310,3 @@ function moveItemToTrash (torrentSummary) { function showItemInFolder (torrentSummary) { ipcRenderer.send('showItemInFolder', TorrentSummary.getFileOrFolder(torrentSummary)) } - -function saveTorrentFileAs (torrentSummary) { - const downloadPath = this.state.saved.prefs.downloadPath - const newFileName = path.parse(torrentSummary.name).name + '.torrent' - const opts = { - title: 'Save Torrent File', - defaultPath: path.join(downloadPath, newFileName), - filters: [ - { name: 'Torrent Files', extensions: ['torrent'] }, - { name: 'All Files', extensions: ['*'] } - ] - } - const win = electron.remote.getCurrentWindow() - electron.remote.dialog.showSaveDialog(win, opts, function (savePath) { - if (!savePath) return // They clicked Cancel - const torrentPath = TorrentSummary.getTorrentPath(torrentSummary) - fs.readFile(torrentPath, function (err, torrentFile) { - if (err) return dispatch('error', err) - fs.writeFile(savePath, torrentFile, function (err) { - if (err) return dispatch('error', err) - }) - }) - }) -} diff --git a/src/renderer/main.js b/src/renderer/main.js index aa99deec..3c2cbdf0 100644 --- a/src/renderer/main.js +++ b/src/renderer/main.js @@ -194,6 +194,8 @@ const dispatchHandlers = { controllers.torrentList.openTorrentContextMenu(infoHash), 'startTorrentingSummary': (torrentKey) => controllers.torrentList.startTorrentingSummary(torrentKey), + 'saveTorrentFileAs': (torrentKey) => + controllers.torrentList.saveTorrentFileAs(torrentKey), // Playback 'playFile': (infoHash, index) => controllers.playback.playFile(infoHash, index), diff --git a/src/renderer/pages/create-torrent-page.js b/src/renderer/pages/create-torrent-page.js index 687ddfb9..357295bf 100644 --- a/src/renderer/pages/create-torrent-page.js +++ b/src/renderer/pages/create-torrent-page.js @@ -99,14 +99,14 @@ class CreateTorrentPage extends React.Component {
diff --git a/test/config.js b/test/config.js new file mode 100644 index 00000000..e1a5dc1c --- /dev/null +++ b/test/config.js @@ -0,0 +1,14 @@ +const path = require('path') + +const TEST_DIR = path.join(__dirname, 'tempTestData') +const TEST_DIR_DOWNLOAD = path.join(TEST_DIR, 'Downloads') +const TEST_DIR_DESKTOP = path.join(TEST_DIR, 'Desktop') + +module.exports = { + TORRENT_FILES: [path.join(__dirname, 'resources', '1.torrent')], + SEED_FILES: [path.join(TEST_DIR_DESKTOP, 'tmp.jpg')], + SAVED_TORRENT_FILE: path.join(TEST_DIR_DESKTOP, 'saved.torrent'), + TEST_DIR, + TEST_DIR_DOWNLOAD, + TEST_DIR_DESKTOP +} diff --git a/test/index.js b/test/index.js index 7373b84f..34362b5d 100644 --- a/test/index.js +++ b/test/index.js @@ -1,17 +1,11 @@ const test = require('tape') -const fs = require('fs-extra') const setup = require('./setup') -console.log('Creating download dir: ' + setup.TEST_DOWNLOAD_DIR) -fs.mkdirpSync(setup.TEST_DOWNLOAD_DIR) - -test.onFinish(function () { - console.log('Removing test dir: ' + setup.TEST_DATA_DIR) - fs.removeSync(setup.TEST_DATA_DIR) // includes download dir -}) +test.onFinish(setup.deleteTestDataDir) test('app runs', function (t) { t.timeoutAfter(10e3) + setup.resetTestDataDir() const app = setup.createApp() setup.waitForLoad(app, t) .then(() => setup.wait()) @@ -20,12 +14,11 @@ test('app runs', function (t) { (err) => setup.endTest(app, t, err || 'error')) }) -// require('./test-torrent-list') +require('./test-torrent-list') require('./test-add-torrent') // TODO: -// require('./test-create-torrent') -// require('./test-prefs') // require('./test-video') // require('./test-audio') // require('./test-cast') +// require('./test-prefs') diff --git a/test/mocks.js b/test/mocks.js index 730d1dbc..6a27b937 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -1,9 +1,15 @@ -const path = require('path') const electron = require('electron') +const config = require('./config') -const MOCK_OPEN_TORRENTS = [path.join(__dirname, 'resources', '1.torrent')] - -console.log('Mocking electron native integrations...') +console.log('Mocking electron.dialog.showOpenDialog...') electron.dialog.showOpenDialog = function (win, opts, cb) { - cb(MOCK_OPEN_TORRENTS) + const ret = /select.*torrent file/i.test(opts.title) + ? config.TORRENT_FILES + : config.SEED_FILES + cb(ret) +} + +console.log('Mocking electron.remote.dialog.showSaveDialog...') +electron.dialog.showSaveDialog = function (win, opts, cb) { + cb(config.SAVED_TORRENT_FILE) } diff --git a/test/resources/expected-single-file.torrent b/test/resources/expected-single-file.torrent new file mode 100644 index 00000000..920c02dc Binary files /dev/null and b/test/resources/expected-single-file.torrent differ diff --git a/test/screenshots/darwin/add-torrent-0-percent.png b/test/screenshots/darwin/add-torrent-0-percent.png new file mode 100644 index 00000000..bcd3d551 Binary files /dev/null and b/test/screenshots/darwin/add-torrent-0-percent.png differ diff --git a/test/screenshots/darwin/add-torrent-100-percent.png b/test/screenshots/darwin/add-torrent-100-percent.png new file mode 100644 index 00000000..85356bd9 Binary files /dev/null and b/test/screenshots/darwin/add-torrent-100-percent.png differ diff --git a/test/screenshots/darwin/add-torrent-existing-1.png b/test/screenshots/darwin/add-torrent-existing-1.png deleted file mode 100644 index 93a24ac8..00000000 Binary files a/test/screenshots/darwin/add-torrent-existing-1.png and /dev/null differ diff --git a/test/screenshots/darwin/add-torrent-existing-2.png b/test/screenshots/darwin/add-torrent-existing-2.png deleted file mode 100644 index 4b627997..00000000 Binary files a/test/screenshots/darwin/add-torrent-existing-2.png and /dev/null differ diff --git a/test/screenshots/darwin/create-torrent-100-percent.png b/test/screenshots/darwin/create-torrent-100-percent.png new file mode 100644 index 00000000..26087fc8 Binary files /dev/null and b/test/screenshots/darwin/create-torrent-100-percent.png differ diff --git a/test/screenshots/darwin/create-torrent-advanced.png b/test/screenshots/darwin/create-torrent-advanced.png new file mode 100644 index 00000000..e02233ea Binary files /dev/null and b/test/screenshots/darwin/create-torrent-advanced.png differ diff --git a/test/screenshots/darwin/create-torrent-simple.png b/test/screenshots/darwin/create-torrent-simple.png new file mode 100644 index 00000000..5c10b836 Binary files /dev/null and b/test/screenshots/darwin/create-torrent-simple.png differ diff --git a/test/setup.js b/test/setup.js index 4e6d179c..5f36d6a1 100644 --- a/test/setup.js +++ b/test/setup.js @@ -1,20 +1,20 @@ const path = require('path') const Application = require('spectron').Application const fs = require('fs-extra') - -const TEST_DATA_DIR = path.join(__dirname, 'tempTestData') -const TEST_DOWNLOAD_DIR = path.join(TEST_DATA_DIR, 'Downloads') +const parseTorrent = require('parse-torrent') +const config = require('./config') module.exports = { - TEST_DATA_DIR, - TEST_DOWNLOAD_DIR, createApp, endTest, screenshotCreateOrCompare, compareDownloadFolder, + compareFiles, + compareTorrentFiles, waitForLoad, wait, - wipeTestDataDir + resetTestDataDir, + deleteTestDataDir } // Runs WebTorrent Desktop. @@ -92,16 +92,22 @@ function screenshotCreateOrCompare (app, t, name) { } // Resets the test directory, containing config.json, torrents, downloads, etc -function wipeTestDataDir () { - fs.removeSync(TEST_DATA_DIR) - fs.mkdirpSync(TEST_DOWNLOAD_DIR) // Downloads/ is inside of TEST_DATA_DIR +function resetTestDataDir () { + fs.removeSync(config.TEST_DIR) + // Create TEST_DIR as well as /Downloads and /Desktop + fs.mkdirpSync(config.TEST_DIR_DOWNLOAD) + fs.mkdirpSync(config.TEST_DIR_DESKTOP) +} + +function deleteTestDataDir () { + fs.removeSync(config.TEST_DIR) } // Checks a given folder under Downloads. // Makes sure that the filenames match exactly. // If `filenames` is null, asserts that the folder doesn't exist. function compareDownloadFolder (t, dirname, filenames) { - const dirpath = path.join(TEST_DOWNLOAD_DIR, dirname) + const dirpath = path.join(config.TEST_DIR_DOWNLOAD, dirname) try { const actualFilenames = fs.readdirSync(dirpath) const expectedSorted = filenames.slice().sort() @@ -116,3 +122,26 @@ function compareDownloadFolder (t, dirname, filenames) { } } } + +// Makes sure two files have identical contents +function compareFiles (t, pathActual, pathExpected) { + const bufActual = fs.readFileSync(pathActual) + const bufExpected = fs.readFileSync(pathExpected) + const match = Buffer.compare(bufActual, bufExpected) === 0 + t.ok(match, 'correct contents: ' + pathActual) +} + +// Makes sure two torrents have the same infohash and flags +function compareTorrentFiles (t, pathActual, pathExpected) { + const bufActual = fs.readFileSync(pathActual) + const bufExpected = fs.readFileSync(pathExpected) + const fieldsActual = extractImportantFields(parseTorrent(bufActual)) + const fieldsExpected = extractImportantFields(parseTorrent(bufExpected)) + t.deepEqual(fieldsActual, fieldsExpected, 'torrent contents: ' + pathActual) +} + +function extractImportantFields (parsedTorrent) { + const { infoHash, name, announce, urlList, comment } = parsedTorrent + const priv = parsedTorrent.private + return { infoHash, name, announce, urlList, comment, 'private': priv } +} diff --git a/test/test-add-torrent.js b/test/test-add-torrent.js index 07782ba8..f234cc24 100644 --- a/test/test-add-torrent.js +++ b/test/test-add-torrent.js @@ -2,34 +2,68 @@ const test = require('tape') const fs = require('fs-extra') const path = require('path') const setup = require('./setup') +const config = require('./config') test('add-torrent', function (t) { - setup.wipeTestDataDir() + setup.resetTestDataDir() - t.timeoutAfter(100e3) + t.timeoutAfter(30e3) const app = setup.createApp() setup.waitForLoad(app, t) .then(() => app.client.waitUntilTextExists('.torrent-list', 'Big Buck Bunny')) // Add an existing torrent. The corresponding file is not present. Should be at 0% - .then(() => app.client.click('.icon.add')) + .then(() => app.electron.ipcRenderer.send('openTorrentFile')) // The call to dialog.openFiles() is mocked. See mocks.js .then(() => app.client.waitUntilTextExists('m3.jpg')) - .then(() => setup.screenshotCreateOrCompare(app, t, 'add-torrent-existing-1')) + .then(() => setup.screenshotCreateOrCompare(app, t, 'add-torrent-0-percent')) // Delete the torrent. .then(() => app.client.moveToObject('.torrent')) .then(() => setup.wait()) .then(() => app.client.click('.icon.delete')) .then(() => app.client.waitUntilTextExists('REMOVE')) .then(() => app.client.click('.control.ok')) - .then(() => setup.wait()) // Add the same existing torrent, this time with the file present. Should be at 100% .then(() => fs.copySync( path.join(__dirname, 'resources', 'm3.jpg'), - path.join(setup.TEST_DOWNLOAD_DIR, 'm3.jpg'))) - .then(() => app.client.click('.icon.add')) + path.join(config.TEST_DIR_DOWNLOAD, 'm3.jpg'))) + .then(() => app.electron.ipcRenderer.send('openTorrentFile')) .then(() => app.client.waitUntilTextExists('m3.jpg')) .then(() => setup.wait()) - .then(() => setup.screenshotCreateOrCompare(app, t, 'add-torrent-existing-2')) + .then(() => setup.screenshotCreateOrCompare(app, t, 'add-torrent-100-percent')) + .then(() => setup.endTest(app, t), + (err) => setup.endTest(app, t, err || 'error')) +}) + +test('create-torrent', function (t) { + setup.resetTestDataDir() + + // Set up the files to seed + fs.copySync(path.join(__dirname, 'resources', 'm3.jpg'), config.SEED_FILES[0]) + + t.timeoutAfter(30e3) + const app = setup.createApp() + setup.waitForLoad(app, t) + .then(() => app.client.waitUntilTextExists('.torrent-list', 'Big Buck Bunny')) + // Click the + button, open a non-torrent file to seed + .then(() => app.client.click('.icon.add')) + .then(() => app.client.waitUntilTextExists('Create')) + .then(() => setup.screenshotCreateOrCompare(app, t, 'create-torrent-simple')) + // Click to show advanced settings + .then(() => app.client.click('.show-more .control')) + .then(() => app.client.waitUntilTextExists('Comment')) + .then(() => setup.screenshotCreateOrCompare(app, t, 'create-torrent-advanced')) + // Click OK to create the torrent + .then(() => app.client.click('.control.create-torrent')) + .then(() => app.client.waitUntilTextExists('tmp.jpg')) + .then(() => setup.screenshotCreateOrCompare(app, t, 'create-torrent-100-percent')) + // Click "Save Torrent File As..." on the new torrent + .then(() => app.webContents.executeJavaScript( + 'dispatch("saveTorrentFileAs", 6)')) + .then(() => setup.wait()) + // Mock saves to /Desktop/saved.torrent + .then(() => setup.compareTorrentFiles(t, + config.SAVED_TORRENT_FILE, + path.join(__dirname, 'resources', 'expected-single-file.torrent'))) .then(() => setup.endTest(app, t), (err) => setup.endTest(app, t, err || 'error')) }) diff --git a/test/test-torrent-list.js b/test/test-torrent-list.js index 981cdc1e..e68e5e98 100644 --- a/test/test-torrent-list.js +++ b/test/test-torrent-list.js @@ -1,10 +1,11 @@ const test = require('tape') const fs = require('fs-extra') const setup = require('./setup') +const config = require('./config') test('torrent-list: show download path missing', function (t) { - setup.wipeTestDataDir() - fs.removeSync(setup.TEST_DOWNLOAD_DIR) + setup.resetTestDataDir() + fs.removeSync(config.TEST_DIR_DOWNLOAD) t.timeoutAfter(10e3) const app = setup.createApp() @@ -24,7 +25,7 @@ test('torrent-list: show download path missing', function (t) { }) test('torrent-list: start, stop, and delete torrents', function (t) { - setup.wipeTestDataDir() + setup.resetTestDataDir() const app = setup.createApp() setup.waitForLoad(app, t, {offline: true}) @@ -35,7 +36,7 @@ test('torrent-list: start, stop, and delete torrents', function (t) { .then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-hover')) // Click download on the first torrent, start downloading .then(() => app.client.click('.icon.download')) - .then(() => app.client.waitUntilTextExists('.torrent-list', '276 MB')) + .then(() => app.client.waitUntilTextExists('.torrent-list', 'peer')) .then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-start-download')) // Click download on the first torrent again, stop downloading .then(() => app.client.click('.icon.download')) @@ -62,7 +63,7 @@ test('torrent-list: start, stop, and delete torrents', function (t) { }) test('torrent-list: expand torrent, unselect file', function (t) { - setup.wipeTestDataDir() + setup.resetTestDataDir() const app = setup.createApp() setup.waitForLoad(app, t, {offline: true})