=a.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,u={},a=[],o=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(ya.map,e,0),0)},u.key=function(n){return a.push(n),u},u.sortKeys=function(n){return o[a.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},ya.set=function(n){var t=new u;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},r(u,{has:function(n){return qa+n in this},add:function(n){return this[qa+n]=!0,n},remove:function(n){return n=qa+n,n in this&&delete this[n]},values:function(){var n=[];return this.forEach(function(t){n.push(t)}),n},forEach:function(n){for(var t in this)t.charCodeAt(0)===Ta&&n.call(this,t.substring(1))}}),ya.behavior={},ya.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.substring(e+1),n=n.substring(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ya.event=null,ya.requote=function(n){return n.replace(ja,"\\$&")};var ja=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,La={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},Ha=function(n,t){return t.querySelector(n)},Fa=function(n,t){return t.querySelectorAll(n)},Pa=xa[o(xa,"matchesSelector")],Oa=function(n,t){return Pa.call(n,t)};"function"==typeof Sizzle&&(Ha=function(n,t){return Sizzle(n,t)[0]||null},Fa=function(n,t){return Sizzle.uniqueSort(Sizzle(n,t))},Oa=Sizzle.matchesSelector),ya.selection=function(){return Ia};var Ya=ya.selection.prototype=[];Ya.select=function(n){var t,e,r,i,u=[];n=v(n);for(var a=-1,o=this.length;++a =0?n.substring(0,t):n,r=t>=0?n.substring(t+1):"in";return e=yc.get(e)||vc,r=Mc.get(r)||mt,kr(r(e.apply(null,Array.prototype.slice.call(arguments,1))))},ya.interpolateHcl=Or,ya.interpolateHsl=Yr,ya.interpolateLab=Rr,ya.interpolateRound=Ur,ya.transform=function(n){var t=Ma.createElementNS(ya.ns.prefix.svg,"g");return(ya.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Ir(e?e.matrix:xc)})(n)},Ir.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var xc={a:1,b:0,c:0,d:1,e:0,f:0};ya.interpolateTransform=Br,ya.layout={},ya.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++e You may have mistyped the address or the page may have moved.
+
Drats! That Dashboard doesn't exist.
+
\ No newline at end of file
+
diff --git a/templates/project/widgets/image/image.scss b/templates/project/widgets/image/image.scss
index 10ca81d6..0b1a3163 100644
--- a/templates/project/widgets/image/image.scss
+++ b/templates/project/widgets/image/image.scss
@@ -6,8 +6,8 @@ $background-color: #4b4b4b;
// ----------------------------------------------------------------------------
// Widget-image styles
// ----------------------------------------------------------------------------
-.widget-image {
+.widget-image {
background-color: $background-color;
-}
\ No newline at end of file
+}
diff --git a/templates/project/widgets/list/list.coffee b/templates/project/widgets/list/list.coffee
index 3cbf7a14..00280731 100644
--- a/templates/project/widgets/list/list.coffee
+++ b/templates/project/widgets/list/list.coffee
@@ -3,4 +3,4 @@ class Dashing.List extends Dashing.Widget
if @get('unordered')
$(@node).find('ol').remove()
else
- $(@node).find('ul').remove()
\ No newline at end of file
+ $(@node).find('ul').remove()
diff --git a/templates/project/widgets/list/list.html b/templates/project/widgets/list/list.html
index 86752bff..07e04716 100644
--- a/templates/project/widgets/list/list.html
+++ b/templates/project/widgets/list/list.html
@@ -15,4 +15,4 @@
-
\ No newline at end of file
+
diff --git a/templates/project/widgets/list/list.scss b/templates/project/widgets/list/list.scss
index 90b6c841..bce70105 100644
--- a/templates/project/widgets/list/list.scss
+++ b/templates/project/widgets/list/list.scss
@@ -11,12 +11,12 @@ $moreinfo-color: rgba(255, 255, 255, 0.7);
// ----------------------------------------------------------------------------
// Widget-list styles
// ----------------------------------------------------------------------------
-.widget-list {
+.widget-list {
background-color: $background-color;
vertical-align: top;
- .title {
+ .title {
color: $title-color;
}
@@ -39,7 +39,7 @@ $moreinfo-color: rgba(255, 255, 255, 0.7);
}
.label {
- color: $label-color;
+ color: $label-color;
}
.value {
@@ -57,4 +57,4 @@ $moreinfo-color: rgba(255, 255, 255, 0.7);
color: $moreinfo-color;
}
-}
\ No newline at end of file
+}
diff --git a/templates/project/widgets/meter/meter.coffee b/templates/project/widgets/meter/meter.coffee
index 0e0a8ad3..b823ec7a 100644
--- a/templates/project/widgets/meter/meter.coffee
+++ b/templates/project/widgets/meter/meter.coffee
@@ -11,4 +11,4 @@ class Dashing.Meter extends Dashing.Widget
meter = $(@node).find(".meter")
meter.attr("data-bgcolor", meter.css("background-color"))
meter.attr("data-fgcolor", meter.css("color"))
- meter.knob()
\ No newline at end of file
+ meter.knob()
diff --git a/templates/project/widgets/meter/meter.html b/templates/project/widgets/meter/meter.html
index 7d51b15c..72592fb1 100644
--- a/templates/project/widgets/meter/meter.html
+++ b/templates/project/widgets/meter/meter.html
@@ -1,7 +1,7 @@
-
+
-
\ No newline at end of file
+
diff --git a/templates/project/widgets/meter/meter.scss b/templates/project/widgets/meter/meter.scss
index 98cf638c..da9ff0b4 100644
--- a/templates/project/widgets/meter/meter.scss
+++ b/templates/project/widgets/meter/meter.scss
@@ -11,16 +11,16 @@ $meter-background: darken($background-color, 15%);
// ----------------------------------------------------------------------------
// Widget-meter styles
// ----------------------------------------------------------------------------
-.widget-meter {
+.widget-meter {
background-color: $background-color;
-
- input.meter {
+
+ input.meter {
background-color: $meter-background;
color: #fff;
}
- .title {
+ .title {
color: $title-color;
}
@@ -31,5 +31,5 @@ $meter-background: darken($background-color, 15%);
.updated-at {
color: rgba(0, 0, 0, 0.3);
}
-
-}
\ No newline at end of file
+
+}
diff --git a/templates/project/widgets/number/number.coffee b/templates/project/widgets/number/number.coffee
index 46cd6c24..0e5950c0 100644
--- a/templates/project/widgets/number/number.coffee
+++ b/templates/project/widgets/number/number.coffee
@@ -13,8 +13,12 @@ class Dashing.Number extends Dashing.Widget
@accessor 'arrow', ->
if @get('last')
- if parseInt(@get('current')) > parseInt(@get('last')) then 'icon-arrow-up' else 'icon-arrow-down'
+ if parseInt(@get('current')) > parseInt(@get('last')) then 'fa fa-arrow-up' else 'fa fa-arrow-down'
onData: (data) ->
if data.status
- $(@get('node')).addClass("status-#{data.status}")
\ No newline at end of file
+ # clear existing "status-*" classes
+ $(@get('node')).attr 'class', (i,c) ->
+ c.replace /\bstatus-\S+/g, ''
+ # add new class
+ $(@get('node')).addClass "status-#{data.status}"
diff --git a/templates/project/widgets/number/number.html b/templates/project/widgets/number/number.html
index d7eeab9d..c82e5f4a 100644
--- a/templates/project/widgets/number/number.html
+++ b/templates/project/widgets/number/number.html
@@ -1,11 +1,11 @@
-
+
-
haml
'
+ end
+ end
+
+ def test_get_nonexistent_dashboard
+ with_generated_project do
+ get '/nodashboard'
+ assert_equal 404, last_response.status
+ end
+ end
+
+ def test_get_widget
+ with_generated_project do
+ get '/views/meter.html'
+ assert_equal 200, last_response.status
+ assert_includes last_response.body, 'class="meter"'
+ end
+ end
+
+ def with_generated_project
+ source_path = File.expand_path('../../templates', __FILE__)
+
+ temp do |dir|
+ cli = Dashing::CLI.new
+ cli.stubs(:source_paths).returns([source_path])
+ silent { cli.new 'new_project' }
+
+ app.settings.public_folder = File.join(dir, 'new_project/public')
+ app.settings.views = File.join(dir, 'new_project/dashboards')
+ app.settings.root = File.join(dir, 'new_project')
+ yield app.settings.root
+ end
+ end
+
+ def app
+ Sinatra::Application
+ end
+
+ def parse_data(string)
+ JSON.parse string[/data: (.+)/, 1]
+ end
+
+ def parse_event(string)
+ string[/event: (.+)/, 1]
+ end
+end
diff --git a/test/cli_test.rb b/test/cli_test.rb
new file mode 100644
index 00000000..4d822969
--- /dev/null
+++ b/test/cli_test.rb
@@ -0,0 +1,168 @@
+require 'test_helper'
+
+class CLITest < Dashing::Test
+ def setup
+ @cli = Dashing::CLI.new
+ end
+
+ def test_new_task_creates_project_directory
+ app_name = 'custom_dashboard'
+ @cli.stubs(:directory).with(:project, app_name).once
+ @cli.new(app_name)
+ end
+
+ def test_generate_task_delegates_to_type
+ types = %w(widget dashboard job)
+
+ types.each do |type|
+ @cli.stubs(:public_send).with("generate_#{type}".to_sym, 'name').once
+ @cli.generate(type, 'name')
+ end
+ end
+
+ def test_generate_task_warns_when_generator_is_not_defined
+ output, _ = capture_io do
+ @cli.generate('wtf', 'name')
+ end
+
+ assert_includes output, 'Invalid generator'
+ end
+
+ def test_generate_widget_creates_a_new_widget
+ @cli.stubs(:directory).with(:widget, 'widgets').once
+ @cli.generate_widget('WidgetName')
+ assert_equal 'widget_name', @cli.name
+ end
+
+ def test_generate_dashboard_creates_a_new_dashboard
+ @cli.stubs(:directory).with(:dashboard, 'dashboards').once
+ @cli.generate_dashboard('DashBoardName')
+ assert_equal 'dash_board_name', @cli.name
+ end
+
+ def test_generate_job_creates_a_new_job
+ @cli.stubs(:directory).with(:job, 'jobs').once
+ @cli.generate_job('MyCustomJob')
+ assert_equal 'my_custom_job', @cli.name
+ end
+
+ def test_install_task_requests_gist_from_downloader
+ return_value = { 'files' => [] }
+ Dashing::Downloader.stubs(:get_gist).with(123).returns(return_value).once
+
+ capture_io { @cli.install(123) }
+ end
+
+ def test_install_task_calls_create_file_for_each_valid_file_in_gist
+ json_response = <<-JSON
+ {
+ "files": {
+ "ruby_job.rb": { "content": "some job content" },
+ "num.html": { "content": "some html content" },
+ "num.scss": { "content": "some sass content" },
+ "num.coffee": { "content": "some coffee content" }
+ }
+ }
+ JSON
+
+ Dir.stubs(:pwd).returns('')
+
+ Dashing::Downloader.stubs(:get_gist).returns(JSON.parse(json_response))
+ @cli.stubs(:create_file).with('/jobs/ruby_job.rb', 'some job content', {:skip => false}).once
+ @cli.stubs(:create_file).with('/widgets/num/num.html', 'some html content', {:skip => false}).once
+ @cli.stubs(:create_file).with('/widgets/num/num.scss', 'some sass content', {:skip => false}).once
+ @cli.stubs(:create_file).with('/widgets/num/num.coffee', 'some coffee content', {:skip => false}).once
+
+ capture_io { @cli.install(123) }
+ end
+
+ def test_install_task_ignores_invalid_files
+ json_response = <<-JSON
+ {
+ "files": {
+ "ruby_job.js": { "content": "some job content" },
+ "num.css": { "content": "some sass content" }
+ }
+ }
+ JSON
+
+ Dashing::Downloader.stubs(:get_gist).returns(JSON.parse(json_response))
+ @cli.stubs(:create_file).never
+
+ capture_io { @cli.install(123) }
+ end
+
+ def test_install_task_warns_when_gist_not_found
+ error = OpenURI::HTTPError.new('error', mock())
+ Dashing::Downloader.stubs(:get_gist).raises(error)
+
+ output, _ = capture_io { @cli.install(123) }
+
+ assert_includes output, 'Could not find gist at '
+ end
+
+ def test_start_task_starts_thin_with_default_port
+ command = 'bundle exec thin -R config.ru start -p 3030 '
+ @cli.stubs(:run_command).with(command).once
+ @cli.start
+ end
+
+ def test_start_task_starts_thin_with_specified_port
+ command = 'bundle exec thin -R config.ru start -p 2020'
+ @cli.stubs(:run_command).with(command).once
+ @cli.start('-p', '2020')
+ end
+
+ def test_start_task_supports_job_path_option
+ commands = [
+ 'export JOB_PATH=other_spot; ',
+ 'bundle exec thin -R config.ru start -p 3030 '
+ ]
+
+ @cli.stubs(:options).returns(job_path: 'other_spot')
+ @cli.stubs(:run_command).with(commands.join('')).once
+ @cli.start
+ end
+
+ def test_stop_task_stops_thin_server
+ @cli.stubs(:run_command).with('bundle exec thin stop')
+ @cli.stop
+ end
+
+ def test_job_task_requires_job_file
+ Dir.stubs(:pwd).returns('')
+ @cli.stubs(:require_file).with('/jobs/special_job.rb').once
+
+ @cli.job('special_job')
+ end
+
+ def test_job_task_requires_every_ruby_file_in_lib
+ Dir.stubs(:pwd).returns('')
+ Dir.stubs(:[]).returns(['lib/dashing/cli.rb', 'lib/dashing.rb'])
+ @cli.stubs(:require_file).times(3)
+
+ @cli.job('special_job')
+ end
+
+ def test_job_sets_auth_token
+ @cli.class.stubs(:auth_token=).with('my_token').once
+ @cli.stubs(:require_file)
+
+ @cli.job('my_job', 'my_token')
+ end
+
+ def test_hyphenate_lowers_and_hyphenates_inputs
+ assertion_map = {
+ 'Power' => 'power',
+ 'POWER' => 'power',
+ 'PowerRangers' => 'power-rangers',
+ 'Power_ranger' => 'power-ranger',
+ 'SuperPowerRangers' => 'super-power-rangers'
+ }
+
+ assertion_map.each do |input, expected|
+ assert_equal expected, Dashing::CLI.hyphenate(input)
+ end
+ end
+
+end
diff --git a/test/downloader_test.rb b/test/downloader_test.rb
new file mode 100644
index 00000000..930ad56f
--- /dev/null
+++ b/test/downloader_test.rb
@@ -0,0 +1,26 @@
+require 'test_helper'
+
+class DownloaderTest < Minitest::Test
+
+ def test_get_json_requests_and_parses_content
+ endpoint = 'http://somehost.com/file.json'
+ response = '{ "name": "value" }'
+ FakeWeb.register_uri(:get, endpoint, body: response)
+ JSON.stubs(:parse).with(response).once
+
+ Dashing::Downloader.get_json(endpoint)
+ end
+
+ def test_get_json_raises_on_bad_request
+ FakeWeb.register_uri(:get, 'http://dead-host.com/', status: '404')
+
+ assert_raises(OpenURI::HTTPError) do
+ Dashing::Downloader.get_json('http://dead-host.com/')
+ end
+ end
+
+ def test_load_gist_attempts_to_get_the_gist
+ Dashing::Downloader.stubs(:get_json).once
+ Dashing::Downloader.get_gist(123)
+ end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644
index 00000000..0b719f52
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,49 @@
+require 'simplecov'
+SimpleCov.start do
+ add_filter "/vendor/"
+ add_filter "/test/"
+end
+
+require 'rack/test'
+require 'stringio'
+require 'tmpdir'
+require 'fakeweb'
+require 'minitest/autorun'
+require 'minitest/pride'
+require 'mocha/setup'
+
+require_relative '../lib/dashing'
+
+FakeWeb.allow_net_connect = false
+
+ENV['RACK_ENV'] = 'test'
+WORKING_DIRECTORY = Dir.pwd.freeze
+ARGV.clear
+
+def load_quietly(file)
+ Minitest::Test.new(nil).capture_io do
+ load file
+ end
+end
+
+def temp
+ path = File.expand_path "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/"
+ FileUtils.mkdir_p path
+ Dir.chdir path
+ yield path
+ensure
+ Dir.chdir WORKING_DIRECTORY
+ FileUtils.rm_rf(path) if File.exists?(path)
+end
+
+module Dashing
+ class Test < Minitest::Test
+ include Rack::Test::Methods
+
+ alias_method :silent, :capture_io
+
+ def teardown
+ FileUtils.rm_f('history.yml')
+ end
+ end
+end
![]()
+