diff --git a/.donejs/templates/component/modlet/component_test.js b/.donejs/templates/component/modlet/component_test.js new file mode 100644 index 0000000..a73df99 --- /dev/null +++ b/.donejs/templates/component/modlet/component_test.js @@ -0,0 +1,13 @@ +import 'steal-mocha'; +import chai from 'chai'; +import { ViewModel } from './<%= name %>'; + +let assert = chai.assert; + +// ViewModel unit tests +describe('<%= module %>', function(){ + it('Has message', function(){ + var vm = new ViewModel(); + assert.equal(vm.attr('message'), 'This is the <%= tag %> component'); + }); +}); diff --git a/.donejs/templates/component/modlet/test.html b/.donejs/templates/component/modlet/test.html new file mode 100644 index 0000000..4e68a7f --- /dev/null +++ b/.donejs/templates/component/modlet/test.html @@ -0,0 +1,3 @@ +<%= module %> + +
diff --git a/.donejs/templates/module/module_test.js b/.donejs/templates/module/module_test.js new file mode 100644 index 0000000..e9558dc --- /dev/null +++ b/.donejs/templates/module/module_test.js @@ -0,0 +1,12 @@ +import 'steal-mocha'; +import chai from 'chai'; +import module from './<%= name %>'; + +let assert = chai.assert; + +describe('<%= module %>', function(){ + it('Initialized the module', function(){ + assert.equal(typeof module, 'function'); + assert.equal(module(), 'This is the <%= name %> module'); + }); +}); diff --git a/.donejs/templates/module/test.html b/.donejs/templates/module/test.html new file mode 100644 index 0000000..4e68a7f --- /dev/null +++ b/.donejs/templates/module/test.html @@ -0,0 +1,3 @@ +<%= module %> + +
diff --git a/.donejs/templates/supermodel/model_test.js b/.donejs/templates/supermodel/model_test.js new file mode 100644 index 0000000..ca8ea24 --- /dev/null +++ b/.donejs/templates/supermodel/model_test.js @@ -0,0 +1,15 @@ +import 'steal-mocha'; +import chai from 'chai'; +import <%= className %> from './<%= name %>'; + +let assert = chai.assert; + +describe('models/<%= name %>', function() { + it('getList', function(done){ + <%= className %>.getList().then(function(items) { + assert.equal(items.length, 2); + assert.equal(items.attr('0.description'), 'First item'); + done(); + }); + }); +}); diff --git a/.gitignore b/.gitignore index e920c16..1318869 100644 --- a/.gitignore +++ b/.gitignore @@ -22,8 +22,10 @@ coverage # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release +dist/ # Dependency directory +# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git node_modules # Optional npm cache directory @@ -31,3 +33,9 @@ node_modules # Optional REPL history .node_repl_history + +# DoneJS +docs/ +build/ +dist/ +cache/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..c596943 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +6.4 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0b68420 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - "6" + - "5" +before_install: + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 853b46d..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index c580c55..5f1a177 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ -# www-client -The desktop and mobile web client for bithub.com. +# bithub-admin + +Welcome to the bithub-admin DoneJS application! + +## Getting started + +To install all dependencies, (e.g. after cloning it from a Git repository) run + +``` +npm install donejs -g +npm install +``` + +## Running tests + +Tests can be run with + +``` +donejs test +``` + +## Development mode + +Development mode can be started with + +``` +donejs develop +``` + +## Build and production mode + +To build the application into a production bundle run + +``` +donejs build +``` + +In Unix environment the production application can be started like this: + +``` +NODE_ENV=production npm start +``` diff --git a/bin/build.js b/bin/build.js new file mode 100644 index 0000000..ae80f5f --- /dev/null +++ b/bin/build.js @@ -0,0 +1,12 @@ +var path = require('path'); +var stealTools = require('steal-tools'); + +var buildPromise = stealTools.build({ + config: path.join(__dirname, '/package.json!npm') +}, { + bundleAssets: true +}); + +buildPromise.then(function () { + console.log('Build complete!'); +}); diff --git a/bithub-embed.js b/bithub-embed.js new file mode 100644 index 0000000..7a5df37 --- /dev/null +++ b/bithub-embed.js @@ -0,0 +1,121 @@ +/* global window */ +/* eslint no-lonely-if: 0 */ +/* eslint no-new: 0 */ + +import $ from 'jquery'; +import AppState from 'models/embed_appstate'; +import can from 'can'; +import Bit from 'models/bit'; +import Hub from 'models/hub'; +import BitList from 'bit-list/'; +import Communicator from 'communicator/'; +import 'can/route/'; +import 'style/embed.less!'; + +const params = can.deparam(window.location.search.substr(1)); +const isLoadedFromIframe = window.parent !== window; +const appState = new AppState(); + +const bodyClasses = [(isLoadedFromIframe ? 'iframe-context' : 'page-context'), 'embed']; + +const triggerPartition = (function () { + let partitionTimeout; + return function (bits) { + clearTimeout(partitionTimeout); + partitionTimeout = setTimeout(function () { + can.trigger(bits, 'partition'); + }, 100); + }; +})(); + +const kickstart = function (hub) { + const initApp = function () { + const div = $('
'); + + $('#app-wrapper').html(div); + console.log('INIT APP'); + new BitList(div, { + state: appState + }); + }; + const resetApp = (function () { + let timeout; + return function () { + clearTimeout(timeout); + setTimeout(function () { + appState.reset(); + initApp(); + }, 1); + }; + })(); + + const communicator = Communicator.bind(window.parent, { + updateAttrs: data => { + appState.setAttrs(data); + }, + reset: () => { + resetApp(); + } + }); + + bodyClasses.push((params.theme || 'light') + '-theme'); + + // can.route.map(appState); + // can.route.ready(); + + appState.attr('hub', hub); + appState.setAttrs(params); + appState.connectLiveService(); + + if (!appState.isPublic()) { + bodyClasses.push('admin-embed'); + } + + Bit.on('lifecycle', function (ev, bit) { + const serviceIds = bit.attr('service_ids'); + const bits = appState.attr('bits'); + const isLive = appState.isLive(); + + if (appState.isPublic() && isLive && bit.isPublic()) { + bits.place(bit); + } else { + if (isLive && bits.indexOf(bit) === -1 && bit.attr('decision') === appState.attr('decision')) { + bits.unshift(bit); + } + } + + triggerPartition(bits); + + if (serviceIds && (params.view !== 'public')) { + communicator.send('loadedBits', serviceIds); + } + }); + + Bit.on('disapproved', function (ev, bit) { + const bits = appState.attr('bits'); + const index = bits.indexOf(bit); + if (appState.isPublic() && index > -1) { + bits.splice(index, 1); + } + }); + + $('body').addClass(bodyClasses.join(' ')); + + appState.on('view', resetApp); + appState.on('order', resetApp); + appState.on('decision', resetApp); + appState.on('service_id', resetApp); + appState.on('image_only', resetApp); + + appState.on('theme', function (ev, newTheme) { + $('body').removeClass('dark-theme light-theme').addClass(newTheme + '-theme'); + }); + + initApp(); +}; + +Hub.findOne({id: params.hubId}).then(function (hub) { + kickstart(hub); +}, function () { + kickstart(); +}); diff --git a/bithub-social.html b/bithub-social.html new file mode 100644 index 0000000..94e4a9d --- /dev/null +++ b/bithub-social.html @@ -0,0 +1,12 @@ + + + + + + BitHub + + + +
+ + \ No newline at end of file diff --git a/bithub-social.js b/bithub-social.js new file mode 100644 index 0000000..6b30fa2 --- /dev/null +++ b/bithub-social.js @@ -0,0 +1,134 @@ +/* global window, document, navigator, alert */ +/* eslint quote-props: 0 */ +/* eslint babel/object-shorthand: 0 */ +/* eslint no-new: 0 */ +/* eslint no-alert: 0 */ + +import $ from 'jquery'; +import AppState from 'models/appstate'; +import can from 'can/'; +import Cookie from 'js-cookie'; +import Models from 'models/'; +import stache from 'can/view/stache/'; +import bindModelEvents from './bind-model-events'; +import initView from './bithub-social.stache!'; + +import 'can/map/define/'; +import 'components/'; +import 'components/helpers'; +import 'components/service-config-formatter/'; + +const kickstart = function (selector) { + const PresetChangeUpdater = can.Control.extend({ + '{appState} preset': 'updateIframeAttrs', + '{appState} customPreset change': 'updateIframeAttrs', + '{appState.adminPreset} change': 'updateIframeAttrs', + updateIframeAttrs: function () { + const self = this; + clearTimeout(this.__updateIframeAttrs); + this.__updateIframeAttrs = setTimeout(function () { + self.options.appState.updateIframeAttrs(); + }); + } + }); + + if (/Mobi/.test(navigator.userAgent)) { + if (Cookie.get('redirected-from-new-ui')) { + Cookie.remove('redirected-from-new-ui'); + } else { + window.location = '/new-bithub/'; + return; + } + } + + $.ajaxPrefilter(function (options, originalOptions) { + if (options.type.toLowerCase() !== 'get') { + options.data = JSON.stringify(originalOptions.data); + options.contentType = 'application/json'; + } + }); + + $.when(Models.Brand.findOne({}), Models.Subscription.findOne({}), Models.Account.current(), Models.Organization.current()).done(function (brand, subscription, account, organization) { + const appState = new AppState({ + currentBrand: brand, + currentSubscription: subscription, + currentAccount: account, + currentOrganization: organization, + embedType: 'admin' + }); + + new PresetChangeUpdater(document.documentElement, { + appState: appState + }); + + can.route('', {page: 'hub-list'}); + can.route(':page'); + can.route(':page/:panel'); + can.route(':page/:panel/:hubId'); + can.route(':page/:panel/:hubId'); + + can.route.map(appState); + + can.route.ready(); + + bindModelEvents(appState); + + stache.registerHelper('ifCanAddHub', function (hubs, opts) { + hubs = can.isFunction(hubs) ? hubs() : hubs; + const canAddHub = appState.attr('currentSubscription').canAddHub(hubs); + return canAddHub ? opts.fn() : opts.inverse(); + }); + + stache.registerHelper('ifCanAddService', function (services, feed, type, opts) { + feed = can.isFunction(feed) ? feed() : feed; + type = can.isFunction(type) ? type() : type; + services = can.isFunction(services) ? services() : services; + const canAddService = appState.attr('currentSubscription').canAddService(services, feed, type); + + return canAddService ? opts.fn() : opts.inverse(); + }); + + $(selector).html(initView({ + state: appState, + switchOrganization: function (organization) { + Models.Organization.choose(organization.id).then(function () { + window.location.reload(); + }, function () { + alert('There was a problem with the organization switching'); + }); + } + }, { + renderIframe: function (iframe) { + iframe = can.isFunction(iframe) ? iframe() : iframe; + return iframe; + }, + renderPage: function () { + const page = can.route.attr('page') || 'hub-list'; + const template = can.stache(''); + + return template(this); + }, + pageLink: function (page, title) { + page = can.isFunction(page) ? page() : page; + title = can.isFunction(title) ? title() : title; + + const currentPage = can.route.attr('page'); + const props = { + class: 'btn ' + }; + + props.class += page === currentPage ? 'btn-default' : 'btn-link'; + + return can.route.link(title, {page: page}, props, false); + }, + serviceStringTitle: function (service) { + service = can.isFunction(service) ? service() : service; + return $(can.stache('')({service: service})).text(); + } + })); + }).fail(function () { + console.log('FAIL', arguments); + }); +}; + +kickstart('#app'); diff --git a/bithub-social.stache b/bithub-social.stache new file mode 100644 index 0000000..7ba6f2c --- /dev/null +++ b/bithub-social.stache @@ -0,0 +1,95 @@ +{{#if state.isSidebar}} + + + +
+
+
+ {{state.embedTypeTitle}} Mode + {{#unless state.isAdminEmbed}} + - {{state.preset.name}} + {{/unless}} + {{#if state.isAdminEmbed}} +
+ {{#if state.services.length}} + + {{/if}} + +
+ {{/if}} +
+
+ Back to Admin | {{state.hub.name}} +
+
+ {{#if state.isAdminEmbed}} +
+ +
+ {{/if}} +
+ {{renderIframe state.iframe}} +
+
+ +{{else}} + +
+
+

BitHub

+
+ +
+
+
+
+ {{{renderPage}}} +
+
+ +{{/if}} diff --git a/deploy-key.pem.enc b/deploy-key.pem.enc new file mode 100644 index 0000000..8dc1898 Binary files /dev/null and b/deploy-key.pem.enc differ diff --git a/development.html b/development.html new file mode 100644 index 0000000..bb12a65 --- /dev/null +++ b/development.html @@ -0,0 +1,9 @@ + + + + bithub-admin + + + + + \ No newline at end of file diff --git a/documentjs.json b/documentjs.json new file mode 100644 index 0000000..acac5df --- /dev/null +++ b/documentjs.json @@ -0,0 +1,14 @@ +{ + "sites": { + "docs": { + "dest": "docs", + "parent": "bithub-admin", + "pageConfig": { + "page": "docs" + }, + "glob": { + "pattern": "src/**/*.{js,md}" + } + } + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..36fbae7 --- /dev/null +++ b/package.json @@ -0,0 +1,101 @@ +{ + "name": "bithub-admin", + "version": "0.1.0", + "description": "Bithub Administration App", + "homepage": "bithub.com/admin", + "author": "Bitovi", + "scripts": { + "test": "testee src/test.html --browsers firefox --reporter Spec", + "lint": "xo", + "start": "done-serve --port 8080", + "develop": "done-serve --develop --port 8080", + "document": "documentjs", + "build": "node bin/build", + "preflight": "npm run lint && npm test && npm run build", + "push-to-git": "git push origin --tags", + "release:patch": "npm run preflight && npm version patch && npm run push-to-git", + "release:minor": "npm run preflight && npm version minor && npm run push-to-git", + "release:major": "npm run preflight && npm version major && npm run push-to-git" + }, + "main": "bithub-social.js", + "files": [ + "src" + ], + "keywords": [ + "bithub" + ], + "system": { + "main": "bithub-admin/index.stache!done-autorender", + "directories": { + "lib": "src" + }, + "configDependencies": [ + "live-reload", + "node_modules/can-zone/register" + ], + "npmAlgorithm": "flat", + "map": { + "chai": "chai/chai" + } + }, + "license": "ISC", + "dependencies": { + "can-component": "^3.0.0-pre.8", + "can-connect": "^0.6.0-pre.4", + "can-define": "^0.7.4", + "can-route": "^3.0.0-pre.5", + "can-route-pushstate": "^3.0.0-pre.3", + "can-stache": "^3.0.0-pre.6", + "can-validate": "^0.9.2", + "can-view-autorender": "^3.0.0-pre.3", + "can-view-model": "^3.0.0-pre.4", + "can-zone": "^0.5.5", + "done-autorender": "^0.9.0-pre.0", + "done-component": "^0.6.0-pre.1", + "done-css": "~2.1.0-pre.0", + "done-serve": "^0.3.0-pre.0", + "generator-donejs": "^0.10.0-pre.0", + "jquery": "~2.2.1", + "steal": "^0.16.0", + "steal-stache": "^3.0.0-pre.2", + "validate.js": "^0.10.0" + }, + "devDependencies": { + "can-fixture": "^0.1.2", + "chai": "^3.5.0", + "documentjs": "^0.4.2", + "donejs-cli": "^0.9.4", + "donejs-deploy": "^0.4.0", + "funcunit": "~3.0.0", + "steal-mocha": "0.0.3", + "steal-qunit": "^0.1.1", + "steal-tools": "^0.16.0", + "testee": "^0.2.4", + "xo": "^0.16.0" + }, + "xo": { + "esnext": true, + "envs": [ + "node", + "jasmine" + ], + "rules": { + "import/no-extraneous-dependencies": 0, + "prefer-arrow-callback": 0 + }, + "globals": [ + "steal" + ], + "ignore": [ + "node_modules/**", + "dist/**", + "docs/**" + ], + "overrides": [ + { + "files": "bin/**", + "esnext": false + } + ] + } +} diff --git a/production.html b/production.html new file mode 100644 index 0000000..b73c1a0 --- /dev/null +++ b/production.html @@ -0,0 +1,9 @@ + + + + bithub-admin + + + + + diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..f77fb69 --- /dev/null +++ b/src/app.js @@ -0,0 +1,24 @@ +import DefineMap from 'can-define/map/map'; +import route from 'can-route'; +import 'can-route-pushstate'; + +const AppViewModel = DefineMap.extend({ + message: { + value: 'Hello World!', + serialize: false + }, + title: { + value: 'bithub-admin', + serialize: false + }, + page: 'string', + slug: 'string', + action: 'string', + route: 'string' +}); + +route(':page', {page: 'home'}); +route(':page/:slug', {slug: null}); +route(':page/:slug/:action', {slug: null, action: null}); + +export default AppViewModel; diff --git a/src/components/components.js b/src/components/components.js new file mode 100644 index 0000000..e69de29 diff --git a/src/components/model-explorer/demo/demo.html b/src/components/model-explorer/demo/demo.html new file mode 100644 index 0000000..adee91e --- /dev/null +++ b/src/components/model-explorer/demo/demo.html @@ -0,0 +1,16 @@ + + + + + Model and Fixture Demo + + +

Model Explorer Demo

+
+ + + + + diff --git a/src/components/model-explorer/demo/demo.js b/src/components/model-explorer/demo/demo.js new file mode 100644 index 0000000..36b0836 --- /dev/null +++ b/src/components/model-explorer/demo/demo.js @@ -0,0 +1,19 @@ +import $ from 'jquery'; +import DefineMap from 'can-define/map/map'; +import Fixtures from 'can-fixture'; +import ViewModel from 'can-view-model'; +import 'bithub-admin/utils/debug-utils'; + +import 'can-stache'; +import './demo.less!'; +import 'bithub-admin/models/fixtures/'; +import '../model-explorer'; + +import template from './demo.stache!'; +import models from './models'; + +import './demo.less!'; + +$('#demo').append(template({ + models: models, +})); diff --git a/src/components/model-explorer/demo/demo.less b/src/components/model-explorer/demo/demo.less new file mode 100644 index 0000000..2db0187 --- /dev/null +++ b/src/components/model-explorer/demo/demo.less @@ -0,0 +1,11 @@ + +@import '../model-explorer.less'; +#demo { + model-explorer { + .response-container { + float: right; + width: 50vw; + height: 60vh; + } + } +} diff --git a/src/components/model-explorer/demo/demo.stache b/src/components/model-explorer/demo/demo.stache new file mode 100644 index 0000000..804bc70 --- /dev/null +++ b/src/components/model-explorer/demo/demo.stache @@ -0,0 +1 @@ + diff --git a/src/components/model-explorer/demo/models.js b/src/components/model-explorer/demo/models.js new file mode 100644 index 0000000..db4ca9a --- /dev/null +++ b/src/components/model-explorer/demo/models.js @@ -0,0 +1,28 @@ +import Organization from 'bithub-admin/models/organization'; + +var models = {}; + +export default models; + +models['organization'] = { + label: 'Organization', + methods: [ + {value: 'getList', label: 'Get List'}, + { + value: 'get', + label: 'Get (one)', + defaultRequest: { + id: '' + } + }, + { + value:'invite', + label: 'Invite', + defaultRequest: { + email: '' + }, + useMethodInstance: true + } + ], + Model: Organization +}; diff --git a/src/components/model-explorer/model-explorer.js b/src/components/model-explorer/model-explorer.js new file mode 100644 index 0000000..c60ee69 --- /dev/null +++ b/src/components/model-explorer/model-explorer.js @@ -0,0 +1,68 @@ +/** + * @module {can.Component} model-explorer Model Explorer + * @parent utilities + * @group utilities.model-explorer.viewModel 0 ViewModel + * @group utilities.model-explorer.events 1 Events + * + * + * @description + * The Model Explorer is a tool that executes method calls on a specified model. + * The explorer can be configured based on the `models` object passed to it, + * desired models and methods can be passed. + * The explorer has a params JSON editor for adding params to the method call. It + * has some basic validation; the value must be valid JSON. + * The call response, whether successful or not, will print in the result area. + * The explorer can operate against the proxied API or using fixtures. + * + * + * @signature '' + * + * @param {can.Map|Object} models An object of objects. Configuration object that + * contains the desired models and their methods. + * + * + * @body + * + * ## Component Initialization + * + * ```html + * + * ``` + * + */ + +import $ from 'jquery'; +import Component from 'can-component'; +import template from './model-explorer.stache!'; +import ViewModel from './viewmodel'; +import fixture from 'can-fixture'; + +export default Component.extend({ + tag: 'model-explorer', + template, + ViewModel, + helpers: { + objectToString: function (obj, opts) { + var resp = obj(); + return JSON.stringify(resp.serialize?resp.serialize():resp, null, 4); + } + }, + events: { + + /** + * @function events.inserted Inserted + * @description Bootstraps the useFixtures variable. + */ + inserted: function () { + this.viewModel.useFixtures = fixture.on; + }, + + /** + * @function events.useFixtures Use Fixtures Change + * @description Updates fixtures setting when useFixtures is updated. + */ + '{viewModel} useFixtures': function (ctx, opts, useFixtures) { + fixture.on = !!useFixtures; + } + } +}); diff --git a/src/components/model-explorer/model-explorer.less b/src/components/model-explorer/model-explorer.less new file mode 100644 index 0000000..2f9100a --- /dev/null +++ b/src/components/model-explorer/model-explorer.less @@ -0,0 +1,10 @@ +pui-model-explorer { + .response-container { + height: 80vh; + } + .alert-inpage { + .message { + margin: 0 5px 0 40px; + } + } +} diff --git a/src/components/model-explorer/model-explorer.stache b/src/components/model-explorer/model-explorer.stache new file mode 100644 index 0000000..b851e10 --- /dev/null +++ b/src/components/model-explorer/model-explorer.stache @@ -0,0 +1,92 @@ +
+ {{^Model}} +
+
+
+ Select a valid model to start exploring... +
+
+
+ {{/Model}} +
+ + {{#if Model}} +
+ {{#if modelMethods.length}} + + {{/if}} +
+ + {{#if showErrors}} +
+
+ {{#errors.params}} + {{.}} + {{/errors.params}} + {{#each errors.validation}} + {{#each .}} +

{{.}}

+ {{/each}} + {{/each}} + {{#if errors.validation.hasErrors}} +

Validation errors, + {{#if mustValidate}} + method not called + {{else}} + method called + {{/if}} + .

+ {{/if}} +
+
+ {{/if}} + + + + + {{#if selectedMethodOptions.useMethodInstance}} + + {{/if}} + {{/if}} +
+
+ + +
+
diff --git a/src/components/model-explorer/viewmodel.js b/src/components/model-explorer/viewmodel.js new file mode 100644 index 0000000..a46b892 --- /dev/null +++ b/src/components/model-explorer/viewmodel.js @@ -0,0 +1,324 @@ +import DefineMap from 'can-define/map/map'; +import DefineList from 'can-define/list/list'; + +const Errors = DefineMap.extend({ + form: { + Value: Array + }, + params: { + Value: Array + } +}); + +/** @add model-exlorer.viewModel */ +export default DefineMap.extend({ + + /** + * @property {can.Map} errors Errors + * @description Errors container object. Errors should follow the following structure: + * ``` + error['{keyName}'] = [ + '{STRING}' + ]; + ``` + */ + errors: Errors, + + /** + * @property {can.Map|Object} models Models + * @description Configuration object that contains desired models and their methods. + */ + models: { + Value: Object + }, + + /** + * @property {array} modelOptions Model Options + * @description Parses the listing of models into a value/label pair of objects that + * the select-input component can use to render a select control. + */ + modelOptions: { + get: function () { + var models = this.models; + var resp = []; + models.each(function (Model, key) { + var item = {value: key, label: Model.label}; + resp.push(item); + }); + + return resp; + } + }, + + /** + * @property {string} selectedModel Selected Model + * @description The key of the selected model. + */ + selectedModel: { + value: '', + type: 'string', + set: function (val) { + this.resetRequest(); + return val; + } + }, + + /** + * @property {can.Model} Model Model + * @description The current active Model constructor. Returns false if no selection has been made. + */ + Model: { + get: function () { + const models = this.models; + const selectedModel = this.selectedModel; + return models[selectedModel]? models[selectedModel].Model: false; + } + }, + + /** + * @property {string} modelMethods Model Methods + * @description The methods for the current selected model. They should match + * the structure required by the select-input compontent: + * `{value:"{{VALUE}}", label:"{{LABEL}}"}` + */ + modelMethods: { + get: function () { + const selectedModel = this.selectedModel; + const models = this.models; + let resp = []; + if (selectedModel) { + resp = models[selectedModel].methods; + } + return new DefineList(resp); + } + }, + + /** + * @property {object} selectedMethodOptions Selected Method Options + * @description The selected method object, contains useful data for + * other parts of the tool. + */ + selectedMethodOptions: { + Value: Object, + set: function (val) { + if (val.defaultRequest) { + this.params = val.defaultRequest; + } + return val; + } + }, + + /** + * @property {string} selectedMethod Seleted Method + * @description The selected method's key, which should be the method name itself. + */ + selectedMethod: { + value: '', + type: 'string', + set: function (val) { + this.resetRequest(); + var self = this; + var selectedModel = this.selectedModel; + var models = this.models; + // Get the method object for the selected Model. + if (selectedModel && models && models[selectedModel]) { + var methods = models[selectedModel].methods; + methods.each(function (method) { + if (method.value === val) { + self.selectedMethodOptions = method; + return false; + } + }); + } + return val; + } + }, + + /** + * @property {can.Map|Object} params Params + * @description The parameters object parsed from the params editor string. + */ + params: { + Value: Object, + Type: DefineMap + }, + + /** + * @property {string} paramsString Params String + * @description The JSON string that the user interacts with for the params. + */ + paramsString: { + set: function (val) { + if (typeof val === 'string') { + try { + val = JSON.parse(val); + this.resetRequest(); + this.params = val; + return val; + } catch(e) { + this.errors.params = ['Invalid JSON']; + } + } + }, + get: function () { + var params = this.params; + var val = JSON.stringify(params.serialize(), null, 4); + + if (!val) { + val = '{}'; + } + + return val; + } + }, + + /** + * @property {object} response Response + * @description The response from the method, renders the errors response if + * call was unsuccesful. + */ + response: { + Value: Object + }, + + /** + * @property {boolean} useFixtures Use Fixtures + * @description Sets fixtures on or off. + */ + useFixtures: { + value: true, + type: 'boolean', + set: function (val) { + this.resetRequest(); + return val; + } + }, + + /** + * @property {can.List|array} usageLog Usage Log + * @description Log storage + */ + usageLog: { + Value: Array + }, + + /** + * @property {boolean} showErrors Show Errors + * @description Tells template to render errors if params or validation + * errors exist + */ + showErrors: { + get: function () { + var errors = this.errors; + // check for validation errors + var validationErrors = errors.validation && errors.validation.hasErrors(); + + // check for params errors + return validationErrors || errors.params ? true : false; + } + }, + + /** + * @property {boolean} mustValidate Must Validate + * @description Will not execute method if true and validation returns errors. + */ + mustValidate: { + value: true, + type: 'boolean', + set: function (val) { + this.resetRequest(); + return val; + } + }, + + /** + * @property {string} version Version + * @description The version of the data, needed for POST/DELETE/PUT calls + * to work with fixtures. + */ + version: { + get: function () { + var params = this.params; + return params.version || ''; + } + }, + + clearLocalStorage: { + type: 'boolean', + value: false + }, + + /** + * @function clearResponse Clear Response + * @description Clears the response field. + */ + clearResponse: function () { + this.response = {}; + }, + + resetRequest: function () { + this.errors = {}; + this.response = {}; + this.params = {}; + }, + toggleProp: function (propName) { + const val = this[propName]; + this.updateProp(propName, !val); + }, + updateProp: function (propName, val) { + this[propName] = val; + }, + + /** + * @function submit Submit + * @description Submits the params to the selected model and method. + */ + submit: function () { + var vm = this; + var method = this.selectedMethod; + var Model = this.Model; + var params = this.params; + var methodOpts = this.selectedMethodOptions; + var Chain; + + vm.errors = {}; + vm.response = {}; + + if (method && Model && params) { + vm.usageLog.push(`Calling... ${this.selectedModel}.${method}(${this.paramsString})`); + + if (vm.clearLocalStorage && localStorage) { + vm.usageLog.push('Clearing local storage'); + localStorage.clear(); + } + if (methodOpts.useMethodInstance) { + var instance = new Model(params.serialize()); + //check validation, if any + if (instance.validate) { + instance.validate(); + this.errors.validation = instance.errors; + if (instance.errors.hasErrors() === true && this.mustValidate === true) { + return; + } + } + + Chain = instance[method](); + } else { + Chain = Model[method](params.serialize()); + } + + Chain.then(function (resp) { + vm.usageLog.push('Call completed successfully.'); + vm.response = resp; + }, function (err) { + vm.usageLog.push('Call failed! Use browser dev tools to debug.'); + vm.response = err; + }); + } else { + vm.usageLog.push('Oops. Missing method/Model and/or params.'); + debugger; + vm.errors.form.push('Invalid method selected.'); + } + + } +}); diff --git a/src/index.md b/src/index.md new file mode 100644 index 0000000..c4bf62e --- /dev/null +++ b/src/index.md @@ -0,0 +1,5 @@ +@page bithub-admin + +# bithub-admin + +This is the documentation for bithub-admin \ No newline at end of file diff --git a/src/index.stache b/src/index.stache new file mode 100644 index 0000000..33ecbf6 --- /dev/null +++ b/src/index.stache @@ -0,0 +1,20 @@ + + + {{title}} + + + + + +

{{message}}

+ + {{#switch env.NODE_ENV}} + {{#case "production"}} + + {{/case}} + {{#default}} + + {{/default}} + {{/switch}} + + diff --git a/src/models/account-invitation.js b/src/models/account-invitation.js new file mode 100644 index 0000000..30a43ff --- /dev/null +++ b/src/models/account-invitation.js @@ -0,0 +1,26 @@ +import DefineMap from 'can-define/map/'; +import DefineList from 'can-define/list/'; +import superMap from 'can-connect/can/super-map/'; +import tag from 'can-connect/can/tag/'; + +export const AccountInvitation = DefineMap.extend({ + seal: false +}, { + 'id': '*' +}); + +AccountInvitation.List = DefineList.extend({ + '*': AccountInvitation +}); + +export const accountInvitationConnection = superMap({ + url: '/accounts/invitation', + idProp: 'id', + Map: AccountInvitation, + List: AccountInvitation.List, + name: 'account-invitation' +}); + +tag('account-invitation-model', accountInvitationConnection); + +export default AccountInvitation; diff --git a/src/models/account-invitation_test.js b/src/models/account-invitation_test.js new file mode 100644 index 0000000..f207c05 --- /dev/null +++ b/src/models/account-invitation_test.js @@ -0,0 +1,15 @@ +import 'steal-mocha'; +import chai from 'chai'; +import AccountInvitation from './account-invitation'; + +let assert = chai.assert; + +describe('models/account-invitation', function() { + it('getList', function(done){ + AccountInvitation.getList().then(function(items) { + assert.equal(items.length, 2); + assert.equal(items.attr('0.description'), 'First item'); + done(); + }); + }); +}); diff --git a/src/models/fixtures/account-invitation.js b/src/models/fixtures/account-invitation.js new file mode 100644 index 0000000..6537347 --- /dev/null +++ b/src/models/fixtures/account-invitation.js @@ -0,0 +1,19 @@ +import fixture from 'can-fixture'; + +const store = fixture.store([{ + id: 0, + description: 'First item' +}, { + id: 1, + description: 'Second item' +}]); + +fixture({ + 'GET /account-invitation': store.findAll, + 'GET /account-invitation/{id}': store.findOne, + 'POST /account-invitation': store.create, + 'PUT /account-invitation/{id}': store.update, + 'DELETE /account-invitation/{id}': store.destroy +}); + +export default store; diff --git a/src/models/fixtures/data/current-account.json b/src/models/fixtures/data/current-account.json new file mode 100644 index 0000000..e4f381d --- /dev/null +++ b/src/models/fixtures/data/current-account.json @@ -0,0 +1,27 @@ +{ + "id": 614, + "email": "mark@bitovi.com", + "name": null, + "organizations": [ + { + "id": 69, + "name": "Taylored Wines" + }, + { + "id": 129, + "name": "Wow Bao" + }, + { + "id": 540, + "name": "Harness Digital Marketing" + }, + { + "id": 35, + "name": "Bithub sales org" + }, + { + "id": 630, + "name": "Mark Stahl" + } + ] +} \ No newline at end of file diff --git a/src/models/fixtures/data/current-brand.json b/src/models/fixtures/data/current-brand.json new file mode 100644 index 0000000..2238d59 --- /dev/null +++ b/src/models/fixtures/data/current-brand.json @@ -0,0 +1,47 @@ +{ + "id": 528, + "name": "benevolent_foliage_3723", + "tenant_name": "benevolent_foliage_3723", + "identities": [ + { + "id": 219, + "provider": "facebook", + "uid": "402714476593110", + "brand_id": 528, + "created_at_timestamp": 1447171332, + "name": "Tom Harness" + }, + { + "id": 222, + "provider": "twitter", + "uid": "1209655969", + "brand_id": 528, + "created_at_timestamp": 1447171498, + "name": "HarnessDM" + }, + { + "id": 223, + "provider": "google_oauth2", + "uid": "108192771246396963416", + "brand_id": 528, + "created_at_timestamp": 1447171533, + "name": "Harness Digital Marketing" + }, + { + "id": 220, + "provider": "foursquare", + "uid": "1820651", + "brand_id": 528, + "created_at_timestamp": 1447171355, + "name": "Tom Harness" + }, + { + "id": 221, + "provider": "instagram", + "uid": "1283955329", + "brand_id": 528, + "created_at_timestamp": 1447171382, + "name": "harnessdigitalmarketing" + } + ] +} \ No newline at end of file diff --git a/src/models/fixtures/data/current-organization.json b/src/models/fixtures/data/current-organization.json new file mode 100644 index 0000000..238c34b --- /dev/null +++ b/src/models/fixtures/data/current-organization.json @@ -0,0 +1,38 @@ +{ + "id": 540, + "name": "Harness Digital Marketing", + "accounts": [ + { + "id": 513, + "email": "info@harnessdigitalmarketing.com", + "name": null + }, + { + "id": 39, + "email": "mihael+bithub@bitovi.com", + "name": "Mihael Konjevic" + }, + { + "id": 3, + "email": "neektza@gmail.com", + "name": "Nikica glavni" + }, + { + "id": 238, + "email": "skyler@bitovi.com", + "name": "Skyler Taylor" + }, + { + "id": 614, + "email": "mark@bitovi.com", + "name": null + } + ], + "invitations": [ + { + "id": 4, + "email": "justin@bitovi.com", + "name": "Justin Meyer" + } + ] +} \ No newline at end of file diff --git a/src/models/fixtures/data/current-subscription.json b/src/models/fixtures/data/current-subscription.json new file mode 100644 index 0000000..bb56153 --- /dev/null +++ b/src/models/fixtures/data/current-subscription.json @@ -0,0 +1,10 @@ +{ + "id": 526, + "status": null, + "card": { + "exp_month": "6", + "exp_year": "2019", + "type": "Visa", + "last4": "8508" + } +} \ No newline at end of file diff --git a/src/models/fixtures/data/embeds.js b/src/models/fixtures/data/embeds.js new file mode 100644 index 0000000..0638c1b --- /dev/null +++ b/src/models/fixtures/data/embeds.js @@ -0,0 +1,20 @@ +[ + { + "id": 3, + "name": "2016 Herrin Chamber Annual Banquet", + "approved_by_default": true, + "published": true + }, + { + "id": 2, + "name": "Hospice of Southern Illinois Gala", + "approved_by_default": false, + "published": true + }, + { + "id": 1, + "name": "Harness Digital Marketing", + "approved_by_default": false, + "published": true + } +] \ No newline at end of file diff --git a/src/models/fixtures/data/services-embed-1.json b/src/models/fixtures/data/services-embed-1.json new file mode 100644 index 0000000..67bf766 --- /dev/null +++ b/src/models/fixtures/data/services-embed-1.json @@ -0,0 +1,33 @@ +[ + { + "id": 34, + "feed_name": "instagram", + "type_name": "tag", + "brand_identity_id": 221, + "approved_by_default": null, + "state": "loaded", + "entity_count": 46, + "config": { + "tag": "SIMensExpo", + "display_name": "#SIMensExpo" + }, + "error": { + "klass": "Fetchers::AuthError", + "message": "GET https:\/\/api.instagram.com\/v1\/tags\/SIMensExpo\/media\/recent.json?client_id=01f063b895ee4b5382f3cdeb7d893620&count=100: 400: The access_token provided is invalid." + } + }, + { + "id": 35, + "feed_name": "twitter", + "type_name": "hashtag", + "brand_identity_id": 222, + "approved_by_default": null, + "state": "loaded", + "entity_count": 86, + "config": { + "hashtag": "simensexpo", + "display_name": "#simensexpo" + }, + "error": null + } +] \ No newline at end of file diff --git a/src/models/fixtures/data/services-embed-2.json b/src/models/fixtures/data/services-embed-2.json new file mode 100644 index 0000000..7237e8e --- /dev/null +++ b/src/models/fixtures/data/services-embed-2.json @@ -0,0 +1,75 @@ +[ + { + "id": 13, + "feed_name": "facebook", + "type_name": "page", + "brand_identity_id": 219, + "approved_by_default": null, + "state": "loaded", + "entity_count": 589, + "config": { + "id": "184262829024", + "display_name": "Hospice of Southern Illinois, INC." + }, + "error": null + }, + { + "id": 16, + "feed_name": "instagram", + "type_name": "tag", + "brand_identity_id": 221, + "approved_by_default": null, + "state": "loaded", + "entity_count": 98, + "config": { + "tag": "BeBurgundy", + "display_name": "#BeBurgundy" + }, + "error": { + "klass": "Fetchers::AuthError", + "message": "GET https:\/\/api.instagram.com\/v1\/tags\/BeBurgundy\/media\/recent.json?client_id=01f063b895ee4b5382f3cdeb7d893620&count=100: 400: The access_token provided is invalid." + } + }, + { + "id": 12, + "feed_name": "twitter", + "type_name": "hashtag", + "brand_identity_id": 222, + "approved_by_default": null, + "state": "loaded", + "entity_count": 124, + "config": { + "hashtag": "BeBurgundy", + "display_name": "#BeBurgundy" + }, + "error": null + }, + { + "id": 26, + "feed_name": "twitter", + "type_name": "user_timeline", + "brand_identity_id": 222, + "approved_by_default": null, + "state": "loaded", + "entity_count": 458, + "config": { + "handle": "wwwhospiceorg", + "display_name": "@wwwhospiceorg" + }, + "error": null + }, + { + "id": 17, + "feed_name": "twitter", + "type_name": "followers", + "brand_identity_id": 222, + "approved_by_default": null, + "state": "loaded", + "entity_count": 334, + "config": { + "handle": "@wwwhospiceorg", + "display_name": "@@wwwhospiceorg" + }, + "error": null + } +] \ No newline at end of file diff --git a/src/models/fixtures/data/services-embed-3.json b/src/models/fixtures/data/services-embed-3.json new file mode 100644 index 0000000..b98db7c --- /dev/null +++ b/src/models/fixtures/data/services-embed-3.json @@ -0,0 +1,47 @@ +[ + { + "id": 15, + "feed_name": "facebook", + "type_name": "page", + "brand_identity_id": 219, + "approved_by_default": null, + "state": "loaded", + "entity_count": 392, + "config": { + "id": "114671828559378", + "display_name": "Herrin Chamber of Commerce" + }, + "error": null + }, + { + "id": 27, + "feed_name": "instagram", + "type_name": "tag", + "brand_identity_id": 221, + "approved_by_default": null, + "state": "loaded", + "entity_count": 21, + "config": { + "tag": "HerrinChamber", + "display_name": "#HerrinChamber" + }, + "error": { + "klass": "Fetchers::AuthError", + "message": "GET https:\/\/api.instagram.com\/v1\/tags\/HerrinChamber\/media\/recent.json?client_id=01f063b895ee4b5382f3cdeb7d893620&count=100: 400: The access_token provided is invalid." + } + }, + { + "id": 14, + "feed_name": "twitter", + "type_name": "hashtag", + "brand_identity_id": 222, + "approved_by_default": false, + "state": "loaded", + "entity_count": 495, + "config": { + "hashtag": "HerrinChamber", + "display_name": "#HerrinChamber" + }, + "error": null + } +] \ No newline at end of file diff --git a/src/models/fixtures/fixtures.js b/src/models/fixtures/fixtures.js new file mode 100644 index 0000000..8b315bb --- /dev/null +++ b/src/models/fixtures/fixtures.js @@ -0,0 +1,15 @@ +// Main file that loads all model fixtures +import fixture from 'can-fixture'; + +if (window) { + if (!window.can) { + window.can = {}; + } + window.can.fixture = fixture; +} + +import 'bithub-admin/models/fixtures/organization'; + +import 'bithub-admin/models/fixtures/organization-account'; + +import 'bithub-admin/models/fixtures/account-invitation'; diff --git a/src/models/fixtures/organization-account.js b/src/models/fixtures/organization-account.js new file mode 100644 index 0000000..22b2fa3 --- /dev/null +++ b/src/models/fixtures/organization-account.js @@ -0,0 +1,20 @@ +import fixture from 'can-fixture'; + + +const store = fixture.store([{ + id: 0, + description: 'First item' +}, { + id: 1, + description: 'Second item' +}]); + +fixture({ + 'GET /api/v3/current/organization/account': store.findAll, + 'GET /api/v3/current/organization/account/{id}': store.findOne, + 'POST /api/v3/current/organization/account': store.create, + 'PUT /api/v3/current/organization/account/{id}': store.update, + 'DELETE /api/v3/current/organization/account/{id}': store.destroy +}); + +export default store; diff --git a/src/models/fixtures/organization.js b/src/models/fixtures/organization.js new file mode 100644 index 0000000..ab2e5ea --- /dev/null +++ b/src/models/fixtures/organization.js @@ -0,0 +1,14 @@ +import fixture from 'can-fixture'; +import currentOrgData from './data/current-organization.json'; + +const store = fixture.store([currentOrgData]); + +fixture({ + 'GET /api/v3/current/organization': store.findAll, + 'GET /api/v3/current/organization/{id}': store.findOne, + 'POST /api/v3/current/organization': store.create, + 'PUT /api/v3/current/organization/{id}': store.update, + 'DELETE /api/v3/current/organization/{id}': store.destroy +}); + +export default store; diff --git a/src/models/organization-account.js b/src/models/organization-account.js new file mode 100644 index 0000000..ad1fa21 --- /dev/null +++ b/src/models/organization-account.js @@ -0,0 +1,29 @@ +import DefineMap from 'can-define/map/'; +import DefineList from 'can-define/list/'; +import superMap from 'can-connect/can/super-map/'; +import tag from 'can-connect/can/tag/'; + +export const OrganizationAccount = DefineMap.extend({ + seal: false +}, { + 'id': '*', + account: { + Value: Object + } +}); + +OrganizationAccount.List = DefineList.extend({ + '*': OrganizationAccount +}); + +export const organizationAccountConnection = superMap({ + url: '/api/v3/current/organization/accounts', + idProp: 'id', + Map: OrganizationAccount, + List: OrganizationAccount.List, + name: 'organization-account' +}); + +tag('organization-account-model', organizationAccountConnection); + +export default OrganizationAccount; diff --git a/src/models/organization-account_test.js b/src/models/organization-account_test.js new file mode 100644 index 0000000..7ffb02b --- /dev/null +++ b/src/models/organization-account_test.js @@ -0,0 +1,15 @@ +import 'steal-mocha'; +import chai from 'chai'; +import OrganizationAccount from './organization-account'; + +let assert = chai.assert; + +describe('models/organization-account', function() { + it('getList', function(done){ + OrganizationAccount.getList().then(function(items) { + assert.equal(items.length, 2); + assert.equal(items.attr('0.description'), 'First item'); + done(); + }); + }); +}); diff --git a/src/models/organization.js b/src/models/organization.js new file mode 100644 index 0000000..c07e06b --- /dev/null +++ b/src/models/organization.js @@ -0,0 +1,52 @@ +import $ from 'jquery'; +import superMap from 'can-connect/can/super-map/'; +import tag from 'can-connect/can/tag/'; +import DefineMap from 'can-define/map/map'; +import DefineList from 'can-define/list/list'; +import OrgAccount from 'bithub-admin/models/organization-account'; +import AccountInvitation from 'bithub-admin/models/account-invitation'; + +export const Organization = DefineMap.extend({ + seal: false, + current : function(){ + return this.findOne({}); + }, + choose : function(id){ + return $.ajax({ + url: '/api/v3/current/organization/choose?id=' + id, + type: 'PUT' + }); + } +},{ + id: 'number', + accounts: Array, + invitations: Array, + name: 'string', + invite : function(account){ + return OrgAccount.get({account: account}) + .then(function(res){ + return res.account; + }, function(){ + return AccountInvitation.get({account: {email: email}}); + }); + }, + addInvitation: function(invitation){ + this.invitations.push(invitation); + } +}); + +Organization.List = DefineList.extend({ + '*': Organization +}); + +export const organizationConnection = superMap({ + url: '/api/v3/current/organization', + idProp: 'id', + Map: Organization, + List: Organization.List, + name: 'organization' +}); + +tag('organization-model', organizationConnection); + +export default Organization; diff --git a/src/models/organization_test.js b/src/models/organization_test.js new file mode 100644 index 0000000..b01d60d --- /dev/null +++ b/src/models/organization_test.js @@ -0,0 +1,15 @@ +import 'steal-mocha'; +import chai from 'chai'; +import Organization from './organization'; + +let assert = chai.assert; + +describe('models/organization', function() { + it('getList', function(done){ + Organization.getList({}).then(function(items) { + assert.equal(items.length, 2); + assert.equal(items[0].description, 'First item'); + done(); + }); + }); +}); diff --git a/src/models/test.js b/src/models/test.js new file mode 100644 index 0000000..24de91e --- /dev/null +++ b/src/models/test.js @@ -0,0 +1,7 @@ +import './fixtures/'; + +import 'bithub-admin/models/organization_test'; + +import 'bithub-admin/models/organization-account_test'; + +import 'bithub-admin/models/account-invitation_test'; diff --git a/src/styles.less b/src/styles.less new file mode 100644 index 0000000..7202617 --- /dev/null +++ b/src/styles.less @@ -0,0 +1,4 @@ +body { + font-family: "Helvetica Neue-Light","Helvetica Neue Light","Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif; + font-weight: 300; +} \ No newline at end of file diff --git a/src/test.html b/src/test.html new file mode 100644 index 0000000..38c3c22 --- /dev/null +++ b/src/test.html @@ -0,0 +1,3 @@ +bithub-admin tests + + diff --git a/src/test/functional.js b/src/test/functional.js new file mode 100644 index 0000000..2730e6a --- /dev/null +++ b/src/test/functional.js @@ -0,0 +1,15 @@ +import F from 'funcunit'; +import mocha from 'steal-mocha'; + +F.attach(mocha); + +describe('bithub-admin functional smoke test', function(){ + beforeEach(function(){ + F.open('../development.html'); + }); + + it('bithub-admin main page shows up', function(){ + F('title').text('bithub-admin', 'Title is set'); + }); +}); + diff --git a/src/test/test.js b/src/test/test.js new file mode 100644 index 0000000..de108b8 --- /dev/null +++ b/src/test/test.js @@ -0,0 +1,2 @@ +import 'bithub-admin/models/test'; +import 'bithub-admin/test/functional'; diff --git a/src/utils/debug-utils.js b/src/utils/debug-utils.js new file mode 100644 index 0000000..58d0da3 --- /dev/null +++ b/src/utils/debug-utils.js @@ -0,0 +1,20 @@ +import $ from 'jquery' +import fixture from 'can-fixture'; +import viewModel from 'can-view-model'; + +export const can = {}; + +// Not sure how SSR handles window, so added this just in case +if (window) { + + // Make sure `can` is on window + if (!window.can) { + window.can = can; + } + + // Add all the things + can.$ = $; + can.fixture = fixture; + can.viewModel = viewModel; + +}