Bläddra i källkod

Google Drive (#80)

* Added two more unit tests for GoogleDrive.  Prettified test reporting.  Added 'every' and 'groupBy' utils.

* GoogleDrive state/rendering. Env variable file. Add state to core.

* corrected small lint errors.
Harry Hedger 9 år sedan
förälder
incheckning
ad79f6687d

+ 4 - 2
package.json

@@ -24,7 +24,7 @@
     "start": "parallelshell 'npm run watch' 'npm run start:server' 'npm run web:preview'",
     "start": "parallelshell 'npm run watch' 'npm run start:server' 'npm run web:preview'",
     "test:acceptance:handleservers": "bin/bootandkill-servers node test/acceptance/index.js",
     "test:acceptance:handleservers": "bin/bootandkill-servers node test/acceptance/index.js",
     "test:acceptance": "node test/acceptance/index.js",
     "test:acceptance": "node test/acceptance/index.js",
-    "test:unit": "node test/unit/index.js",
+    "test:unit": "node test/unit/index.js | tap-spec",
     "test": "npm run lint && npm run test:unit",
     "test": "npm run lint && npm run test:unit",
     "travis:deletecache": "travis cache --delete",
     "travis:deletecache": "travis cache --delete",
     "watch:css": "nodemon --watch src --ext scss -x 'npm run build:css && node website/update.js'",
     "watch:css": "nodemon --watch src --ext scss -x 'npm run build:css && node website/update.js'",
@@ -64,11 +64,12 @@
     "browserify": "12.0.1",
     "browserify": "12.0.1",
     "chalk": "1.1.1",
     "chalk": "1.1.1",
     "disc": "1.3.2",
     "disc": "1.3.2",
+    "eslint": "2.7.0",
     "eslint-config-standard": "5.1.0",
     "eslint-config-standard": "5.1.0",
     "eslint-plugin-promise": "1.1.0",
     "eslint-plugin-promise": "1.1.0",
     "eslint-plugin-standard": "1.3.2",
     "eslint-plugin-standard": "1.3.2",
-    "eslint": "2.7.0",
     "fakefile": "0.0.5",
     "fakefile": "0.0.5",
+    "isomorphic-fetch": "^2.2.1",
     "multi-glob": "1.0.1",
     "multi-glob": "1.0.1",
     "nock": "^8.0.0",
     "nock": "^8.0.0",
     "node-fetch": "^1.5.0",
     "node-fetch": "^1.5.0",
@@ -77,6 +78,7 @@
     "nodemon": "1.8.1",
     "nodemon": "1.8.1",
     "parallelshell": "2.0.0",
     "parallelshell": "2.0.0",
     "selenium-webdriver": "^2.52.0",
     "selenium-webdriver": "^2.52.0",
+    "tap-spec": "^4.1.1",
     "tape": "4.4.0",
     "tape": "4.4.0",
     "uppy-server": "0.0.2",
     "uppy-server": "0.0.2",
     "watchify": "3.6.1"
     "watchify": "3.6.1"

+ 6 - 0
src/core/Core.js

@@ -40,6 +40,12 @@ export default class Core {
       modal: {
       modal: {
         isHidden: true,
         isHidden: true,
         targets: []
         targets: []
+      },
+      googleDrive: {
+        authenticated: false,
+        files: [],
+        folders: [],
+        directory: '/'
       }
       }
     }
     }
 
 

+ 34 - 0
src/core/Utils.js

@@ -53,6 +53,38 @@ function qsa (selector, context) {
   return Array.prototype.slice.call((context || document).querySelectorAll(selector) || [])
   return Array.prototype.slice.call((context || document).querySelectorAll(selector) || [])
 }
 }
 
 
+/**
+ * Partition array by a grouping function.
+ * @param  {[type]} array      Input array
+ * @param  {[type]} groupingFn Grouping function
+ * @return {[type]}            Array of arrays
+ */
+function groupBy (array, groupingFn) {
+  return array.reduce((result, item) => {
+    let key = groupingFn(item)
+    let xs = result.get(key) || []
+    xs.push(item)
+    result.set(key, xs)
+    return result
+  }, new Map())
+}
+
+/**
+ * Tests if every array element passes predicate
+ * @param  {Array}  array       Input array
+ * @param  {Object} predicateFn Predicate
+ * @return {bool}               Every element pass
+ */
+function every (array, predicateFn) {
+  return array.reduce((result, item) => {
+    if (!result) {
+      return false
+    }
+
+    return predicateFn(item)
+  }, true)
+}
+
 /**
 /**
  * Takes a fileName and turns it into fileID, by converting to lowercase,
  * Takes a fileName and turns it into fileID, by converting to lowercase,
  * removing extra characters and adding unix timestamp
  * removing extra characters and adding unix timestamp
@@ -71,6 +103,8 @@ export default {
   promiseWaterfall,
   promiseWaterfall,
   generateFileID,
   generateFileID,
   addListenerMulti,
   addListenerMulti,
+  every,
   flatten,
   flatten,
+  groupBy,
   qsa
   qsa
 }
 }

+ 73 - 38
src/plugins/GoogleDrive.js

@@ -1,7 +1,6 @@
 import yo from 'yo-yo'
 import yo from 'yo-yo'
 import Utils from '../core/Utils'
 import Utils from '../core/Utils'
 import Plugin from './Plugin'
 import Plugin from './Plugin'
-var fetch = fetch || require('node-fetch')
 
 
 export default class Google extends Plugin {
 export default class Google extends Plugin {
   constructor (core, opts) {
   constructor (core, opts) {
@@ -14,7 +13,7 @@ export default class Google extends Plugin {
         <path d="M2.955 14.93l2.667-4.62H16l-2.667 4.62H2.955zm2.378-4.62l-2.666 4.62L0 10.31l5.19-8.99 2.666 4.62-2.523 4.37zm10.523-.25h-5.333l-5.19-8.99h5.334l5.19 8.99z"/>
         <path d="M2.955 14.93l2.667-4.62H16l-2.667 4.62H2.955zm2.378-4.62l-2.666 4.62L0 10.31l5.19-8.99 2.666 4.62-2.523 4.37zm10.523-.25h-5.333l-5.19-8.99h5.334l5.19 8.99z"/>
       </svg>
       </svg>
     `
     `
-    this.authUrl = 'http://localhost:3020/connect/google'
+
     // set default options
     // set default options
     const defaultOptions = {}
     const defaultOptions = {}
 
 
@@ -22,10 +21,6 @@ export default class Google extends Plugin {
     this.opts = Object.assign({}, defaultOptions, opts)
     this.opts = Object.assign({}, defaultOptions, opts)
     this.currentFolder = 'root'
     this.currentFolder = 'root'
     this.isAuthenticated = false
     this.isAuthenticated = false
-    this.checkAuthentication()
-      .then((auth) => {
-        this.isAuthenticated = auth.isAuthenticated
-      })
   }
   }
 
 
   focus () {
   focus () {
@@ -43,7 +38,7 @@ export default class Google extends Plugin {
   }
   }
 
 
   checkAuthentication () {
   checkAuthentication () {
-    return fetch('http://localhost:3020/google/authorize', {
+    return fetch(`${this.opts.host}/google/authorize`, {
       method: 'get',
       method: 'get',
       credentials: 'include',
       credentials: 'include',
       headers: {
       headers: {
@@ -60,27 +55,12 @@ export default class Google extends Plugin {
         throw error
         throw error
       }
       }
     })
     })
-    .then((data) => {
-      return data.isAuthenticated
-    })
-    .catch((err) => {
-      return err
-    })
+    .then((data) => data.isAuthenticated)
+    .catch((err) => err)
   }
   }
 
 
-  getFolder (folderId = this.currentFolder) {
-    /**
-     * Leave this here
-     */
-    // fetch('http://localhost:3020/google/logout', {
-    //   method: 'get',
-    //   credentials: 'include',
-    //   headers: {
-    //     'Accept': 'application/json',
-    //     'Content-Type': 'application/json'
-    //   }
-    // }).then(res => console.log(res))
-    return fetch('http://localhost:3020/google/list', {
+  getFolder (folderId = this.core.state.googleDrive.folder) {
+    return fetch(`${this.opts.host}/google/list`, {
       method: 'get',
       method: 'get',
       credentials: 'include',
       credentials: 'include',
       headers: {
       headers: {
@@ -94,6 +74,8 @@ export default class Google extends Plugin {
     .then((res) => {
     .then((res) => {
       if (res.status >= 200 && res.status <= 300) {
       if (res.status >= 200 && res.status <= 300) {
         return res.json().then((data) => {
         return res.json().then((data) => {
+          // let result = Utils.groupBy(data.items, (item) => item.mimeType)
+
           let folders = []
           let folders = []
           let files = []
           let files = []
           data.items.forEach((item) => {
           data.items.forEach((item) => {
@@ -103,14 +85,20 @@ export default class Google extends Plugin {
               files.push(item)
               files.push(item)
             }
             }
           })
           })
-
           return {
           return {
             folders,
             folders,
             files
             files
           }
           }
         })
         })
+      } else {
+        let error = new Error(res.statusText)
+        error.response = res
+        throw error
       }
       }
     })
     })
+    .catch((err) => {
+      return err
+    })
   }
   }
 
 
   getFile (fileId) {
   getFile (fileId) {
@@ -118,7 +106,7 @@ export default class Google extends Plugin {
       return new Error('getFile: File ID is not a string.')
       return new Error('getFile: File ID is not a string.')
     }
     }
 
 
-    return fetch('http://localhost:3020/google/get', {
+    return fetch(`${this.opts.host}/google/get`, {
       method: 'post',
       method: 'post',
       credentials: 'include',
       credentials: 'include',
       headers: {
       headers: {
@@ -133,39 +121,86 @@ export default class Google extends Plugin {
       return res.json()
       return res.json()
         .then((json) => json)
         .then((json) => json)
     })
     })
-    .catch((err) => console.log(err))
+    .catch((err) => err)
   }
   }
 
 
   install () {
   install () {
     const caller = this
     const caller = this
-    this.target = document.querySelector(this.getTarget(this.opts.target, caller))
+    this.checkAuthentication()
+      .then((authenticated) => {
+        this.updateState({authenticated})
+
+        if (authenticated) {
+          return this.getFolder()
+        }
+
+        return authenticated
+      })
+      .then((newState) => {
+        this.updateState(newState)
+        this.el = this.render(this.core.state)
+        this.target = this.getTarget(this.opts.target, caller, this.el)
+      })
+
     return
     return
   }
   }
 
 
+  logout () {
+    /**
+     * Leave this here
+     */
+    // fetch(`${this.opts.host}/google/logout`, {
+    //   method: 'get',
+    //   credentials: 'include',
+    //   headers: {
+    //     'Accept': 'application/json',
+    //     'Content-Type': 'application/json'
+    //   }
+    // }).then(res => console.log(res))
+  }
+
+  update (state) {
+    if (!this.el) {
+      return
+    }
+    const newEl = this.render(state)
+    yo.update(this.el, newEl)
+  }
+
+  updateState (newState) {
+    const {state} = this.core
+    const googleDrive = Object.assign({}, state.googleDrive, newState)
+
+    this.core.setState({googleDrive})
+  }
+
   render (state) {
   render (state) {
-    if (state.authenticated) {
-      this.renderBrowser()
+    if (state.googleDrive.authenticated) {
+      return this.renderBrowser(state.googleDrive)
     } else {
     } else {
-      this.renderAuth(state)
+      return this.renderAuth()
     }
     }
   }
   }
 
 
   renderAuth () {
   renderAuth () {
+    const link = this.opts.host ? `${this.opts.host}/connect/google` : '#'
     return yo`
     return yo`
       <div>
       <div>
         <h1>Authenticate With Google Drive</h1>
         <h1>Authenticate With Google Drive</h1>
-        <a href=${this.authUrl || '#'}>Authenticate</a>
+        <a href=${link}>Authenticate</a>
       </div>
       </div>
     `
     `
   }
   }
 
 
   renderBrowser (state) {
   renderBrowser (state) {
-    const folders = state.folders.map((folder) => `<li>Folder<button class="GoogleDriveFolder" data-id="${folder.id}" data-title="${folder.title}">${folder.title}</button></li>`)
-    const files = state.files.map((file) => `<li><button class="GoogleDriveFile" data-id="${file.id}" data-title="${file.title}">${file.title}</button></li>`)
+    const folders = state.folders.map((folder) => yo`<li>Folder<button class="GoogleDriveFolder" data-id="${folder.id}" data-title="${folder.title}">${folder.title}</button></li>`)
+    const files = state.files.map((file) => yo`<li><button class="GoogleDriveFile" data-id="${file.id}" data-title="${file.title}">${file.title}</button></li>`)
 
 
     return yo`
     return yo`
-      <ul>${folders}</ul>
-      <ul>${files}</ul>
+      <div>
+        <ul>${folders}</ul>
+        <ul>${files}</ul>
+      </div>
     `
     `
   }
   }
 
 

+ 59 - 26
test/unit/GoogleDrive.spec.js

@@ -1,46 +1,43 @@
 import test from 'tape'
 import test from 'tape'
 import nock from 'nock'
 import nock from 'nock'
+import Utils from '../../src/core/Utils'
 import Google from '../../src/plugins/GoogleDrive'
 import Google from '../../src/plugins/GoogleDrive'
 
 
 test('checkAuthentication success', function (t) {
 test('checkAuthentication success', function (t) {
-  nock('http://localhost:3020')
-    .get('/google/authorize')
-    .reply(200, {
-      isAuthenticated: true
-    })
+  t.plan(1)
+
   nock('http://localhost:3020')
   nock('http://localhost:3020')
     .get('/google/authorize')
     .get('/google/authorize')
     .reply(200, {
     .reply(200, {
       isAuthenticated: true
       isAuthenticated: true
     })
     })
 
 
-  var GoogleDrive = new Google()
+  var GoogleDrive = new Google(null, {host: 'http://localhost:3020'})
   GoogleDrive.checkAuthentication()
   GoogleDrive.checkAuthentication()
-    .then((isAuthed) => t.equal(isAuthed, true))
-
-  t.end()
+    .then((isAuthed) => {
+      t.equal(isAuthed, true)
+    })
 })
 })
 
 
 test('checkAuthentication fail', function (t) {
 test('checkAuthentication fail', function (t) {
-  nock('http://localhost:3020')
-    .get('/google/authorize')
-    .reply(200, {
-      isAuthenticated: false
-    })
+  t.plan(1)
+
   nock('http://localhost:3020')
   nock('http://localhost:3020')
     .get('/google/authorize')
     .get('/google/authorize')
     .reply(200, {
     .reply(200, {
       isAuthenticated: false
       isAuthenticated: false
     })
     })
 
 
-  var GoogleDrive = new Google()
+  var GoogleDrive = new Google(null, {host: 'http://localhost:3020'})
   GoogleDrive.checkAuthentication()
   GoogleDrive.checkAuthentication()
-    .then((isAuthed) => t.equal(isAuthed, false))
-
-  t.end()
+    .then((isAuthed) => {
+      t.equal(isAuthed, false)
+    })
 })
 })
 
 
 test('getFile: success', function (t) {
 test('getFile: success', function (t) {
+  t.plan(1)
+
   nock('http://localhost:3020')
   nock('http://localhost:3020')
     .post('/google/get')
     .post('/google/get')
     .reply(201, (uri, requestBody) => {
     .reply(201, (uri, requestBody) => {
@@ -50,29 +47,65 @@ test('getFile: success', function (t) {
       }
       }
     })
     })
 
 
-  var GoogleDrive = new Google()
+  var GoogleDrive = new Google(null, {host: 'http://localhost:3020'})
 
 
   GoogleDrive.getFile('12345')
   GoogleDrive.getFile('12345')
     .then((result) => {
     .then((result) => {
       t.equal(result.ok, true)
       t.equal(result.ok, true)
     })
     })
-
-  t.end()
 })
 })
 
 
 test('getFile: fileId not a string', function (t) {
 test('getFile: fileId not a string', function (t) {
-  var GoogleDrive = new Google()
+  t.plan(1)
+
+  var GoogleDrive = new Google(null, {host: 'http://localhost:3020'})
   var result = GoogleDrive.getFile()
   var result = GoogleDrive.getFile()
 
 
   t.equal(result instanceof Error, true)
   t.equal(result instanceof Error, true)
-
-  t.end()
 })
 })
 
 
 test('getFolder: success', function (t) {
 test('getFolder: success', function (t) {
-  t.end()
+  t.plan(1)
+
+  nock('http://localhost:3020')
+  .get('/google/list')
+  .reply(200, {
+    items: [{
+      mimeType: 'application/vnd.google-apps.folder'
+    }, {
+      mimeType: 'application/vnd.google-apps.spreadsheet'
+    }, {
+      mimeType: 'application/vnd.google-apps.spreadsheet'
+    }, {
+      mimeType: 'application/vnd.google-apps.folder'
+    }]
+  })
+
+  var GoogleDrive = new Google(null, {host: 'http://localhost:3020'})
+  GoogleDrive.getFolder('/')
+    .then((res) => {
+      const allFolders = Utils.every(res.folders, function (folder) {
+        return folder.mimeType === 'application/vnd.google-apps.folder'
+      })
+
+      const allFiles = Utils.every(res.files, (file) => {
+        return file.mimeType !== 'application/vnd.google-apps.folder'
+      })
+
+      t.equal(allFolders && allFiles, true)
+    })
 })
 })
 
 
 test('getFolder: fail', function (t) {
 test('getFolder: fail', function (t) {
-  t.end()
+  t.plan(1)
+
+  nock('http://localhost:3020')
+  .get('/google/list')
+  .reply(500, 'Not authenticated')
+
+  var GoogleDrive = new Google(null, {host: 'http://localhost:3020'})
+  GoogleDrive.getFolder('/')
+    .then((err) => {
+      t.equal(err instanceof Error, true)
+    })
 })
 })

+ 1 - 0
test/unit/index.js

@@ -1,4 +1,5 @@
 require('babel-register')
 require('babel-register')
+require('isomorphic-fetch')
 require('./core.spec.js')
 require('./core.spec.js')
 require('./translator.spec.js')
 require('./translator.spec.js')
 require('./GoogleDrive.spec.js')
 require('./GoogleDrive.spec.js')

+ 1 - 1
website/package.json

@@ -40,4 +40,4 @@
     "remark": "4.2.0",
     "remark": "4.2.0",
     "watchify": "3.7.0"
     "watchify": "3.7.0"
   }
   }
-}
+}

+ 0 - 5
website/src/examples/drive/app.css

@@ -1,5 +0,0 @@
-ul#target {
-  border: 1px dashed orange;
-  width: 100%;
-  height: 300px;
-}

+ 6 - 6
website/src/examples/drive/app.es6

@@ -1,10 +1,10 @@
 import Uppy from 'uppy/core'
 import Uppy from 'uppy/core'
-import { Drive } from 'uppy/plugins'
-
-const uppy = new Uppy({wait: false})
+import { GoogleDrive } from 'uppy/plugins'
+import ProgressDrawer from '../../../../src/plugins/ProgressDrawer.js'
+import { UPPY_SERVER } from '../env'
+console.log(UPPY_SERVER)
 
 
+const uppy = new Uppy({debug: true, autoProceed: false})
 uppy
 uppy
-  .use(Drive, {selector: '#target'})
+  .use(GoogleDrive, { target: '#GoogleDriveContainer', host: UPPY_SERVER })
   .run()
   .run()
-
-console.log(uppy.type)

+ 4 - 2
website/src/examples/drive/app.html

@@ -1,2 +1,4 @@
-<ul id="target"></ul>
-<script src="//cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.10.2/dropbox.min.js"></script>
+<!-- Basic Uppy styles -->
+<link rel="stylesheet" href="/uppy/uppy.css">
+
+<div id="GoogleDriveContainer"></div>

+ 1 - 0
website/src/examples/env.js

@@ -0,0 +1 @@
+export const UPPY_SERVER = 'http://localhost:3020'

+ 0 - 0
website/src/examples/google/app.css


+ 0 - 8
website/src/examples/google/app.es6

@@ -1,8 +0,0 @@
-import Uppy from 'uppy/core'
-import { GoogleDrive } from 'uppy/plugins'
-import ProgressDrawer from '../../../../src/plugins/ProgressDrawer.js'
-
-const uppy = new Uppy({debug: true, autoProceed: false})
-uppy
-  .use(GoogleDrive, {target: '#GoogleDriveContainer'})
-  .run()

+ 0 - 4
website/src/examples/google/app.html

@@ -1,4 +0,0 @@
-<!-- Basic Uppy styles -->
-<link rel="stylesheet" href="/uppy/uppy.css">
-
-<div id="GoogleDriveContainer"></div>

+ 0 - 35
website/src/examples/google/index.ejs

@@ -1,35 +0,0 @@
----
-title: Google
-layout: example
-type: examples
-order: 1
----
-
-{% blockquote %}
-Showcasing a google drive integration
-{% endblockquote %}
-
-<link rel="stylesheet" href="app.css">
-<% include app.html %>
-<script src="app.js"></script>
-
-<hr />
-
-<p id="console-wrapper">
-  Console output (latest logs are at the top): <br />
-</p>
-
-<p>
-  On this page we're using the following HTML snippet:
-</p>
-{% include_code lang:html google/app.html %}
-
-<p>
-  Along with this JavaScript:
-</p>
-{% include_code lang:js google/app.es6 %}
-
-<p>
-  And the following CSS:
-</p>
-{% include_code lang:css google/app.css %}