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.
- data/LICENSE +13 -0
- data/README.md +3 -0
- data/config/locales/en.yml +26 -0
- data/config/locales/translations/zendesk_apps_support.yml +54 -0
- data/lib/zendesk_apps_support.rb +20 -0
- data/lib/zendesk_apps_support/app_file.rb +42 -0
- data/lib/zendesk_apps_support/app_version.rb +68 -0
- data/lib/zendesk_apps_support/default_styles.scss +21 -0
- data/lib/zendesk_apps_support/engine.rb +11 -0
- data/lib/zendesk_apps_support/i18n.rb +28 -0
- data/lib/zendesk_apps_support/package.rb +100 -0
- data/lib/zendesk_apps_support/sass_functions.rb +22 -0
- data/lib/zendesk_apps_support/src.js.erb +24 -0
- data/lib/zendesk_apps_support/stylesheet_compiler.rb +25 -0
- data/lib/zendesk_apps_support/validations/manifest.rb +48 -0
- data/lib/zendesk_apps_support/validations/source.rb +43 -0
- data/lib/zendesk_apps_support/validations/templates.rb +20 -0
- data/lib/zendesk_apps_support/validations/translations.rb +38 -0
- data/lib/zendesk_apps_support/validations/validation_error.rb +103 -0
- data/spec/app/app.css +6 -0
- data/spec/app/app.js +12 -0
- data/spec/app/assets/logo-small.png +0 -0
- data/spec/app/assets/logo.png +0 -0
- data/spec/app/manifest.json +11 -0
- data/spec/app/templates/layout.hdbs +11 -0
- data/spec/app/translations/en.json +5 -0
- data/spec/app_file_spec.rb +27 -0
- data/spec/app_version_spec.rb +61 -0
- data/spec/i18n_spec.rb +22 -0
- data/spec/package_spec.rb +84 -0
- data/spec/validations/jshint_error_spec.rb +17 -0
- data/spec/validations/manifest_spec.rb +39 -0
- data/spec/validations/source_spec.rb +21 -0
- data/spec/validations/templates_spec.rb +13 -0
- data/spec/validations/translations_spec.rb +58 -0
- data/spec/validations/validation_serialization_spec.rb +96 -0
- 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
|
data/spec/app/app.css
ADDED
data/spec/app/app.js
ADDED
Binary file
|
Binary file
|
@@ -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
|
data/spec/i18n_spec.rb
ADDED
@@ -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
|