Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cdc73edce | ||
|
|
3d254fa075 | ||
|
|
ed1e43015e | ||
|
|
e6cbbd73f0 | ||
|
|
67dff7b38c | ||
|
|
ced67176a3 | ||
|
|
00ac8afe64 | ||
|
|
a6964c4495 | ||
|
|
6541291e0d | ||
|
|
711d274398 | ||
|
|
6c5861b9fc | ||
|
|
f7ab27f9fd | ||
|
|
e4e789cc5b | ||
|
|
09b525fe58 | ||
|
|
9dabfc1367 | ||
|
|
bdb733352a | ||
|
|
75a4655a0f | ||
|
|
051c1516a0 | ||
|
|
62c5b78358 | ||
|
|
290913d07a | ||
|
|
77534d650a | ||
|
|
a4c715e3f6 | ||
|
|
7415d3cee5 | ||
|
|
e1ba9c89fe | ||
|
|
0fcbe7369a | ||
|
|
c8087b5b63 | ||
|
|
065faca8eb | ||
|
|
bcd6a38a05 | ||
|
|
fa67f9b82b | ||
|
|
39acd0bd47 | ||
|
|
a629f287f0 |
@@ -32,5 +32,6 @@
|
|||||||
- Vamsi Krishna Avula (vamsi_ism@outlook.com)
|
- Vamsi Krishna Avula (vamsi_ism@outlook.com)
|
||||||
- Noam Okman (noamokman@gmail.com)
|
- Noam Okman (noamokman@gmail.com)
|
||||||
- PurgingPanda (t3ch0wn3r@gmail.com)
|
- PurgingPanda (t3ch0wn3r@gmail.com)
|
||||||
|
- Kai Curtis (morecode@kcurtis.com)
|
||||||
|
|
||||||
#### Generated by bin/update-authors.sh.
|
#### Generated by bin/update-authors.sh.
|
||||||
|
|||||||
22
CHANGELOG.md
@@ -1,5 +1,27 @@
|
|||||||
# WebTorrent Desktop Version History
|
# 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
|
## v0.14.0 - 2016-09-03
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
22
README.md
@@ -56,6 +56,28 @@ Restart the app automatically every time code changes. Useful during development
|
|||||||
$ npm run watch
|
$ 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
|
### Package the app
|
||||||
|
|
||||||
Builds app binaries for Mac, Linux, and Windows.
|
Builds app binaries for Mac, Linux, and Windows.
|
||||||
|
|||||||
109
bin/package.js
@@ -112,7 +112,7 @@ var darwin = {
|
|||||||
// Build for Mac
|
// Build for Mac
|
||||||
platform: 'darwin',
|
platform: 'darwin',
|
||||||
|
|
||||||
// Build 64 bit binaries only.
|
// Build x64 binaries only.
|
||||||
arch: 'x64',
|
arch: 'x64',
|
||||||
|
|
||||||
// The bundle identifier to use in the application's plist (Mac only).
|
// The bundle identifier to use in the application's plist (Mac only).
|
||||||
@@ -133,8 +133,8 @@ var win32 = {
|
|||||||
// Build for Windows.
|
// Build for Windows.
|
||||||
platform: 'win32',
|
platform: 'win32',
|
||||||
|
|
||||||
// Build 32 bit binaries only.
|
// Build ia32 and x64 binaries.
|
||||||
arch: 'ia32',
|
arch: 'all',
|
||||||
|
|
||||||
// Object hash of application metadata to embed into the executable (Windows only)
|
// Object hash of application metadata to embed into the executable (Windows only)
|
||||||
'version-string': {
|
'version-string': {
|
||||||
@@ -167,7 +167,7 @@ var linux = {
|
|||||||
// Build for Linux.
|
// Build for Linux.
|
||||||
platform: 'linux',
|
platform: 'linux',
|
||||||
|
|
||||||
// Build 32 and 64 bit binaries.
|
// Build ia32 and x64 binaries.
|
||||||
arch: 'all'
|
arch: 'all'
|
||||||
|
|
||||||
// Note: Application icon for Linux is specified via the BrowserWindow `icon` option.
|
// Note: Application icon for Linux is specified via the BrowserWindow `icon` option.
|
||||||
@@ -388,19 +388,25 @@ function buildWin32 (cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tasks = []
|
var tasks = []
|
||||||
if (argv.package === 'exe' || argv.package === 'all') {
|
buildPath.forEach(function (filesPath) {
|
||||||
tasks.push((cb) => packageInstaller(cb))
|
var destArch = filesPath.split('-').pop()
|
||||||
}
|
|
||||||
if (argv.package === 'portable' || argv.package === 'all') {
|
if (argv.package === 'exe' || argv.package === 'all') {
|
||||||
tasks.push((cb) => packagePortable(cb))
|
tasks.push((cb) => packageInstaller(filesPath, destArch, cb))
|
||||||
}
|
}
|
||||||
|
if (argv.package === 'portable' || argv.package === 'all') {
|
||||||
|
tasks.push((cb) => packagePortable(filesPath, destArch, cb))
|
||||||
|
}
|
||||||
|
})
|
||||||
series(tasks, cb)
|
series(tasks, cb)
|
||||||
|
|
||||||
function packageInstaller (cb) {
|
function packageInstaller (filesPath, destArch, cb) {
|
||||||
console.log('Windows: Creating installer...')
|
console.log(`Windows: Creating ${destArch} installer...`)
|
||||||
|
|
||||||
|
var archStr = destArch === 'ia32' ? '-ia32' : ''
|
||||||
|
|
||||||
installer.createWindowsInstaller({
|
installer.createWindowsInstaller({
|
||||||
appDirectory: buildPath[0],
|
appDirectory: filesPath,
|
||||||
authors: config.APP_TEAM,
|
authors: config.APP_TEAM,
|
||||||
description: config.APP_NAME,
|
description: config.APP_NAME,
|
||||||
exe: config.APP_NAME + '.exe',
|
exe: config.APP_NAME + '.exe',
|
||||||
@@ -410,8 +416,21 @@ function buildWin32 (cb) {
|
|||||||
noMsi: true,
|
noMsi: true,
|
||||||
outputDirectory: DIST_PATH,
|
outputDirectory: DIST_PATH,
|
||||||
productName: config.APP_NAME,
|
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',
|
setupIcon: config.APP_ICON + '.ico',
|
||||||
signWithParams: signWithParams,
|
signWithParams: signWithParams,
|
||||||
title: config.APP_NAME,
|
title: config.APP_NAME,
|
||||||
@@ -419,23 +438,65 @@ function buildWin32 (cb) {
|
|||||||
version: pkg.version
|
version: pkg.version
|
||||||
})
|
})
|
||||||
.then(function () {
|
.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)
|
cb(null)
|
||||||
})
|
})
|
||||||
.catch(cb)
|
.catch(cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
function packagePortable (cb) {
|
function packagePortable (filesPath, destArch, cb) {
|
||||||
console.log('Windows: Creating portable app...')
|
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)
|
mkdirp.sync(portablePath)
|
||||||
|
|
||||||
var inPath = path.join(DIST_PATH, path.basename(buildPath[0]))
|
var archStr = destArch === 'ia32' ? '-ia32' : ''
|
||||||
var outPath = path.join(DIST_PATH, BUILD_NAME + '-win.zip')
|
|
||||||
|
var inPath = path.join(DIST_PATH, path.basename(filesPath))
|
||||||
|
var outPath = path.join(DIST_PATH, BUILD_NAME + '-win' + archStr + '.zip')
|
||||||
zip.zipSync(inPath, outPath)
|
zip.zipSync(inPath, outPath)
|
||||||
|
|
||||||
console.log('Windows: Created portable app.')
|
console.log(`Windows: Created ${destArch} portable app.`)
|
||||||
cb(null)
|
cb(null)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -500,8 +561,10 @@ function buildLinux (cb) {
|
|||||||
// Create .zip file for Linux
|
// Create .zip file for Linux
|
||||||
console.log(`Linux: Creating ${destArch} zip...`)
|
console.log(`Linux: Creating ${destArch} zip...`)
|
||||||
|
|
||||||
|
var archStr = destArch === 'ia32' ? '-ia32' : ''
|
||||||
|
|
||||||
var inPath = path.join(DIST_PATH, path.basename(filesPath))
|
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)
|
zip.zipSync(inPath, outPath)
|
||||||
|
|
||||||
console.log(`Linux: Created ${destArch} zip.`)
|
console.log(`Linux: Created ${destArch} zip.`)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "webtorrent-desktop",
|
"name": "webtorrent-desktop",
|
||||||
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
|
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
|
||||||
"version": "0.15.0",
|
"version": "0.16.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "WebTorrent, LLC",
|
"name": "WebTorrent, LLC",
|
||||||
"email": "feross@webtorrent.io",
|
"email": "feross@webtorrent.io",
|
||||||
@@ -18,12 +18,13 @@
|
|||||||
"application-config": "^1.0.0",
|
"application-config": "^1.0.0",
|
||||||
"auto-launch": "^4.0.1",
|
"auto-launch": "^4.0.1",
|
||||||
"bitfield": "^1.0.2",
|
"bitfield": "^1.0.2",
|
||||||
|
"capture-frame": "^1.0.0",
|
||||||
"chromecasts": "^1.8.0",
|
"chromecasts": "^1.8.0",
|
||||||
"create-torrent": "^3.24.5",
|
"create-torrent": "^3.24.5",
|
||||||
"deep-equal": "^1.0.1",
|
"deep-equal": "^1.0.1",
|
||||||
"dlnacasts": "^0.1.0",
|
"dlnacasts": "^0.1.0",
|
||||||
"drag-drop": "^2.12.1",
|
"drag-drop": "^2.12.1",
|
||||||
"electron": "1.3.5",
|
"electron": "1.4.0",
|
||||||
"es6-error": "^3.0.1",
|
"es6-error": "^3.0.1",
|
||||||
"fs-extra": "^0.30.0",
|
"fs-extra": "^0.30.0",
|
||||||
"iso-639-1": "^1.2.1",
|
"iso-639-1": "^1.2.1",
|
||||||
@@ -65,6 +66,7 @@
|
|||||||
"nodemon": "^1.10.2",
|
"nodemon": "^1.10.2",
|
||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
"plist": "^2.0.1",
|
"plist": "^2.0.1",
|
||||||
|
"pngjs": "^3.0.0",
|
||||||
"rimraf": "^2.5.2",
|
"rimraf": "^2.5.2",
|
||||||
"run-series": "^1.1.4",
|
"run-series": "^1.1.4",
|
||||||
"spectron": "^3.3.0",
|
"spectron": "^3.3.0",
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ module.exports = {
|
|||||||
IS_PRODUCTION: IS_PRODUCTION,
|
IS_PRODUCTION: IS_PRODUCTION,
|
||||||
IS_TEST: IS_TEST,
|
IS_TEST: IS_TEST,
|
||||||
|
|
||||||
|
OS_SYSARCH: is64BitOperatingSystem() ? 'x64' : 'ia32',
|
||||||
|
|
||||||
POSTER_PATH: path.join(getConfigPath(), 'Posters'),
|
POSTER_PATH: path.join(getConfigPath(), 'Posters'),
|
||||||
ROOT_PATH: path.join(__dirname, '..'),
|
ROOT_PATH: path.join(__dirname, '..'),
|
||||||
STATIC_PATH: path.join(__dirname, '..', 'static'),
|
STATIC_PATH: path.join(__dirname, '..', 'static'),
|
||||||
@@ -149,3 +151,32 @@ function isProduction () {
|
|||||||
return !/\/electron$/.test(process.execPath)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,12 +21,7 @@ function openSeedFile () {
|
|||||||
title: 'Select a file for the torrent.',
|
title: 'Select a file for the torrent.',
|
||||||
properties: [ 'openFile' ]
|
properties: [ 'openFile' ]
|
||||||
}
|
}
|
||||||
setTitle(opts.title)
|
showOpenSeed(opts)
|
||||||
electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) {
|
|
||||||
resetTitle()
|
|
||||||
if (!Array.isArray(selectedPaths)) return
|
|
||||||
windows.main.dispatch('showCreateTorrent', selectedPaths)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -46,12 +41,7 @@ function openSeedDirectory () {
|
|||||||
title: 'Select a folder for the torrent.',
|
title: 'Select a folder for the torrent.',
|
||||||
properties: [ 'openDirectory' ]
|
properties: [ 'openDirectory' ]
|
||||||
}
|
}
|
||||||
setTitle(opts.title)
|
showOpenSeed(opts)
|
||||||
electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) {
|
|
||||||
resetTitle()
|
|
||||||
if (!Array.isArray(selectedPaths)) return
|
|
||||||
windows.main.dispatch('showCreateTorrent', selectedPaths)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -119,3 +109,16 @@ function setTitle (title) {
|
|||||||
function resetTitle () {
|
function resetTitle () {
|
||||||
windows.main.dispatch('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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
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) {
|
function processArgv (argv) {
|
||||||
|
|||||||
@@ -98,14 +98,16 @@ function init () {
|
|||||||
/**
|
/**
|
||||||
* File handlers
|
* File handlers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ipc.on('setDefaultFileHandler', (e, flag) => {
|
ipc.on('setDefaultFileHandler', (e, flag) => {
|
||||||
if (flag) handlers.install()
|
if (flag) handlers.install()
|
||||||
else handlers.uninstall()
|
else handlers.uninstall()
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Startup
|
* Auto start on login
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ipc.on('setStartup', (e, flag) => {
|
ipc.on('setStartup', (e, flag) => {
|
||||||
if (flag) startup.install()
|
if (flag) startup.install()
|
||||||
else startup.uninstall()
|
else startup.uninstall()
|
||||||
@@ -143,7 +145,10 @@ function init () {
|
|||||||
|
|
||||||
ipc.on('quitExternalPlayer', () => externalPlayer.kill())
|
ipc.on('quitExternalPlayer', () => externalPlayer.kill())
|
||||||
|
|
||||||
// Capture all events
|
/**
|
||||||
|
* Message passing
|
||||||
|
*/
|
||||||
|
|
||||||
const oldEmit = ipc.emit
|
const oldEmit = ipc.emit
|
||||||
ipc.emit = function (name, e, ...args) {
|
ipc.emit = function (name, e, ...args) {
|
||||||
// Relay messages between the main window and the WebTorrent hidden window
|
// Relay messages between the main window and the WebTorrent hidden window
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ const windows = require('./windows')
|
|||||||
|
|
||||||
const AUTO_UPDATE_URL = config.AUTO_UPDATE_URL +
|
const AUTO_UPDATE_URL = config.AUTO_UPDATE_URL +
|
||||||
'?version=' + config.APP_VERSION +
|
'?version=' + config.APP_VERSION +
|
||||||
'&platform=' + process.platform
|
'&platform=' + process.platform +
|
||||||
|
'&sysarch=' + config.OS_SYSARCH
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'linux') {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const React = require('react')
|
const React = require('react')
|
||||||
|
|
||||||
const FlatButton = require('material-ui/FlatButton').default
|
const RaisedButton = require('material-ui/RaisedButton').default
|
||||||
|
|
||||||
class ShowMore extends React.Component {
|
class ShowMore extends React.Component {
|
||||||
static get propTypes () {
|
static get propTypes () {
|
||||||
@@ -39,9 +39,10 @@ class ShowMore extends React.Component {
|
|||||||
? this.props.hideLabel
|
? this.props.hideLabel
|
||||||
: this.props.showLabel
|
: this.props.showLabel
|
||||||
return (
|
return (
|
||||||
<div style={this.props.style}>
|
<div className='show-more' style={this.props.style}>
|
||||||
{this.state.expanded ? this.props.children : null}
|
{this.state.expanded ? this.props.children : null}
|
||||||
<FlatButton
|
<RaisedButton
|
||||||
|
className='control'
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
label={label} />
|
label={label} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -215,11 +215,41 @@ module.exports = class TorrentListController {
|
|||||||
|
|
||||||
menu.append(new electron.remote.MenuItem({
|
menu.append(new electron.remote.MenuItem({
|
||||||
label: 'Save Torrent File As...',
|
label: 'Save Torrent File As...',
|
||||||
click: () => saveTorrentFileAs(torrentSummary)
|
click: () => dispatch('saveTorrentFileAs', torrentSummary.torrentKey)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
menu.popup(electron.remote.getCurrentWindow())
|
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
|
// Recursively finds {name, path, size} for all files in a folder
|
||||||
@@ -280,27 +310,3 @@ function moveItemToTrash (torrentSummary) {
|
|||||||
function showItemInFolder (torrentSummary) {
|
function showItemInFolder (torrentSummary) {
|
||||||
ipcRenderer.send('showItemInFolder', TorrentSummary.getFileOrFolder(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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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')
|
|
||||||
}
|
|
||||||
@@ -33,6 +33,15 @@ function init (appState, callback) {
|
|||||||
state = appState
|
state = appState
|
||||||
update = callback
|
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
|
// Load modules, scan the network for devices
|
||||||
airplayer = require('airplayer')()
|
airplayer = require('airplayer')()
|
||||||
chromecasts = require('chromecasts')()
|
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
|
// chromecast player implementation
|
||||||
function chromecastPlayer () {
|
function chromecastPlayer () {
|
||||||
const ret = {
|
const ret = {
|
||||||
|
|||||||
@@ -35,10 +35,6 @@ class TorrentKeyNotFoundError extends TorrentError {
|
|||||||
|
|
||||||
class InvalidTorrentError extends TorrentError {}
|
class InvalidTorrentError extends TorrentError {}
|
||||||
|
|
||||||
/* Miscellaneous */
|
|
||||||
|
|
||||||
class IllegalArgumentError extends ExtendableError {}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
CastingError,
|
CastingError,
|
||||||
PlaybackError,
|
PlaybackError,
|
||||||
@@ -49,6 +45,5 @@ module.exports = {
|
|||||||
PlaybackTimedOutError,
|
PlaybackTimedOutError,
|
||||||
InvalidSoundNameError,
|
InvalidSoundNameError,
|
||||||
TorrentKeyNotFoundError,
|
TorrentKeyNotFoundError,
|
||||||
InvalidTorrentError,
|
InvalidTorrentError
|
||||||
IllegalArgumentError
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ function getSystemInfo () {
|
|||||||
osPlatform: process.platform,
|
osPlatform: process.platform,
|
||||||
osRelease: os.type() + ' ' + os.release(),
|
osRelease: os.type() + ' ' + os.release(),
|
||||||
architecture: os.arch(),
|
architecture: os.arch(),
|
||||||
|
systemArchitecture: config.OS_SYSARCH,
|
||||||
totalMemoryMB: roundPow2(os.totalmem() / (1 << 20)),
|
totalMemoryMB: roundPow2(os.totalmem() / (1 << 20)),
|
||||||
numCores: os.cpus().length
|
numCores: os.cpus().length
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module.exports = torrentPoster
|
module.exports = torrentPoster
|
||||||
|
|
||||||
const captureVideoFrame = require('./capture-video-frame')
|
const captureFrame = require('capture-frame')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
function torrentPoster (torrent, cb) {
|
function torrentPoster (torrent, cb) {
|
||||||
@@ -61,7 +61,7 @@ function torrentPosterFromVideo (file, torrent, cb) {
|
|||||||
function onSeeked () {
|
function onSeeked () {
|
||||||
video.removeEventListener('seeked', onSeeked)
|
video.removeEventListener('seeked', onSeeked)
|
||||||
|
|
||||||
const buf = captureVideoFrame(video)
|
const buf = captureFrame(video)
|
||||||
|
|
||||||
// unload video element
|
// unload video element
|
||||||
video.pause()
|
video.pause()
|
||||||
|
|||||||
@@ -194,6 +194,8 @@ const dispatchHandlers = {
|
|||||||
controllers.torrentList.openTorrentContextMenu(infoHash),
|
controllers.torrentList.openTorrentContextMenu(infoHash),
|
||||||
'startTorrentingSummary': (torrentKey) =>
|
'startTorrentingSummary': (torrentKey) =>
|
||||||
controllers.torrentList.startTorrentingSummary(torrentKey),
|
controllers.torrentList.startTorrentingSummary(torrentKey),
|
||||||
|
'saveTorrentFileAs': (torrentKey) =>
|
||||||
|
controllers.torrentList.saveTorrentFileAs(torrentKey),
|
||||||
|
|
||||||
// Playback
|
// Playback
|
||||||
'playFile': (infoHash, index) => controllers.playback.playFile(infoHash, index),
|
'playFile': (infoHash, index) => controllers.playback.playFile(infoHash, index),
|
||||||
|
|||||||
@@ -99,14 +99,14 @@ class CreateTorrentPage extends React.Component {
|
|||||||
</ShowMore>
|
</ShowMore>
|
||||||
<div className='float-right'>
|
<div className='float-right'>
|
||||||
<FlatButton
|
<FlatButton
|
||||||
className='control'
|
className='control cancel'
|
||||||
label='Cancel'
|
label='Cancel'
|
||||||
style={{
|
style={{
|
||||||
marginRight: 10
|
marginRight: 10
|
||||||
}}
|
}}
|
||||||
onClick={dispatcher('cancel')} />
|
onClick={dispatcher('cancel')} />
|
||||||
<RaisedButton
|
<RaisedButton
|
||||||
className='control'
|
className='control create-torrent'
|
||||||
label='Create Torrent'
|
label='Create Torrent'
|
||||||
primary
|
primary
|
||||||
onClick={this.handleSubmit} />
|
onClick={this.handleSubmit} />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const zeroFill = require('zero-fill')
|
|||||||
const TorrentSummary = require('../lib/torrent-summary')
|
const TorrentSummary = require('../lib/torrent-summary')
|
||||||
const Playlist = require('../lib/playlist')
|
const Playlist = require('../lib/playlist')
|
||||||
const {dispatch, dispatcher} = require('../lib/dispatcher')
|
const {dispatch, dispatcher} = require('../lib/dispatcher')
|
||||||
|
const config = require('../../config')
|
||||||
|
|
||||||
// Shows a streaming video player. Standard features + Chromecast + Airplay
|
// Shows a streaming video player. Standard features + Chromecast + Airplay
|
||||||
module.exports = class Player extends React.Component {
|
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
|
// Renders the loading bar. Shows which parts of the torrent are loaded, which
|
||||||
// can be 'spongey' / non-contiguous
|
// can be 'spongey' / non-contiguous
|
||||||
function renderLoadingBar (state) {
|
function renderLoadingBar (state) {
|
||||||
|
if (config.IS_TEST) return // Don't integration test the loading bar. Screenshots won't match.
|
||||||
|
|
||||||
const torrentSummary = state.getPlayingTorrentSummary()
|
const torrentSummary = state.getPlayingTorrentSummary()
|
||||||
if (!torrentSummary.progress) {
|
if (!torrentSummary.progress) {
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -54,11 +54,18 @@ const VERSION_STR = VERSION.match(/([0-9]+)/g)
|
|||||||
*/
|
*/
|
||||||
const VERSION_PREFIX = '-WD' + VERSION_STR + '-'
|
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
|
// Connect to the WebTorrent and BitTorrent networks. WebTorrent Desktop is a hybrid
|
||||||
// client, as explained here: https://webtorrent.io/faq
|
// client, as explained here: https://webtorrent.io/faq
|
||||||
const client = window.client = new WebTorrent({
|
let client = window.client = new WebTorrent({ peerId: PEER_ID })
|
||||||
peerId: Buffer.from(VERSION_PREFIX + crypto.randomBytes(6).toString('hex'))
|
|
||||||
})
|
|
||||||
|
|
||||||
// WebTorrent-to-HTTP streaming sever
|
// WebTorrent-to-HTTP streaming sever
|
||||||
let server = null
|
let server = null
|
||||||
@@ -69,8 +76,7 @@ let prevProgress = null
|
|||||||
init()
|
init()
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
|
listenToClientEvents()
|
||||||
client.on('error', (err) => ipc.send('wt-error', null, err.message))
|
|
||||||
|
|
||||||
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
|
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
|
||||||
startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
|
startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
|
||||||
@@ -101,6 +107,11 @@ function init () {
|
|||||||
console.timeEnd('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.
|
// Starts a given TorrentID, which can be an infohash, magnet URI, etc.
|
||||||
// Returns a WebTorrent object. See https://git.io/vik9M
|
// Returns a WebTorrent object. See https://git.io/vik9M
|
||||||
function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections) {
|
function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections) {
|
||||||
@@ -157,7 +168,7 @@ function addTorrentEvents (torrent) {
|
|||||||
function torrentReady () {
|
function torrentReady () {
|
||||||
const info = getTorrentInfo(torrent)
|
const info = getTorrentInfo(torrent)
|
||||||
ipc.send('wt-ready', torrent.key, info)
|
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()
|
updateTorrentProgress()
|
||||||
}
|
}
|
||||||
@@ -395,3 +406,17 @@ function getTorrent (torrentKey) {
|
|||||||
function onError (err) {
|
function onError (err) {
|
||||||
console.log(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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
<p>
|
<p>
|
||||||
Version <script>document.write(require('../package.json').version)</script>
|
Version <script>document.write(require('../package.json').version)</script>
|
||||||
(<script>document.write(require('webtorrent/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>
|
||||||
<p><script>document.write(require('../config').APP_COPYRIGHT)</script></p>
|
<p><script>document.write(require('../config').APP_COPYRIGHT)</script></p>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
14
test/config.js
Normal 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
|
||||||
|
}
|
||||||
@@ -1,33 +1,20 @@
|
|||||||
const test = require('tape')
|
const test = require('tape')
|
||||||
const fs = require('fs-extra')
|
|
||||||
const setup = require('./setup')
|
const setup = require('./setup')
|
||||||
|
|
||||||
console.log('Creating download dir: ' + setup.TEST_DOWNLOAD_DIR)
|
test.onFinish(setup.deleteTestDataDir)
|
||||||
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('app runs', function (t) {
|
test('app runs', function (t) {
|
||||||
t.timeoutAfter(10e3)
|
t.timeoutAfter(10e3)
|
||||||
|
setup.resetTestDataDir()
|
||||||
const app = setup.createApp()
|
const app = setup.createApp()
|
||||||
setup.waitForLoad(app, t)
|
setup.waitForLoad(app, t)
|
||||||
.then(() => setup.wait())
|
.then(() => setup.wait())
|
||||||
.then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-basic'))
|
.then(() => setup.screenshotCreateOrCompare(app, t, 'app-basic'))
|
||||||
.then(() => setup.endTest(app, t),
|
.then(() => setup.endTest(app, t),
|
||||||
(err) => setup.endTest(app, t, err || 'error'))
|
(err) => setup.endTest(app, t, err || 'error'))
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('Testing the torrent list (home page)...')
|
|
||||||
setup.wipeTestDataDir()
|
|
||||||
require('./test-torrent-list')
|
require('./test-torrent-list')
|
||||||
|
require('./test-add-torrent')
|
||||||
// TODO:
|
require('./test-video')
|
||||||
// require('./test-add-torrent')
|
require('./test-audio')
|
||||||
// require('./test-create-torrent')
|
|
||||||
// require('./test-prefs')
|
|
||||||
// require('./test-video')
|
|
||||||
// require('./test-audio')
|
|
||||||
// require('./test-cast')
|
|
||||||
|
|||||||
15
test/mocks.js
Normal 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
BIN
test/resources/expected-single-file.torrent
Normal file
BIN
test/resources/m3.jpg
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
test/resources/monitor-test.mp4
Normal file
BIN
test/screenshots/darwin/add-torrent-0-percent.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
test/screenshots/darwin/add-torrent-100-percent.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
BIN
test/screenshots/darwin/create-torrent-100-percent.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
test/screenshots/darwin/create-torrent-advanced.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
test/screenshots/darwin/create-torrent-simple.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
test/screenshots/darwin/play-torrent-bbb.png
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
test/screenshots/darwin/play-torrent-return.png
Normal file
|
After Width: | Height: | Size: 777 KiB |
BIN
test/screenshots/darwin/play-torrent-wired-2.png
Normal file
|
After Width: | Height: | Size: 478 KiB |
BIN
test/screenshots/darwin/play-torrent-wired-3.png
Normal file
|
After Width: | Height: | Size: 480 KiB |
BIN
test/screenshots/darwin/play-torrent-wired-4.png
Normal file
|
After Width: | Height: | Size: 480 KiB |
BIN
test/screenshots/darwin/play-torrent-wired-5.png
Normal file
|
After Width: | Height: | Size: 480 KiB |
BIN
test/screenshots/darwin/play-torrent-wired-fullscreen.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
test/screenshots/darwin/play-torrent-wired-list.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
test/screenshots/darwin/play-torrent-wired.png
Normal file
|
After Width: | Height: | Size: 481 KiB |
BIN
test/screenshots/darwin/torrent-list-cosmos-delete-data.png
Normal file
|
After Width: | Height: | Size: 701 KiB |
BIN
test/screenshots/darwin/torrent-list-cosmos-deleted.png
Normal file
|
After Width: | Height: | Size: 932 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
101
test/setup.js
@@ -1,20 +1,21 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const Application = require('spectron').Application
|
const Application = require('spectron').Application
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
|
const parseTorrent = require('parse-torrent')
|
||||||
const TEST_DATA_DIR = path.join(__dirname, 'tempTestData')
|
const PNG = require('pngjs').PNG
|
||||||
const TEST_DOWNLOAD_DIR = path.join(TEST_DATA_DIR, 'Downloads')
|
const config = require('./config')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
TEST_DATA_DIR,
|
|
||||||
TEST_DOWNLOAD_DIR,
|
|
||||||
createApp,
|
createApp,
|
||||||
endTest,
|
endTest,
|
||||||
screenshotCreateOrCompare,
|
screenshotCreateOrCompare,
|
||||||
compareDownloadFolder,
|
compareDownloadFolder,
|
||||||
|
compareFiles,
|
||||||
|
compareTorrentFiles,
|
||||||
waitForLoad,
|
waitForLoad,
|
||||||
wait,
|
wait,
|
||||||
wipeTestDataDir
|
resetTestDataDir,
|
||||||
|
deleteTestDataDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runs WebTorrent Desktop.
|
// Runs WebTorrent Desktop.
|
||||||
@@ -24,7 +25,7 @@ function createApp (t) {
|
|||||||
return new Application({
|
return new Application({
|
||||||
path: path.join(__dirname, '..', 'node_modules', '.bin',
|
path: path.join(__dirname, '..', 'node_modules', '.bin',
|
||||||
'electron' + (process.platform === 'win32' ? '.cmd' : '')),
|
'electron' + (process.platform === 'win32' ? '.cmd' : '')),
|
||||||
args: [path.join(__dirname, '..')],
|
args: ['-r', path.join(__dirname, 'mocks.js'), path.join(__dirname, '..')],
|
||||||
env: {NODE_ENV: 'test'}
|
env: {NODE_ENV: 'test'}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -35,10 +36,8 @@ function waitForLoad (app, t, opts) {
|
|||||||
return app.start().then(function () {
|
return app.start().then(function () {
|
||||||
return app.client.waitUntilWindowLoaded()
|
return app.client.waitUntilWindowLoaded()
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
// Offline mode? Disable internet in the webtorrent window
|
// Offline mode
|
||||||
// TODO. For now, just run integration tests with internet turned off.
|
if (opts.offline) app.webContents.executeJavaScript('testOfflineMode()')
|
||||||
// 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
|
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
// Switch to the main window. Index 0 is apparently the hidden webtorrent window...
|
// Switch to the main window. Index 0 is apparently the hidden webtorrent window...
|
||||||
return app.client.windowByIndex(1)
|
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) {
|
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) {
|
return new Promise(function (resolve, reject) {
|
||||||
setTimeout(resolve, ms)
|
setTimeout(resolve, ms)
|
||||||
})
|
})
|
||||||
@@ -80,7 +79,7 @@ function screenshotCreateOrCompare (app, t, name) {
|
|||||||
console.log('Saving screenshot ' + ssPath)
|
console.log('Saving screenshot ' + ssPath)
|
||||||
fs.writeFileSync(ssPath, buffer)
|
fs.writeFileSync(ssPath, buffer)
|
||||||
} else {
|
} else {
|
||||||
const match = Buffer.compare(buffer, ssBuf) === 0
|
const match = compareIgnoringTransparency(buffer, ssBuf)
|
||||||
t.ok(match, 'screenshot comparison ' + name)
|
t.ok(match, 'screenshot comparison ' + name)
|
||||||
if (!match) {
|
if (!match) {
|
||||||
const ssFailedPath = path.join(ssDir, name + '-failed.png')
|
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
|
// Compares two PNGs, ignoring any transparent regions in bufExpected.
|
||||||
function wipeTestDataDir () {
|
// Returns true if they match.
|
||||||
fs.removeSync(TEST_DATA_DIR)
|
function compareIgnoringTransparency (bufActual, bufExpected) {
|
||||||
fs.mkdirpSync(TEST_DOWNLOAD_DIR) // Downloads/ is inside of TEST_DATA_DIR
|
// 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) {
|
function compareDownloadFolder (t, dirname, filenames) {
|
||||||
const dirpath = path.join(TEST_DOWNLOAD_DIR, dirname)
|
const dirpath = path.join(config.TEST_DIR_DOWNLOAD, dirname)
|
||||||
try {
|
try {
|
||||||
const actualFilenames = fs.readdirSync(dirpath)
|
const actualFilenames = fs.readdirSync(dirpath)
|
||||||
const expectedSorted = filenames.slice().sort()
|
const expectedSorted = filenames.slice().sort()
|
||||||
const actualSorted = actualFilenames.slice().sort()
|
const actualSorted = actualFilenames.slice().sort()
|
||||||
t.deepEqual(actualSorted, expectedSorted, 'download folder contents: ' + dirname)
|
t.deepEqual(actualSorted, expectedSorted, 'download folder contents: ' + dirname)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
if (e.code === 'ENOENT') {
|
||||||
t.equal(filenames, null, 'download folder missing: ' + dirname)
|
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
@@ -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
@@ -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'))
|
||||||
|
})
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
const test = require('tape')
|
const test = require('tape')
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const setup = require('./setup')
|
const setup = require('./setup')
|
||||||
|
const config = require('./config')
|
||||||
|
|
||||||
test.skip('torrent-list: show download path missing', function (t) {
|
test('torrent-list: show download path missing', function (t) {
|
||||||
setup.wipeTestDataDir()
|
setup.resetTestDataDir()
|
||||||
fs.removeSync(setup.TEST_DOWNLOAD_DIR)
|
fs.removeSync(config.TEST_DIR_DOWNLOAD)
|
||||||
|
|
||||||
t.timeoutAfter(10e3)
|
t.timeoutAfter(10e3)
|
||||||
const app = setup.createApp()
|
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'))
|
(err) => setup.endTest(app, t, err || 'error'))
|
||||||
})
|
})
|
||||||
|
|
||||||
test.skip('torrent-list: start, stop, and delete torrents', function (t) {
|
test('torrent-list: start, stop, and delete torrents', function (t) {
|
||||||
setup.wipeTestDataDir()
|
setup.resetTestDataDir()
|
||||||
|
|
||||||
const app = setup.createApp()
|
const app = setup.createApp()
|
||||||
setup.waitForLoad(app, t, {offline: true})
|
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'))
|
.then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-hover'))
|
||||||
// Click download on the first torrent, start downloading
|
// Click download on the first torrent, start downloading
|
||||||
.then(() => app.client.click('.icon.download'))
|
.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'))
|
.then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-start-download'))
|
||||||
// Click download on the first torrent again, stop downloading
|
// Click download on the first torrent again, stop downloading
|
||||||
.then(() => app.client.click('.icon.download'))
|
.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) {
|
test('torrent-list: expand torrent, unselect file', function (t) {
|
||||||
setup.wipeTestDataDir()
|
setup.resetTestDataDir()
|
||||||
|
|
||||||
const app = setup.createApp()
|
const app = setup.createApp()
|
||||||
setup.waitForLoad(app, t, {offline: true})
|
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'))
|
.then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-cosmos-expand-deselect'))
|
||||||
// Start the torrent
|
// Start the torrent
|
||||||
.then(() => app.client.click('#torrent-cosmos .icon.download'))
|
.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'))
|
.then(() => setup.screenshotCreateOrCompare(app, t, 'torrent-list-cosmos-expand-start'))
|
||||||
// Make sure that it creates all files EXCEPT the deslected one
|
// Make sure that it creates all files EXCEPT the deslected one
|
||||||
.then(() => setup.compareDownloadFolder(t, 'CosmosLaundromatFirstCycle', [
|
.then(() => setup.compareDownloadFolder(t, 'CosmosLaundromatFirstCycle', [
|
||||||
@@ -97,6 +98,16 @@ test('torrent-list: expand torrent, unselect file', function (t) {
|
|||||||
'CosmosLaundromatFirstCycle_meta.sqlite',
|
'CosmosLaundromatFirstCycle_meta.sqlite',
|
||||||
'CosmosLaundromatFirstCycle_meta.xml'
|
'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
|
// Make sure that all the files are gone
|
||||||
.then(() => setup.compareDownloadFolder(t, 'CosmosLaundromatFirstCycle', null))
|
.then(() => setup.compareDownloadFolder(t, 'CosmosLaundromatFirstCycle', null))
|
||||||
.then(() => setup.endTest(app, t),
|
.then(() => setup.endTest(app, t),
|
||||||
|
|||||||
36
test/test-video.js
Normal 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'))
|
||||||
|
})
|
||||||