Compare commits

...

31 Commits

Author SHA1 Message Date
Feross Aboukhadijeh
9cdc73edce 0.16.0 2016-09-18 01:56:59 -07:00
Feross Aboukhadijeh
3d254fa075 changelog 2016-09-18 01:41:01 -07:00
Feross Aboukhadijeh
ed1e43015e Merge pull request #931 from feross/detect-arch
Add 64-bit Windows build
2016-09-18 10:37:05 +02:00
Feross Aboukhadijeh
e6cbbd73f0 Fix silly typo 2016-09-18 01:35:20 -07:00
Feross Aboukhadijeh
67dff7b38c Add sanity check 2016-09-18 01:33:51 -07:00
Feross Aboukhadijeh
ced67176a3 add changelog 2016-09-18 01:29:14 -07:00
Feross Aboukhadijeh
00ac8afe64 About window: Show 32-bit vs. 64-bit status 2016-09-18 01:22:16 -07:00
Feross Aboukhadijeh
a6964c4495 Change file name inside RELEASES-ia32 to match renamed file 2016-09-18 01:07:45 -07:00
DC
6541291e0d Integration test: address PR comments 2016-09-17 20:35:54 -07:00
DC
711d274398 Integration test: mock cast, remove loading bar
This lets us use exact screenshots with no transparency.
2016-09-17 20:35:53 -07:00
DC
6c5861b9fc Test screenshots: Create Torrent raised button 2016-09-17 20:35:53 -07:00
DC
f7ab27f9fd Integration test: audio 2016-09-17 20:35:53 -07:00
DC
e4e789cc5b Integration test: screenshot compare ignoring transparency 2016-09-17 20:35:52 -07:00
DC
09b525fe58 Integration test: simplify offline mode 2016-09-17 20:35:52 -07:00
DC
9dabfc1367 Integration test: offline mode 2016-09-17 20:35:52 -07:00
DC
bdb733352a Integration test: video playback 2016-09-17 20:35:52 -07:00
DC
75a4655a0f Integration test: create torrents 2016-09-17 20:35:52 -07:00
DC
051c1516a0 Integration test: add existing torrent 2016-09-17 20:35:51 -07:00
DC
62c5b78358 Integration test: update README 2016-09-17 20:35:51 -07:00
DC
290913d07a Integration test: delete torrent + data 2016-09-17 20:35:51 -07:00
Feross Aboukhadijeh
77534d650a Add 64-bit Windows build
Right now all Windows users are running a 32-bit app, even if their OS
is 64-bit.

Here's the plan to improve things:

1. We release a 64-bit installer, in addition to the 32-bit installer.

2. We auto-detect in the browser when a visitor is on a 32-bit vs.
64-bit OS and try to offer them the right installer. When in doubt, we
give them the 32-bit installer since that's safest.

3. The auto-updater will return the right binaries for the architecture
the user is on. This means that all our existing users who have 64-bit
OSs but are running the 32-bit app will get updated to the 64-bit app
on the next update. Also, 64-bit users who accidentally download the
32-bit installer will also get the 64-bit app on the next update.

---

Other notes:

- We don't generate 32-bit delta files. See reasoning inline.
- The package script now deletes extraneous Squirrel files (i.e.
*.nupkg delta files for older versions of the app) which should make
uploading the right files to GitHub easier. :)

The binary file naming works like this:

- Most users are on 64-bit systems, so they get nice clean file names
that don't specify an architecture (WebTorrentSetup-v1.0.0.exe). The
32-bit build files have the same naming but contain the string "-ia32"
at the end. In a few years, we will be able to just stop producing the
32-bit build files entirely.

- This means that the "WebTorrent-v0.15.0-linux-x64.zip" linux build
file is changing to "WebTorrent-v0.15.0-linux.zip" to match the Windows
naming convention. The .deb installer files must contain to
architecture in order to install correctly, so those do not change.

- Mac is 100% 64-bit, so it does not change.
2016-09-17 19:37:50 -07:00
Feross Aboukhadijeh
a4c715e3f6 Merge pull request #928 from feross/detect-arch
Detect system architecture; send in update/telemetry
2016-09-17 16:21:46 -07:00
Feross Aboukhadijeh
7415d3cee5 Detect system architecture; send in update/telemetry
Detect the actual operating system CPU architecture. This is different
than `process.arch` which returns the architecture the binary was
compiled for.

This is just good info to have in the telemetry, but we're also sending
it in the update check so that eventually we can upgrade Windows 32-bit
apps to 64-bit, like Slack does.

Context:
https://github.com/feross/webtorrent-desktop/issues/873#issuecomment-247
722023
2016-09-16 19:24:21 -07:00
Feross Aboukhadijeh
e1ba9c89fe Merge pull request #922 from feross/raised
Use raised button for inline button
2016-09-16 15:45:20 -07:00
Feross Aboukhadijeh
0fcbe7369a Merge pull request #925 from feross/capture-frame
Use capture-frame package
2016-09-16 15:43:20 -07:00
Feross Aboukhadijeh
c8087b5b63 Merge pull request #926 from feross/electron-1.4.0
Electron 1.4.0
2016-09-16 15:42:44 -07:00
Feross Aboukhadijeh
065faca8eb Electron 1.4.0 2016-09-16 10:25:03 -07:00
Feross Aboukhadijeh
bcd6a38a05 Use capture-frame package
See: https://github.com/feross/capture-frame
Capture video screenshot from a `<video>` tag (at the current time)

Changes from our version:

- Added tests in Chrome/Firefox browsers.
- Use built-in TypeError (which is meant for bad arguments) instead of
custom IllegalArgumentError.
2016-09-16 10:14:39 -07:00
DC
fa67f9b82b changelog 2016-09-16 01:11:12 -07:00
DC
39acd0bd47 authors 2016-09-16 01:02:16 -07:00
Feross Aboukhadijeh
a629f287f0 Use raised button for inline button 2016-09-13 08:16:38 -07:00
53 changed files with 608 additions and 165 deletions

View File

@@ -32,5 +32,6 @@
- Vamsi Krishna Avula (vamsi_ism@outlook.com)
- Noam Okman (noamokman@gmail.com)
- PurgingPanda (t3ch0wn3r@gmail.com)
- Kai Curtis (morecode@kcurtis.com)
#### Generated by bin/update-authors.sh.

View File

@@ -1,5 +1,27 @@
# WebTorrent Desktop Version History
## v0.16.0 - 2016-09-18
### Added
- **Windows 64-bit support!** ([#931](https://github.com/feross/webtorrent-desktop/pull/931))
- Existing 32-bit users will update to 64-bit automatically in next release
- 64-bit reduces likelihood of out-of-memory errors by increasing the address space
## v0.15.0 - 2016-09-16
### Added
- Option to start automatically on login
- Add integration tests
- Add more detailed telemetry to diagnose "buffer allocation failed"
### Changed
- Disable playback controls while in external player (#909)
### Fixed
- Fix several uncaught errors (#889, #891, #892)
- Update to the latest webtorrent.js, fixing some more uncaught errors
- Clicking on the "torrent finished" notification works again (#912)
## v0.14.0 - 2016-09-03
### Added

View File

@@ -56,6 +56,28 @@ Restart the app automatically every time code changes. Useful during development
$ npm run watch
```
### Run linters
```
$ npm test
```
### Run integration tests
```
$ npm run integration-test
```
The integration tests use Spectron and Tape. They click through the app, taking screenshots and comparing each one to a reference. Why screenshots?
* Ad-hoc checking makes the tests a lot more work to write
* Even diffing the whole HTML is not as thorough as screenshot diffing. For example, it wouldn't catch an bug where hitting ESC from a video doesn't correctly restore window size.
* Chrome's own integration tests use screenshot diffing iirc
* Small UI changes will break a few tests, but the fix is as easy as deleting the offending screenshots and running the tests, which will recreate them with the new look.
* The resulting Github PR will then show, pixel by pixel, the exact UI changes that were made! Ses https://github.com/blog/817-behold-image-view-modes
For MacOS, you'll need a Retina screen for the integration tests to pass.
### Package the app
Builds app binaries for Mac, Linux, and Windows.

View File

@@ -112,7 +112,7 @@ var darwin = {
// Build for Mac
platform: 'darwin',
// Build 64 bit binaries only.
// Build x64 binaries only.
arch: 'x64',
// The bundle identifier to use in the application's plist (Mac only).
@@ -133,8 +133,8 @@ var win32 = {
// Build for Windows.
platform: 'win32',
// Build 32 bit binaries only.
arch: 'ia32',
// Build ia32 and x64 binaries.
arch: 'all',
// Object hash of application metadata to embed into the executable (Windows only)
'version-string': {
@@ -167,7 +167,7 @@ var linux = {
// Build for Linux.
platform: 'linux',
// Build 32 and 64 bit binaries.
// Build ia32 and x64 binaries.
arch: 'all'
// Note: Application icon for Linux is specified via the BrowserWindow `icon` option.
@@ -388,19 +388,25 @@ function buildWin32 (cb) {
}
var tasks = []
if (argv.package === 'exe' || argv.package === 'all') {
tasks.push((cb) => packageInstaller(cb))
}
if (argv.package === 'portable' || argv.package === 'all') {
tasks.push((cb) => packagePortable(cb))
}
buildPath.forEach(function (filesPath) {
var destArch = filesPath.split('-').pop()
if (argv.package === 'exe' || argv.package === 'all') {
tasks.push((cb) => packageInstaller(filesPath, destArch, cb))
}
if (argv.package === 'portable' || argv.package === 'all') {
tasks.push((cb) => packagePortable(filesPath, destArch, cb))
}
})
series(tasks, cb)
function packageInstaller (cb) {
console.log('Windows: Creating installer...')
function packageInstaller (filesPath, destArch, cb) {
console.log(`Windows: Creating ${destArch} installer...`)
var archStr = destArch === 'ia32' ? '-ia32' : ''
installer.createWindowsInstaller({
appDirectory: buildPath[0],
appDirectory: filesPath,
authors: config.APP_TEAM,
description: config.APP_NAME,
exe: config.APP_NAME + '.exe',
@@ -410,8 +416,21 @@ function buildWin32 (cb) {
noMsi: true,
outputDirectory: DIST_PATH,
productName: config.APP_NAME,
remoteReleases: config.GITHUB_URL,
setupExe: config.APP_NAME + 'Setup-v' + config.APP_VERSION + '.exe',
/**
* Only create delta updates for the Windows x64 build because 90% of our
* users have Windows x64 and the delta files take a *very* long time to
* generate. Also, the ia32 files on GitHub have non-standard Squirrel
* names (i.e. RELEASES-ia32 instead of RELEASES) and so Squirrel won't
* find them unless we proxy the requests.
*/
remoteReleases: destArch === 'x64'
? config.GITHUB_URL
: undefined,
/**
* If you hit a "GitHub API rate limit exceeded" error, set this token!
*/
remoteToken: process.env.WEBTORRENT_GITHUB_API_TOKEN,
setupExe: config.APP_NAME + 'Setup-v' + config.APP_VERSION + archStr + '.exe',
setupIcon: config.APP_ICON + '.ico',
signWithParams: signWithParams,
title: config.APP_NAME,
@@ -419,23 +438,65 @@ function buildWin32 (cb) {
version: pkg.version
})
.then(function () {
console.log('Windows: Created installer.')
console.log(`Windows: Created ${destArch} installer.`)
/**
* Delete extraneous Squirrel files (i.e. *.nupkg delta files for older
* versions of the app)
*/
fs.readdirSync(DIST_PATH)
.filter((name) => name.endsWith('.nupkg') && !name.includes(pkg.version))
.forEach((filename) => {
fs.unlinkSync(path.join(DIST_PATH, filename))
})
if (destArch === 'ia32') {
console.log('Windows: Renaming ia32 installer files...')
// RELEASES -> RELEASES-ia32
var relPath = path.join(DIST_PATH, 'RELEASES-ia32')
fs.renameSync(
path.join(DIST_PATH, 'RELEASES'),
relPath
)
// WebTorrent-vX.X.X-full.nupkg -> WebTorrent-vX.X.X-ia32-full.nupkg
fs.renameSync(
path.join(DIST_PATH, `${BUILD_NAME}-full.nupkg`),
path.join(DIST_PATH, `${BUILD_NAME}-ia32-full.nupkg`)
)
// Change file name inside RELEASES-ia32 to match renamed file
var relContent = fs.readFileSync(relPath, 'utf8')
var relContent32 = relContent.replace(/full\.nupkg$/, '-ia32-full.nupkg')
fs.writeFileSync(relPath, relContent32)
if (relContent === relContent32) {
// Sanity check
throw new Error('Fixing RELEASE-ia32 failed. Replacement did not modify the file.')
}
console.log('Windows: Renamed ia32 installer files.')
}
cb(null)
})
.catch(cb)
}
function packagePortable (cb) {
console.log('Windows: Creating portable app...')
function packagePortable (filesPath, destArch, cb) {
console.log(`Windows: Creating ${destArch} portable app...`)
var portablePath = path.join(buildPath[0], 'Portable Settings')
var portablePath = path.join(filesPath, 'Portable Settings')
mkdirp.sync(portablePath)
var inPath = path.join(DIST_PATH, path.basename(buildPath[0]))
var outPath = path.join(DIST_PATH, BUILD_NAME + '-win.zip')
var archStr = destArch === 'ia32' ? '-ia32' : ''
var inPath = path.join(DIST_PATH, path.basename(filesPath))
var outPath = path.join(DIST_PATH, BUILD_NAME + '-win' + archStr + '.zip')
zip.zipSync(inPath, outPath)
console.log('Windows: Created portable app.')
console.log(`Windows: Created ${destArch} portable app.`)
cb(null)
}
})
@@ -500,8 +561,10 @@ function buildLinux (cb) {
// Create .zip file for Linux
console.log(`Linux: Creating ${destArch} zip...`)
var archStr = destArch === 'ia32' ? '-ia32' : ''
var inPath = path.join(DIST_PATH, path.basename(filesPath))
var outPath = path.join(DIST_PATH, BUILD_NAME + '-linux-' + destArch + '.zip')
var outPath = path.join(DIST_PATH, BUILD_NAME + '-linux' + archStr + '.zip')
zip.zipSync(inPath, outPath)
console.log(`Linux: Created ${destArch} zip.`)

View File

@@ -1,7 +1,7 @@
{
"name": "webtorrent-desktop",
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
"version": "0.15.0",
"version": "0.16.0",
"author": {
"name": "WebTorrent, LLC",
"email": "feross@webtorrent.io",
@@ -18,12 +18,13 @@
"application-config": "^1.0.0",
"auto-launch": "^4.0.1",
"bitfield": "^1.0.2",
"capture-frame": "^1.0.0",
"chromecasts": "^1.8.0",
"create-torrent": "^3.24.5",
"deep-equal": "^1.0.1",
"dlnacasts": "^0.1.0",
"drag-drop": "^2.12.1",
"electron": "1.3.5",
"electron": "1.4.0",
"es6-error": "^3.0.1",
"fs-extra": "^0.30.0",
"iso-639-1": "^1.2.1",
@@ -65,6 +66,7 @@
"nodemon": "^1.10.2",
"open": "0.0.5",
"plist": "^2.0.1",
"pngjs": "^3.0.0",
"rimraf": "^2.5.2",
"run-series": "^1.1.4",
"spectron": "^3.3.0",

View File

@@ -81,6 +81,8 @@ module.exports = {
IS_PRODUCTION: IS_PRODUCTION,
IS_TEST: IS_TEST,
OS_SYSARCH: is64BitOperatingSystem() ? 'x64' : 'ia32',
POSTER_PATH: path.join(getConfigPath(), 'Posters'),
ROOT_PATH: path.join(__dirname, '..'),
STATIC_PATH: path.join(__dirname, '..', 'static'),
@@ -149,3 +151,32 @@ function isProduction () {
return !/\/electron$/.test(process.execPath)
}
}
/**
* Returns the operating system's CPU architecture. This is different than
* `process.arch` which returns the architecture the binary was compiled for.
*
* On Windows, the most reliable way to detect a 64-bit OS from within a 32-bit
* app is based on the presence of a WOW64 file: %SystemRoot%\SysNative.
*
* Background: https://twitter.com/feross/status/776949077208510464
*/
function is64BitOperatingSystem () {
// This is a 64-bit binary, so the OS clearly supports 64-bit apps
if (process.arch === 'x64') return true
let useEnv = false
try {
useEnv = !!(process.env.SYSTEMROOT && fs.statSync(process.env.SYSTEMROOT))
} catch (err) {}
let sysRoot = useEnv ? process.env.SYSTEMROOT : 'C:\\Windows'
// If %SystemRoot%\SysNative exists, we are in a WOW64 FS Redirected application.
let isWOW64 = false
try {
isWOW64 = !!fs.statSync(path.join(sysRoot, 'sysnative'))
} catch (err) {}
return isWOW64
}

View File

@@ -21,12 +21,7 @@ function openSeedFile () {
title: 'Select a file for the torrent.',
properties: [ 'openFile' ]
}
setTitle(opts.title)
electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) {
resetTitle()
if (!Array.isArray(selectedPaths)) return
windows.main.dispatch('showCreateTorrent', selectedPaths)
})
showOpenSeed(opts)
}
/*
@@ -46,12 +41,7 @@ function openSeedDirectory () {
title: 'Select a folder for the torrent.',
properties: [ 'openDirectory' ]
}
setTitle(opts.title)
electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) {
resetTitle()
if (!Array.isArray(selectedPaths)) return
windows.main.dispatch('showCreateTorrent', selectedPaths)
})
showOpenSeed(opts)
}
/*
@@ -119,3 +109,16 @@ function setTitle (title) {
function resetTitle () {
windows.main.dispatch('resetTitle')
}
/**
* Pops up an Open File dialog with the given options.
* After the user selects files / folders, shows the Create Torrent page.
*/
function showOpenSeed (opts) {
setTitle(opts.title)
electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) {
resetTitle()
if (!Array.isArray(selectedPaths)) return
windows.main.dispatch('showCreateTorrent', selectedPaths)
})
}

View File

@@ -147,8 +147,14 @@ function onAppOpen (newArgv) {
}
}
// Remove leading args.
// Production: 1 arg, eg: /Applications/WebTorrent.app/Contents/MacOS/WebTorrent
// Development: 2 args, eg: electron .
// Test: 4 args, eg: electron -r .../mocks.js .
function sliceArgv (argv) {
return argv.slice(config.IS_PRODUCTION ? 1 : 2)
return argv.slice(config.IS_PRODUCTION ? 1
: config.IS_TEST ? 4
: 2)
}
function processArgv (argv) {

View File

@@ -98,14 +98,16 @@ function init () {
/**
* File handlers
*/
ipc.on('setDefaultFileHandler', (e, flag) => {
if (flag) handlers.install()
else handlers.uninstall()
})
/**
* Startup
* Auto start on login
*/
ipc.on('setStartup', (e, flag) => {
if (flag) startup.install()
else startup.uninstall()
@@ -143,7 +145,10 @@ function init () {
ipc.on('quitExternalPlayer', () => externalPlayer.kill())
// Capture all events
/**
* Message passing
*/
const oldEmit = ipc.emit
ipc.emit = function (name, e, ...args) {
// Relay messages between the main window and the WebTorrent hidden window

View File

@@ -11,7 +11,8 @@ const windows = require('./windows')
const AUTO_UPDATE_URL = config.AUTO_UPDATE_URL +
'?version=' + config.APP_VERSION +
'&platform=' + process.platform
'&platform=' + process.platform +
'&sysarch=' + config.OS_SYSARCH
function init () {
if (process.platform === 'linux') {

View File

@@ -1,6 +1,6 @@
const React = require('react')
const FlatButton = require('material-ui/FlatButton').default
const RaisedButton = require('material-ui/RaisedButton').default
class ShowMore extends React.Component {
static get propTypes () {
@@ -39,9 +39,10 @@ class ShowMore extends React.Component {
? this.props.hideLabel
: this.props.showLabel
return (
<div style={this.props.style}>
<div className='show-more' style={this.props.style}>
{this.state.expanded ? this.props.children : null}
<FlatButton
<RaisedButton
className='control'
onClick={this.handleClick}
label={label} />
</div>

View File

@@ -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)
})
})
})
}

View File

@@ -1,32 +0,0 @@
module.exports = captureVideoFrame
const {IllegalArgumentError} = require('./errors')
function captureVideoFrame (video, format) {
if (typeof video === 'string') {
video = document.querySelector(video)
}
if (video == null || video.nodeName !== 'VIDEO') {
throw new IllegalArgumentError('First argument must be a <video> element or selector')
}
if (format == null) {
format = 'png'
}
if (format !== 'png' && format !== 'jpg' && format !== 'webp') {
throw new IllegalArgumentError('Second argument must be one of "png", "jpg", or "webp"')
}
const canvas = document.createElement('canvas')
canvas.width = video.videoWidth
canvas.height = video.videoHeight
canvas.getContext('2d').drawImage(video, 0, 0)
const dataUri = canvas.toDataURL('image/' + format)
const data = dataUri.split(',')[1]
return new Buffer(data, 'base64')
}

View File

@@ -33,6 +33,15 @@ function init (appState, callback) {
state = appState
update = callback
// Don't actually cast during integration tests
// (Otherwise you'd need a physical Chromecast + AppleTV + DLNA TV to run them.)
if (config.IS_TEST) {
state.devices.chromecast = testPlayer('chromecast')
state.devices.airplay = testPlayer('airplay')
state.devices.dlna = testPlayer('dlna')
return
}
// Load modules, scan the network for devices
airplayer = require('airplayer')()
chromecasts = require('chromecasts')()
@@ -58,6 +67,32 @@ function init (appState, callback) {
})
}
// integration test player implementation
function testPlayer (type) {
return {
getDevices,
open,
play,
pause,
stop,
status,
seek,
volume
}
function getDevices () {
return [{name: type + '-1'}, {name: type + '-2'}]
}
function open () {}
function play () {}
function pause () {}
function stop () {}
function status () {}
function seek () {}
function volume () {}
}
// chromecast player implementation
function chromecastPlayer () {
const ret = {

View File

@@ -35,10 +35,6 @@ class TorrentKeyNotFoundError extends TorrentError {
class InvalidTorrentError extends TorrentError {}
/* Miscellaneous */
class IllegalArgumentError extends ExtendableError {}
module.exports = {
CastingError,
PlaybackError,
@@ -49,6 +45,5 @@ module.exports = {
PlaybackTimedOutError,
InvalidSoundNameError,
TorrentKeyNotFoundError,
InvalidTorrentError,
IllegalArgumentError
InvalidTorrentError
}

View File

@@ -105,6 +105,7 @@ function getSystemInfo () {
osPlatform: process.platform,
osRelease: os.type() + ' ' + os.release(),
architecture: os.arch(),
systemArchitecture: config.OS_SYSARCH,
totalMemoryMB: roundPow2(os.totalmem() / (1 << 20)),
numCores: os.cpus().length
}

View File

@@ -1,6 +1,6 @@
module.exports = torrentPoster
const captureVideoFrame = require('./capture-video-frame')
const captureFrame = require('capture-frame')
const path = require('path')
function torrentPoster (torrent, cb) {
@@ -61,7 +61,7 @@ function torrentPosterFromVideo (file, torrent, cb) {
function onSeeked () {
video.removeEventListener('seeked', onSeeked)
const buf = captureVideoFrame(video)
const buf = captureFrame(video)
// unload video element
video.pause()

View File

@@ -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),

View File

@@ -99,14 +99,14 @@ class CreateTorrentPage extends React.Component {
</ShowMore>
<div className='float-right'>
<FlatButton
className='control'
className='control cancel'
label='Cancel'
style={{
marginRight: 10
}}
onClick={dispatcher('cancel')} />
<RaisedButton
className='control'
className='control create-torrent'
label='Create Torrent'
primary
onClick={this.handleSubmit} />

View File

@@ -6,6 +6,7 @@ const zeroFill = require('zero-fill')
const TorrentSummary = require('../lib/torrent-summary')
const Playlist = require('../lib/playlist')
const {dispatch, dispatcher} = require('../lib/dispatcher')
const config = require('../../config')
// Shows a streaming video player. Standard features + Chromecast + Airplay
module.exports = class Player extends React.Component {
@@ -585,6 +586,8 @@ function renderPlayerControls (state) {
// Renders the loading bar. Shows which parts of the torrent are loaded, which
// can be 'spongey' / non-contiguous
function renderLoadingBar (state) {
if (config.IS_TEST) return // Don't integration test the loading bar. Screenshots won't match.
const torrentSummary = state.getPlayingTorrentSummary()
if (!torrentSummary.progress) {
return []

View File

@@ -54,11 +54,18 @@ const VERSION_STR = VERSION.match(/([0-9]+)/g)
*/
const VERSION_PREFIX = '-WD' + VERSION_STR + '-'
/**
* Generate an ephemeral peer ID each time.
* Once there are around 2^24 = ~8 million WebTorrent Desktops online at the same time,
* ID collisions will start happening. Birthday paradox.
* This is fine, though. Bad peers can already clone someone else's peer ID.
* The network is robust to occasional collisions.
*/
const PEER_ID = Buffer.from(VERSION_PREFIX + crypto.randomBytes(6).toString('hex'))
// Connect to the WebTorrent and BitTorrent networks. WebTorrent Desktop is a hybrid
// client, as explained here: https://webtorrent.io/faq
const client = window.client = new WebTorrent({
peerId: Buffer.from(VERSION_PREFIX + crypto.randomBytes(6).toString('hex'))
})
let client = window.client = new WebTorrent({ peerId: PEER_ID })
// WebTorrent-to-HTTP streaming sever
let server = null
@@ -69,8 +76,7 @@ let prevProgress = null
init()
function init () {
client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
client.on('error', (err) => ipc.send('wt-error', null, err.message))
listenToClientEvents()
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
@@ -101,6 +107,11 @@ function init () {
console.timeEnd('init')
}
function listenToClientEvents () {
client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
client.on('error', (err) => ipc.send('wt-error', null, err.message))
}
// Starts a given TorrentID, which can be an infohash, magnet URI, etc.
// Returns a WebTorrent object. See https://git.io/vik9M
function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections) {
@@ -157,7 +168,7 @@ function addTorrentEvents (torrent) {
function torrentReady () {
const info = getTorrentInfo(torrent)
ipc.send('wt-ready', torrent.key, info)
ipc.send('wt-ready-' + torrent.infoHash, torrent.key, info) // TODO: hack
ipc.send('wt-ready-' + torrent.infoHash, torrent.key, info)
updateTorrentProgress()
}
@@ -395,3 +406,17 @@ function getTorrent (torrentKey) {
function onError (err) {
console.log(err)
}
// TODO: remove this once the following bugs are fixed:
// https://bugs.chromium.org/p/chromium/issues/detail?id=490143
// https://github.com/electron/electron/issues/7212
window.testOfflineMode = function () {
console.log('Test, going OFFLINE')
client = window.client = new WebTorrent({
peerId: PEER_ID,
tracker: false,
dht: false,
webSeeds: false
})
listenToClientEvents()
}

View File

@@ -32,6 +32,7 @@
<p>
Version <script>document.write(require('../package.json').version)</script>
(<script>document.write(require('webtorrent/package.json').version)</script>)
(<script>document.write(process.arch === 'x64' ? '64-bit' : '32-bit')</script>)
</p>
<p><script>document.write(require('../config').APP_COPYRIGHT)</script></p>
</body>

14
test/config.js Normal file
View File

@@ -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
}

View File

@@ -1,33 +1,20 @@
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())
.then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-basic'))
.then(() => setup.screenshotCreateOrCompare(app, t, 'app-basic'))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})
console.log('Testing the torrent list (home page)...')
setup.wipeTestDataDir()
require('./test-torrent-list')
// TODO:
// require('./test-add-torrent')
// require('./test-create-torrent')
// require('./test-prefs')
// require('./test-video')
// require('./test-audio')
// require('./test-cast')
require('./test-add-torrent')
require('./test-video')
require('./test-audio')

15
test/mocks.js Normal file
View File

@@ -0,0 +1,15 @@
const electron = require('electron')
const config = require('./config')
console.log('Mocking electron.dialog.showOpenDialog...')
electron.dialog.showOpenDialog = function (win, opts, cb) {
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)
}

BIN
test/resources/1.torrent Normal file

Binary file not shown.

Binary file not shown.

BIN
test/resources/m3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -1,20 +1,21 @@
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 PNG = require('pngjs').PNG
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.
@@ -24,7 +25,7 @@ function createApp (t) {
return new Application({
path: path.join(__dirname, '..', 'node_modules', '.bin',
'electron' + (process.platform === 'win32' ? '.cmd' : '')),
args: [path.join(__dirname, '..')],
args: ['-r', path.join(__dirname, 'mocks.js'), path.join(__dirname, '..')],
env: {NODE_ENV: 'test'}
})
}
@@ -35,10 +36,8 @@ function waitForLoad (app, t, opts) {
return app.start().then(function () {
return app.client.waitUntilWindowLoaded()
}).then(function () {
// Offline mode? Disable internet in the webtorrent window
// TODO. For now, just run integration tests with internet turned off.
// Spectron is poorly documented, and contrary to the docs, webContents.session is missing
// That is the correct API (in theory) to put the app in offline mode
// Offline mode
if (opts.offline) app.webContents.executeJavaScript('testOfflineMode()')
}).then(function () {
// Switch to the main window. Index 0 is apparently the hidden webtorrent window...
return app.client.windowByIndex(1)
@@ -52,9 +51,9 @@ function waitForLoad (app, t, opts) {
})
}
// Returns a promise that resolves after 'ms' milliseconds. Default: 500
// Returns a promise that resolves after 'ms' milliseconds. Default: 1 second
function wait (ms) {
if (ms === undefined) ms = 500 // Default: wait long enough for the UI to update
if (ms === undefined) ms = 1000 // Default: wait long enough for the UI to update
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms)
})
@@ -80,7 +79,7 @@ function screenshotCreateOrCompare (app, t, name) {
console.log('Saving screenshot ' + ssPath)
fs.writeFileSync(ssPath, buffer)
} else {
const match = Buffer.compare(buffer, ssBuf) === 0
const match = compareIgnoringTransparency(buffer, ssBuf)
t.ok(match, 'screenshot comparison ' + name)
if (!match) {
const ssFailedPath = path.join(ssDir, name + '-failed.png')
@@ -91,21 +90,81 @@ 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
// Compares two PNGs, ignoring any transparent regions in bufExpected.
// Returns true if they match.
function compareIgnoringTransparency (bufActual, bufExpected) {
// Common case: exact byte-for-byte match
if (Buffer.compare(bufActual, bufExpected) === 0) return true
// Otherwise, compare pixel by pixel
const pngA = PNG.sync.read(bufActual)
const pngE = PNG.sync.read(bufExpected)
if (pngA.width !== pngE.width || pngA.height !== pngE.height) return false
const w = pngA.width
const h = pngE.height
const da = pngA.data
const de = pngE.data
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const i = (y * w + x) * 4
if (de[i + 3] === 0) continue // Skip transparent pixels
if (da[i] !== de[i] || da[i + 1] !== de[i + 1] || da[i + 2] !== de[i + 2]) return false
}
}
return true
}
// Resets the test directory, containing config.json, torrents, downloads, etc
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()
const actualSorted = actualFilenames.slice().sort()
t.deepEqual(actualSorted, expectedSorted, 'download folder contents: ' + dirname)
} catch (e) {
console.error(e)
t.equal(filenames, null, 'download folder missing: ' + dirname)
if (e.code === 'ENOENT') {
t.equal(filenames, null, 'download folder missing: ' + dirname)
} else {
console.error(e)
t.fail('unexpected error getting download folder: ' + dirname)
}
}
}
// 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 // private is a reserved word in JS
return { infoHash, name, announce, urlList, comment, 'private': priv }
}

69
test/test-add-torrent.js Normal file
View File

@@ -0,0 +1,69 @@
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.resetTestDataDir()
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.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-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'))
// 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(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-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 <temp folder>/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'))
})

59
test/test-audio.js Normal file
View File

@@ -0,0 +1,59 @@
const test = require('tape')
const setup = require('./setup')
test('audio-streaming', function (t) {
setup.resetTestDataDir()
t.timeoutAfter(60e3)
const app = setup.createApp()
setup.waitForLoad(app, t, {online: true})
.then(() => app.client.waitUntilTextExists('.torrent-list', 'Big Buck Bunny'))
// Play Wired CD. Wait for it to start streaming.
.then(() => app.client.moveToObject('#torrent-wired'))
.then(() => setup.wait())
.then(() => app.client.click('#torrent-wired .icon.play'))
.then(() => app.client.waitUntilTextExists('The Wired CD'))
// Pause. Skip to two seconds in. Wait another two seconds for it to load.
.then(() => app.webContents.executeJavaScript('dispatch("playPause")'))
.then(() => app.webContents.executeJavaScript('dispatch("skipTo", 2)'))
.then(() => setup.wait())
.then(() => app.client.waitUntilTextExists('Artist'))
.then(() => setup.screenshotCreateOrCompare(app, t, 'play-torrent-wired'))
// Click next
.then(() => app.client.click('.skip-next'))
.then(() => app.client.waitUntilTextExists('The Wired CD'))
.then(() => app.client.moveToObject('.letterbox'))
.then(() => app.webContents.executeJavaScript('dispatch("playPause")'))
.then(() => app.webContents.executeJavaScript('dispatch("skipTo", 2)'))
.then(() => setup.wait())
.then(() => setup.screenshotCreateOrCompare(app, t, 'play-torrent-wired-2'))
// Play from end of song, let it advance on its own
.then(() => app.webContents.executeJavaScript('dispatch("skipTo", 206)'))
.then(() => app.webContents.executeJavaScript('dispatch("playPause")'))
.then(() => setup.wait(5e3)) // Let it play a few seconds, past the end of the song
.then(() => app.webContents.executeJavaScript('dispatch("playPause")'))
.then(() => app.webContents.executeJavaScript('dispatch("skipTo", 2)'))
.then(() => setup.wait())
.then(() => setup.screenshotCreateOrCompare(app, t, 'play-torrent-wired-3'))
// Fullscreen
.then(() => app.client.click('.fullscreen'))
.then(() => setup.wait())
.then(() => setup.screenshotCreateOrCompare(app, t, 'play-torrent-wired-fullscreen'))
// Back to normal audio view. Give the player controls have had time to disappear.
.then(() => app.webContents.executeJavaScript('dispatch("escapeBack")'))
.then(() => setup.wait())
.then(() => setup.screenshotCreateOrCompare(app, t, 'play-torrent-wired-4'))
// Back. Return to torrent list
.then(() => app.client.click('.back'))
.then(() => app.client.waitUntilTextExists('Big Buck Bunny'))
.then(() => setup.screenshotCreateOrCompare(app, t, 'play-torrent-wired-list'))
// Forward. Should play again where we left off (should not stay paused)
.then(() => app.client.click('.forward'))
.then(() => setup.wait())
.then(() => app.webContents.executeJavaScript('dispatch("playPause")'))
.then(() => app.webContents.executeJavaScript('dispatch("skipTo", 2)'))
.then(() => setup.wait())
.then(() => setup.screenshotCreateOrCompare(app, t, 'play-torrent-wired-5'))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})

View File

@@ -1,10 +1,11 @@
const test = require('tape')
const fs = require('fs-extra')
const setup = require('./setup')
const config = require('./config')
test.skip('torrent-list: show download path missing', function (t) {
setup.wipeTestDataDir()
fs.removeSync(setup.TEST_DOWNLOAD_DIR)
test('torrent-list: show download path missing', function (t) {
setup.resetTestDataDir()
fs.removeSync(config.TEST_DIR_DOWNLOAD)
t.timeoutAfter(10e3)
const app = setup.createApp()
@@ -23,8 +24,8 @@ test.skip('torrent-list: show download path missing', function (t) {
(err) => setup.endTest(app, t, err || 'error'))
})
test.skip('torrent-list: start, stop, and delete torrents', function (t) {
setup.wipeTestDataDir()
test('torrent-list: start, stop, and delete torrents', function (t) {
setup.resetTestDataDir()
const app = setup.createApp()
setup.waitForLoad(app, t, {offline: true})
@@ -35,7 +36,7 @@ test.skip('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', '276MB'))
.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.skip('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})
@@ -81,7 +82,7 @@ test('torrent-list: expand torrent, unselect file', function (t) {
.then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-cosmos-expand-deselect'))
// Start the torrent
.then(() => app.client.click('#torrent-cosmos .icon.download'))
.then(() => app.client.waitUntilTextExists('.torrent-list', '0%'))
.then(() => app.client.waitUntilTextExists('.torrent-list', 'peers'))
.then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-cosmos-expand-start'))
// Make sure that it creates all files EXCEPT the deslected one
.then(() => setup.compareDownloadFolder(t, 'CosmosLaundromatFirstCycle', [
@@ -97,6 +98,16 @@ test('torrent-list: expand torrent, unselect file', function (t) {
'CosmosLaundromatFirstCycle_meta.sqlite',
'CosmosLaundromatFirstCycle_meta.xml'
]))
// Delete torrent plus data
// Spectron doesn't have proper support for menu clicks yet...
.then(() => app.webContents.executeJavaScript(
'dispatch("confirmDeleteTorrent", "6a02592d2bbc069628cd5ed8a54f88ee06ac0ba5", true)'))
.then(() => setup.wait())
.then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-cosmos-delete-data'))
// Click confirm
.then(() => app.client.click('.control.ok'))
.then(() => setup.wait())
.then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-cosmos-deleted'))
// Make sure that all the files are gone
.then(() => setup.compareDownloadFolder(t, 'CosmosLaundromatFirstCycle', null))
.then(() => setup.endTest(app, t),

36
test/test-video.js Normal file
View File

@@ -0,0 +1,36 @@
const test = require('tape')
const setup = require('./setup')
test('video-streaming', function (t) {
setup.resetTestDataDir()
t.timeoutAfter(30e3)
const app = setup.createApp()
setup.waitForLoad(app, t, {online: true})
.then(() => app.client.waitUntilTextExists('.torrent-list', 'Big Buck Bunny'))
// Play Big Buck Bunny. Wait for it to start streaming.
.then(() => app.client.moveToObject('.torrent'))
.then(() => setup.wait())
.then(() => app.client.click('.icon.play'))
.then(() => setup.wait(10e3))
// Pause. Skip to two seconds in. Wait another two seconds for it to load.
.then(() => app.webContents.executeJavaScript('dispatch("playPause")'))
.then(() => app.webContents.executeJavaScript('dispatch("skipTo", 2)'))
.then(() => setup.wait(5e3))
// Take a screenshot to verify video playback
.then(() => setup.screenshotCreateOrCompare(app, t, 'play-torrent-bbb'))
// Hit escape
.then(() => app.webContents.executeJavaScript('dispatch("escapeBack")'))
.then(() => setup.wait())
// Delete Big Buck Bunny
.then(() => app.client.moveToObject('.torrent'))
.then(() => setup.wait())
.then(() => app.client.click('.icon.delete'))
.then(() => setup.wait())
.then(() => app.client.click('.control.ok'))
.then(() => setup.wait())
// Take another screenshot to verify that the window resized correctly
.then(() => setup.screenshotCreateOrCompare(app, t, 'play-torrent-return'))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})