zendesk_apps_support 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/LICENSE +13 -0
  2. data/README.md +3 -0
  3. data/config/locales/en.yml +26 -0
  4. data/config/locales/translations/zendesk_apps_support.yml +54 -0
  5. data/lib/zendesk_apps_support.rb +20 -0
  6. data/lib/zendesk_apps_support/app_file.rb +42 -0
  7. data/lib/zendesk_apps_support/app_version.rb +68 -0
  8. data/lib/zendesk_apps_support/default_styles.scss +21 -0
  9. data/lib/zendesk_apps_support/engine.rb +11 -0
  10. data/lib/zendesk_apps_support/i18n.rb +28 -0
  11. data/lib/zendesk_apps_support/package.rb +100 -0
  12. data/lib/zendesk_apps_support/sass_functions.rb +22 -0
  13. data/lib/zendesk_apps_support/src.js.erb +24 -0
  14. data/lib/zendesk_apps_support/stylesheet_compiler.rb +25 -0
  15. data/lib/zendesk_apps_support/validations/manifest.rb +48 -0
  16. data/lib/zendesk_apps_support/validations/source.rb +43 -0
  17. data/lib/zendesk_apps_support/validations/templates.rb +20 -0
  18. data/lib/zendesk_apps_support/validations/translations.rb +38 -0
  19. data/lib/zendesk_apps_support/validations/validation_error.rb +103 -0
  20. data/spec/app/app.css +6 -0
  21. data/spec/app/app.js +12 -0
  22. data/spec/app/assets/logo-small.png +0 -0
  23. data/spec/app/assets/logo.png +0 -0
  24. data/spec/app/manifest.json +11 -0
  25. data/spec/app/templates/layout.hdbs +11 -0
  26. data/spec/app/translations/en.json +5 -0
  27. data/spec/app_file_spec.rb +27 -0
  28. data/spec/app_version_spec.rb +61 -0
  29. data/spec/i18n_spec.rb +22 -0
  30. data/spec/package_spec.rb +84 -0
  31. data/spec/validations/jshint_error_spec.rb +17 -0
  32. data/spec/validations/manifest_spec.rb +39 -0
  33. data/spec/validations/source_spec.rb +21 -0
  34. data/spec/validations/templates_spec.rb +13 -0
  35. data/spec/validations/translations_spec.rb +58 -0
  36. data/spec/validations/validation_serialization_spec.rb +96 -0
  37. metadata +212 -0
@@ -0,0 +1,25 @@
1
+ require 'sass'
2
+
3
+ module ZendeskAppsSupport
4
+ class StylesheetCompiler
5
+
6
+ def initialize(source, app_id, url_prefix)
7
+ @source, @app_id, @url_prefix = source, app_id, url_prefix
8
+ end
9
+
10
+ def compile
11
+ Sass::Engine.new(wrapped_source, :syntax => :scss, :app_asset_url_builder => self).render
12
+ end
13
+
14
+ def app_asset_url(name)
15
+ "#{@url_prefix}/#{name}"
16
+ end
17
+
18
+ private
19
+
20
+ def wrapped_source
21
+ ".app-#{@app_id} {#{@source}}"
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ require 'multi_json'
2
+
3
+ module ZendeskAppsSupport
4
+ module Validations
5
+ module Manifest
6
+
7
+ REQUIRED_MANIFEST_FIELDS = %w( author defaultLocale location frameworkVersion).freeze
8
+
9
+ class <<self
10
+ def call(package)
11
+ manifest = package.files.find { |f| f.relative_path == 'manifest.json' }
12
+
13
+ return [ ValidationError.new(:missing_manifest) ] unless manifest
14
+
15
+ manifest = MultiJson.load(manifest.read)
16
+
17
+ [].tap do |errors|
18
+ errors << missing_keys_error(manifest)
19
+ errors << default_locale_error(manifest)
20
+ errors.compact!
21
+ end
22
+ rescue MultiJson::DecodeError => e
23
+ return [ ValidationError.new(:manifest_not_json, :errors => e) ]
24
+ end
25
+
26
+ private
27
+
28
+ def missing_keys_error(manifest)
29
+ missing = REQUIRED_MANIFEST_FIELDS.select do |key|
30
+ manifest[key].nil?
31
+ end
32
+
33
+ if missing.any?
34
+ ValidationError.new('manifest_keys.missing', :missing_keys => missing.join(', '), :count => missing.length)
35
+ end
36
+ end
37
+
38
+ def default_locale_error(manifest)
39
+ default_locale = manifest['defaultLocale']
40
+ if !default_locale.nil? && default_locale !~ /^[a-z]{2,3}$/
41
+ ValidationError.new(:invalid_default_locale, :defaultLocale => default_locale)
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,43 @@
1
+ require 'jshintrb'
2
+
3
+ module ZendeskAppsSupport
4
+ module Validations
5
+ module Source
6
+
7
+ LINTER_OPTIONS = {
8
+ # enforcing options:
9
+ :noarg => true,
10
+ :undef => true,
11
+
12
+ # relaxing options:
13
+ :eqnull => true,
14
+ :laxcomma => true,
15
+
16
+ # predefined globals:
17
+ :predef => %w(_ console services helpers alert JSON Base64 clearInterval clearTimeout setInterval setTimeout)
18
+ }.freeze
19
+
20
+ class <<self
21
+ def call(package)
22
+ source = package.files.find { |f| f.relative_path == 'app.js' }
23
+
24
+ return [ ValidationError.new(:missing_source) ] unless source
25
+
26
+ jshint_errors = linter.lint(source.read)
27
+ if jshint_errors.any?
28
+ [ JSHintValidationError.new(source.relative_path, jshint_errors) ]
29
+ else
30
+ []
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def linter
37
+ Jshintrb::Lint.new(LINTER_OPTIONS)
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ module ZendeskAppsSupport
2
+ module Validations
3
+ module Templates
4
+
5
+ class <<self
6
+ def call(package)
7
+ errors = []
8
+ package.template_files.each do |template|
9
+ contents = template.read
10
+ if contents =~ /<\s*style\b/
11
+ errors << ValidationError.new(:style_in_template, :template => template.relative_path)
12
+ end
13
+ end
14
+ errors
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,38 @@
1
+ require 'jshintrb'
2
+
3
+ module ZendeskAppsSupport
4
+ module Validations
5
+ module Translations
6
+ class << self
7
+
8
+ TRANSLATIONS_PATH = %r{^translations/(.*)\.json$}
9
+ VALID_LOCALE = /^[a-z]{2,3}$/
10
+
11
+ def call(package)
12
+ package.files.inject([]) do |errors, file|
13
+ if path_match = TRANSLATIONS_PATH.match(file.relative_path)
14
+ errors << locale_error(file, path_match[1]) << json_error(file)
15
+ end
16
+ errors
17
+ end.compact
18
+ end
19
+
20
+ private
21
+
22
+ def locale_error(file, locale)
23
+ return nil if VALID_LOCALE =~ locale
24
+ ValidationError.new('translation.invalid_locale', :file => file.relative_path)
25
+ end
26
+
27
+ def json_error(file)
28
+ json = MultiJson.load(file.read)
29
+ return nil if json.kind_of?(Hash)
30
+ ValidationError.new('translation.not_json_object', :file => file.relative_path)
31
+ rescue MultiJson::DecodeError => e
32
+ ValidationError.new('translation.not_json', :file => file.relative_path, :errors => e)
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,103 @@
1
+ require 'multi_json'
2
+
3
+ module ZendeskAppsSupport
4
+ module Validations
5
+
6
+ class ValidationError
7
+ KEY_PREFIX = 'txt.apps.admin.error.app_build.'.freeze
8
+
9
+ class DeserializationError < StandardError
10
+ def initialize(serialized)
11
+ super "Cannot deserialize ValidationError from #{serialized}"
12
+ end
13
+ end
14
+
15
+ class << self
16
+
17
+ # Turn a JSON string into a ValidationError.
18
+ def from_json(json)
19
+ hash = MultiJson.decode(json)
20
+ raise DeserializationError.new(json) unless hash.is_a?(Hash)
21
+ from_hash(hash)
22
+ rescue MultiJson::DecodeError, NameError
23
+ raise DeserializationError.new(json)
24
+ end
25
+
26
+ def from_hash(hash)
27
+ raise DeserializationError.new(hash) unless hash['class']
28
+ klass = constantize(hash['class'])
29
+ raise DeserializationError.new(hash) unless klass <= self
30
+ klass.vivify(hash)
31
+ end
32
+
33
+ # Turn a Hash into a ValidationError. Used within from_json.
34
+ def vivify(hash)
35
+ new(hash['key'], hash['data'])
36
+ end
37
+
38
+ private
39
+
40
+ def constantize(klass)
41
+ klass.to_s.split('::').inject(Object) { |klass, part| klass = klass.const_get(part) }
42
+ end
43
+ end
44
+
45
+ attr_reader :key, :data
46
+
47
+ def initialize(key, data = nil)
48
+ @key, @data = key, symbolize_keys(data || {})
49
+ end
50
+
51
+ def to_s
52
+ ZendeskAppsSupport::I18n.t("#{KEY_PREFIX}#{key}", data)
53
+ end
54
+
55
+ def to_json(*options)
56
+ MultiJson.encode(as_json)
57
+ end
58
+
59
+ def as_json(*options)
60
+ {
61
+ 'class' => self.class.to_s,
62
+ 'key' => key,
63
+ 'data' => data
64
+ }
65
+ end
66
+
67
+ private
68
+
69
+ def symbolize_keys(hash)
70
+ hash.inject({}) do |result, (key, value)|
71
+ result[key.to_sym] = value
72
+ result
73
+ end
74
+ end
75
+ end
76
+
77
+ class JSHintValidationError < ValidationError
78
+ attr_reader :filename, :jshint_errors
79
+
80
+ def self.vivify(hash)
81
+ new(hash['filename'], hash['jshint_errors'])
82
+ end
83
+
84
+ def initialize(filename, jshint_errors)
85
+ errors = jshint_errors.compact.map { |err| "\n L#{err['line']}: #{err['reason']}" }.join('')
86
+ @filename = filename, @jshint_errors = jshint_errors
87
+ super(:jshint, {
88
+ :file => filename,
89
+ :errors => errors,
90
+ :count => jshint_errors.length
91
+ })
92
+ end
93
+
94
+ def as_json(*options)
95
+ {
96
+ 'class' => self.class.to_s,
97
+ 'filename' => filename,
98
+ 'jshint_errors' => jshint_errors
99
+ }
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,6 @@
1
+ h1 {
2
+ color: red;
3
+ span {
4
+ color: green;
5
+ }
6
+ }
@@ -0,0 +1,12 @@
1
+ (function() {
2
+
3
+ return {
4
+ events: {
5
+ 'app.activated':'doSomething'
6
+ },
7
+
8
+ doSomething: function() {
9
+ }
10
+ };
11
+
12
+ }());
Binary file
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "ABC",
3
+ "author": {
4
+ "name": "John Smith",
5
+ "email": "john@example.com"
6
+ },
7
+ "defaultLocale": "en",
8
+ "private": true,
9
+ "location": "ticket_sidebar",
10
+ "frameworkVersion": "0.5"
11
+ }
@@ -0,0 +1,11 @@
1
+ <header>
2
+ <span class="logo"/>
3
+ <h3>{{setting "name"}}</h3>
4
+ </header>
5
+ <section data-main/>
6
+ <footer>
7
+ <a href="mailto:{{author.email}}">
8
+ {{author.name}}
9
+ </a>
10
+ </footer>
11
+ </div>
@@ -0,0 +1,5 @@
1
+ {
2
+ "app": {
3
+ "name": "Buddha Machine"
4
+ }
5
+ }
@@ -0,0 +1,27 @@
1
+ require 'zendesk_apps_support'
2
+
3
+ describe ZendeskAppsSupport::AppFile do
4
+
5
+ before do
6
+ package = mock('Package', :root => Pathname("spec/app/templates/"))
7
+ @file = ZendeskAppsSupport::AppFile.new(package, 'layout.hdbs')
8
+ end
9
+
10
+ describe '=~' do
11
+ it 'should test against the relative path of the file' do
12
+ @file.should =~ /layout/
13
+ end
14
+ end
15
+
16
+ describe 'read' do
17
+ it 'should read file content' do
18
+ @file.read.should =~ /<header>/
19
+ end
20
+ end
21
+
22
+ describe 'to_s' do
23
+ it 'should return file name' do
24
+ @file.to_s.should == 'layout.hdbs'
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,61 @@
1
+ require 'zendesk_apps_support'
2
+
3
+ describe ZendeskAppsSupport::AppVersion do
4
+
5
+ describe 'the current version' do
6
+ subject do
7
+ ZendeskAppsSupport::AppVersion.new(ZendeskAppsSupport::AppVersion::CURRENT)
8
+ end
9
+
10
+ it { should be_frozen }
11
+ it { should be_present }
12
+ it { should be_servable }
13
+ it { should be_valid_for_update }
14
+ it { should_not be_blank }
15
+ it { should_not be_deprecated }
16
+ it { should_not be_obsolete }
17
+ it { should == ZendeskAppsSupport::AppVersion.new(ZendeskAppsSupport::AppVersion::CURRENT) }
18
+ it { should_not == ZendeskAppsSupport::AppVersion.new('0.2') }
19
+
20
+ its(:to_s) { should == ZendeskAppsSupport::AppVersion::CURRENT }
21
+ its(:to_json) { should == ZendeskAppsSupport::AppVersion::CURRENT.to_json }
22
+ end
23
+
24
+ describe 'the deprecated version' do
25
+ subject do
26
+ ZendeskAppsSupport::AppVersion.new(ZendeskAppsSupport::AppVersion::DEPRECATED)
27
+ end
28
+
29
+ it { should be_frozen }
30
+ it { should be_present }
31
+ it { should be_servable }
32
+ it { should_not be_valid_for_update }
33
+ it { should_not be_blank }
34
+ it { should be_deprecated }
35
+ it { should_not be_obsolete }
36
+ it { should == ZendeskAppsSupport::AppVersion.new(ZendeskAppsSupport::AppVersion::DEPRECATED) }
37
+ it { should_not == ZendeskAppsSupport::AppVersion.new('0.2') }
38
+
39
+ its(:to_s) { should == ZendeskAppsSupport::AppVersion::DEPRECATED }
40
+ its(:to_json) { should == ZendeskAppsSupport::AppVersion::DEPRECATED.to_json }
41
+ end
42
+
43
+ describe 'a really old version' do
44
+ subject do
45
+ ZendeskAppsSupport::AppVersion.new('0.1')
46
+ end
47
+
48
+ it { should be_frozen }
49
+ it { should be_present }
50
+ it { should_not be_servable }
51
+ it { should_not be_valid_for_update }
52
+ it { should_not be_blank }
53
+ it { should_not be_deprecated }
54
+ it { should be_obsolete }
55
+ it { should == ZendeskAppsSupport::AppVersion.new('0.1') }
56
+ it { should_not == ZendeskAppsSupport::AppVersion.new('0.2') }
57
+
58
+ its(:to_s) { should == '0.1' }
59
+ its(:to_json) { should == '0.1'.to_json }
60
+ end
61
+ end
@@ -0,0 +1,22 @@
1
+ require 'zendesk_apps_support'
2
+ require 'pathname'
3
+
4
+ describe ZendeskAppsSupport::I18n do
5
+
6
+ it 'should translate error messages' do
7
+ key_prefix = ZendeskAppsSupport::Validations::ValidationError::KEY_PREFIX
8
+ ZendeskAppsSupport::I18n.t("#{key_prefix}.missing_manifest").should == 'Could not find manifest.json'
9
+ end
10
+
11
+ end
12
+
13
+ describe 'translations' do
14
+
15
+ it 'should be up-to-date' do
16
+ project_root = Pathname.new(File.expand_path('../../', __FILE__))
17
+ zendesk_version = project_root.join('config/locales/translations/zendesk_apps_support.yml')
18
+ standard_version = project_root.join('config/locales/en.yml')
19
+ File.mtime(standard_version).should be >= File.mtime(zendesk_version)
20
+ end
21
+
22
+ end