izi_json_ld 1.0.0 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/app/entities/aggregate_rating_entity.rb +3 -1
  3. data/app/entities/application_entity.rb +54 -7
  4. data/app/entities/article_entity.rb +16 -0
  5. data/app/entities/breadcrumb_list_entity.rb +7 -0
  6. data/app/entities/image_object_entity.rb +6 -0
  7. data/app/entities/list_item_entity.rb +9 -0
  8. data/app/entities/offer_entity.rb +2 -1
  9. data/app/entities/organization_entity.rb +10 -0
  10. data/app/entities/person_entity.rb +14 -0
  11. data/app/entities/product_entity.rb +2 -2
  12. data/app/entities/rating_entity.rb +1 -1
  13. data/app/entities/review_entity.rb +3 -3
  14. data/app/entities/web_page_entity.rb +11 -0
  15. data/app/helpers/json_ld_helper.rb +7 -0
  16. data/lib/izi_json_ld/types.rb +3 -0
  17. data/lib/izi_json_ld/types/date_time.rb +11 -0
  18. data/lib/izi_json_ld/types/one_or_more.rb +13 -0
  19. data/lib/izi_json_ld/version.rb +1 -1
  20. data/spec/dummy/config/application.rb +5 -1
  21. data/spec/dummy/config/environments/test.rb +9 -7
  22. data/spec/dummy/db/development.sqlite3 +0 -0
  23. data/spec/dummy/db/test.sqlite3 +0 -0
  24. data/spec/dummy/log/development.log +16 -0
  25. data/spec/dummy/log/test.log +13419 -0
  26. data/spec/entities/aggregate_rating_entity_spec.rb +2 -1
  27. data/spec/entities/article_entity_spec.rb +113 -0
  28. data/spec/entities/breadcrumb_list_entity_spec.rb +55 -0
  29. data/spec/entities/product_entity_spec.rb +52 -0
  30. data/spec/rails_helper.rb +1 -17
  31. metadata +18 -24
  32. data/app/entities/service_entity.rb +0 -10
  33. data/app/helpers/critical_helper.rb +0 -118
  34. data/spec/dummy/Gemfile +0 -14
  35. data/spec/dummy/Gemfile.lock +0 -229
  36. data/spec/dummy/app/models/application_record.rb +0 -3
  37. data/spec/dummy/bin/_guard-core +0 -29
  38. data/spec/dummy/bin/guard +0 -29
  39. data/spec/dummy/bin/rails +0 -4
  40. data/spec/dummy/bin/rake +0 -4
  41. data/spec/dummy/bin/rspec +0 -29
  42. data/spec/dummy/bin/setup +0 -33
  43. data/spec/entities/service_entity_spec.rb +0 -62
@@ -23,7 +23,8 @@ RSpec.describe AggregateRatingEntity do
23
23
 
24
24
  [
25
25
  ['bestRating', 5],
26
- ['worstRating', 1]
26
+ ['worstRating', 1],
27
+ ['reviewCount', 1]
27
28
  ].each do |(field, value)|
28
29
  it "should have default #{field}" do
29
30
  expect(item.as_json).to have_key field
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe ArticleEntity do
4
+ let(:item) do
5
+ described_class.new(
6
+ headline: 'Test Article Title',
7
+ mainEntityOfPage: {
8
+ web_page: 'https://exampla.com/blog/page'
9
+ }
10
+ )
11
+ end
12
+
13
+ it 'should be searializable as hash' do
14
+ expect(item.as_json).to be_present
15
+ end
16
+
17
+ it 'should have @type' do
18
+ expect(item.as_json).to have_key '@type'
19
+ expect(item.as_json['@type']).to be_present
20
+ expect(item.as_json['@type']).to eq 'Article'
21
+ end
22
+
23
+ it 'should have @context at root entity' do
24
+ expect(item.as_json).to have_key '@context'
25
+ expect(item.as_json['@context']).to be_present
26
+ expect(item.as_json['@context']).to include 'schema.org'
27
+ end
28
+
29
+ it 'possible to set mainEntityOfPage as URL' do
30
+ custom = described_class.new(headline: 'Title', mainEntityOfPage: 'https://exampla.com/blog/page')
31
+ expect(custom.as_json.dig('mainEntityOfPage')).to be_present
32
+ expect(custom.as_json.dig('mainEntityOfPage')).to eq 'https://exampla.com/blog/page'
33
+ end
34
+
35
+ context 'filled Article' do
36
+ let(:item) do
37
+ described_class.new(
38
+ mainEntityOfPage: {
39
+ web_page: 'https://exampla.com/blog/page'
40
+ },
41
+ headline: 'Test Article Title',
42
+ description: 'Test Article Description',
43
+ image: 'https://exampla.com/blog/page.png',
44
+ author: { name: 'TestUser' },
45
+ publisher: {
46
+ name: 'TestInc',
47
+ logo: { url: 'https://exampla.com/logo.png' }
48
+ },
49
+ datePublished: 1.week.ago,
50
+ dateModified: 2.days.ago
51
+ )
52
+ end
53
+
54
+ it 'should not fail' do
55
+ expect do
56
+ item.dump
57
+ item.as_json
58
+ item.to_json
59
+ end.not_to raise_exception
60
+ end
61
+
62
+ [
63
+ ['Publisher name', %w[publisher name], 'TestInc'],
64
+ ['Main Entity id', %w[mainEntityOfPage @id], 'https://exampla.com/blog/page'],
65
+ ['Publisher type', %w[publisher @type], 'Organization'],
66
+ ['Publisher logo type', %w[publisher logo @type], 'ImageObject'],
67
+ ['Publisher logo', %w[publisher logo url], 'https://exampla.com/logo.png'],
68
+ ['Post image', %w[image], 'https://exampla.com/blog/page.png'],
69
+ ['Author as org', %w[author @type], 'Organization'],
70
+ ['Author name', %w[author name], 'TestUser']
71
+ ].each do |(desc, path, val)|
72
+ it "should render #{desc}[#{path.join('.')}]" do
73
+ expect(item.as_json.dig(*path)).to be_present
74
+ expect(item.as_json.dig(*path)).to eq val
75
+ end
76
+ end
77
+ end
78
+
79
+ context 'with author as a Person' do
80
+ let(:item) do
81
+ described_class.new(
82
+ mainEntityOfPage: {
83
+ web_page: 'https://exampla.com/blog/page'
84
+ },
85
+ headline: 'Test Article Title',
86
+ publisher: {
87
+ person_name: 'SiteOwner',
88
+ email: 'site.owner@example.com',
89
+ image: 'owner.png'
90
+ },
91
+ author: {
92
+ person_name: 'TestBlogger',
93
+ image: 'blogger.png'
94
+ }
95
+ )
96
+ end
97
+
98
+ [
99
+ ['Publisher as person', %w[publisher @type], 'Person'],
100
+ ['Publisher name', %w[publisher name], 'SiteOwner'],
101
+ ['Publisher email', %w[publisher email], 'site.owner@example.com'],
102
+ ['Publisher image', %w[publisher image], 'owner.png'],
103
+ ['Author as person', %w[author @type], 'Person'],
104
+ ['Author name', %w[author name], 'TestBlogger'],
105
+ ['Author image', %w[author image], 'blogger.png']
106
+ ].each do |(desc, path, val)|
107
+ it "should render #{desc}[#{path.join('.')}]" do
108
+ expect(item.as_json.dig(*path)).to be_present
109
+ expect(item.as_json.dig(*path)).to eq val
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe BreadcrumbListEntity do
4
+ let(:item) { described_class.new(itemListElement: []) }
5
+
6
+ it 'should be searializable as hash' do
7
+ expect(item.as_json).to be_present
8
+ end
9
+
10
+ it 'should have @type' do
11
+ expect(item.as_json).to have_key '@type'
12
+ expect(item.as_json['@type']).to be_present
13
+ expect(item.as_json['@type']).to eq 'BreadcrumbList'
14
+ end
15
+
16
+ it 'should have @context at root entity' do
17
+ expect(item.as_json).to have_key '@context'
18
+ expect(item.as_json['@context']).to be_present
19
+ expect(item.as_json['@context']).to include 'schema.org'
20
+ end
21
+
22
+ context 'with simple item' do
23
+ let(:item) do
24
+ described_class.new(itemListElement: { name: 'Home', position: 0, item: 'https://example.com' })
25
+ end
26
+
27
+ it 'should not fail' do
28
+ expect do
29
+ item.dump
30
+ item.as_json
31
+ item.to_json
32
+ end.not_to raise_exception
33
+ end
34
+ end
35
+
36
+ context 'with multiple items' do
37
+ let(:item) do
38
+ described_class.new(
39
+ itemListElement: [
40
+ { name: 'Home', position: 0, item: 'https://example.com' },
41
+ { name: 'Blog', position: 1, item: 'https://example.com/blog' },
42
+ { name: 'Post', position: 2, item: 'https://example.com/blog/post' },
43
+ ]
44
+ )
45
+ end
46
+
47
+ it 'should not fail' do
48
+ expect do
49
+ item.dump
50
+ item.as_json
51
+ item.to_json
52
+ end.not_to raise_exception
53
+ end
54
+ end
55
+ end
@@ -35,6 +35,12 @@ RSpec.describe ProductEntity do
35
35
  expect(item.as_json['aggregateRating']).to be_present
36
36
  expect(item.as_json.dig('aggregateRating', 'ratingValue')).to eq 4
37
37
  end
38
+
39
+ it 'should have aggregateRating @type' do
40
+ expect(item.as_json).to have_key 'aggregateRating'
41
+ expect(item.as_json['aggregateRating']).to be_present
42
+ expect(item.as_json.dig('aggregateRating', '@type')).to eq 'AggregateRating'
43
+ end
38
44
  end
39
45
 
40
46
  context 'with offers' do
@@ -45,5 +51,51 @@ RSpec.describe ProductEntity do
45
51
  expect(item.as_json['offers']).to be_present
46
52
  expect(item.as_json.dig('offers', 'url')).to eq 'test'
47
53
  end
54
+
55
+ it 'offers should have right @type' do
56
+ expect(item.as_json).to have_key 'offers'
57
+ expect(item.as_json['offers']).to be_present
58
+ expect(item.as_json.dig('offers', '@type')).to eq 'Offer'
59
+ end
60
+ end
61
+
62
+ context 'with review' do
63
+ let(:review) { { author: 'test', reviewRating: { ratingValue: 2 } } }
64
+ let(:reviews) do
65
+ [
66
+ { author: 'test', reviewRating: { ratingValue: 2 } },
67
+ { author: 'test2', reviewRating: { ratingValue: 3 } },
68
+ { author: 'test3', reviewRating: { ratingValue: 4 } }
69
+ ]
70
+ end
71
+
72
+ it 'should allow create product with single review' do
73
+ data = described_class.new(name: 'Test', review: review)
74
+ expect(data.to_json).to be_present
75
+ expect(data.as_json['review']).to be_present
76
+ expect(data.as_json['review'].class.name).to eq 'Hash'
77
+ end
78
+
79
+ it 'should allow create product with single review' do
80
+ data = described_class.new(name: 'Test', review: reviews)
81
+ expect(data.to_json).to be_present
82
+ expect(data.as_json['review']).to be_present
83
+ expect(data.as_json['review'].class.name).to eq 'Array'
84
+ end
85
+
86
+ it 'review & reviewRating should contain right @type' do
87
+ data = described_class.new(name: 'Test', review: reviews)
88
+ expect(data.as_json.dig('review', 0, '@type')).to eq 'Review'
89
+ expect(data.as_json.dig('review', 0, 'reviewRating', '@type')).to eq 'Rating'
90
+ end
91
+
92
+ it 'ensure proper serialization' do
93
+ data = described_class.new(name: 'Test', review: reviews)
94
+ raw = data.to_json
95
+ parsed = JSON.parse(raw)
96
+ expect(parsed).to be_present
97
+ expect(parsed.dig('review', 0, '@type')).to eq 'Review'
98
+ expect(parsed.dig('review', 0, 'reviewRating', '@type')).to eq 'Rating'
99
+ end
48
100
  end
49
101
  end
data/spec/rails_helper.rb CHANGED
@@ -22,25 +22,9 @@ require 'rspec/rails'
22
22
  #
23
23
  # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
24
24
 
25
- # Checks for pending migrations and applies them before tests are run.
26
- # If you are not using ActiveRecord, you can remove these lines.
27
- begin
28
- ActiveRecord::Migration.maintain_test_schema!
29
- rescue ActiveRecord::PendingMigrationError => e
30
- puts e.to_s.strip
31
- exit 1
32
- end
33
25
  RSpec.configure do |config|
34
- # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
35
- config.fixture_path = "#{::Rails.root}/spec/fixtures"
36
-
37
- # If you're not using ActiveRecord, or you'd prefer not to run each of your
38
- # examples within a transaction, remove the following line or assign false
39
- # instead of true.
40
- config.use_transactional_fixtures = true
41
-
42
26
  # You can uncomment this line to turn off ActiveRecord support entirely.
43
- # config.use_active_record = false
27
+ config.use_active_record = false
44
28
 
45
29
  # RSpec Rails can automatically mix in different behaviours to your tests
46
30
  # based on their file location, for example enabling you to call `get` and
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: izi_json_ld
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - IzikAJ
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-15 00:00:00.000000000 Z
11
+ date: 2021-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-struct
@@ -49,31 +49,30 @@ files:
49
49
  - README.rdoc
50
50
  - app/entities/aggregate_rating_entity.rb
51
51
  - app/entities/application_entity.rb
52
+ - app/entities/article_entity.rb
53
+ - app/entities/breadcrumb_list_entity.rb
54
+ - app/entities/image_object_entity.rb
55
+ - app/entities/list_item_entity.rb
52
56
  - app/entities/offer_entity.rb
57
+ - app/entities/organization_entity.rb
58
+ - app/entities/person_entity.rb
53
59
  - app/entities/product_entity.rb
54
60
  - app/entities/rating_entity.rb
55
61
  - app/entities/review_entity.rb
56
- - app/entities/service_entity.rb
57
- - app/helpers/critical_helper.rb
62
+ - app/entities/web_page_entity.rb
63
+ - app/helpers/json_ld_helper.rb
58
64
  - lib/izi_json_ld.rb
59
65
  - lib/izi_json_ld/engine.rb
60
66
  - lib/izi_json_ld/extentions/autoload_paths.rb
61
67
  - lib/izi_json_ld/types.rb
68
+ - lib/izi_json_ld/types/date_time.rb
69
+ - lib/izi_json_ld/types/one_or_more.rb
62
70
  - lib/izi_json_ld/version.rb
63
- - spec/dummy/Gemfile
64
- - spec/dummy/Gemfile.lock
65
71
  - spec/dummy/Rakefile
66
72
  - spec/dummy/app/assets/config/manifest.js
67
73
  - spec/dummy/app/controllers/application_controller.rb
68
74
  - spec/dummy/app/helpers/application_helper.rb
69
- - spec/dummy/app/models/application_record.rb
70
75
  - spec/dummy/app/views/layouts/application.html.erb
71
- - spec/dummy/bin/_guard-core
72
- - spec/dummy/bin/guard
73
- - spec/dummy/bin/rails
74
- - spec/dummy/bin/rake
75
- - spec/dummy/bin/rspec
76
- - spec/dummy/bin/setup
77
76
  - spec/dummy/config.ru
78
77
  - spec/dummy/config/application.rb
79
78
  - spec/dummy/config/boot.rb
@@ -89,16 +88,18 @@ files:
89
88
  - spec/dummy/config/initializers/wrap_parameters.rb
90
89
  - spec/dummy/config/puma.rb
91
90
  - spec/dummy/config/routes.rb
91
+ - spec/dummy/db/development.sqlite3
92
92
  - spec/dummy/db/test.sqlite3
93
93
  - spec/dummy/log/development.log
94
94
  - spec/dummy/log/test.log
95
95
  - spec/dummy/tmp/development_secret.txt
96
96
  - spec/entities/aggregate_rating_entity_spec.rb
97
+ - spec/entities/article_entity_spec.rb
98
+ - spec/entities/breadcrumb_list_entity_spec.rb
97
99
  - spec/entities/offer_entity_spec.rb
98
100
  - spec/entities/product_entity_spec.rb
99
101
  - spec/entities/rating_entity_spec.rb
100
102
  - spec/entities/review_entity_spec.rb
101
- - spec/entities/service_entity_spec.rb
102
103
  - spec/rails_helper.rb
103
104
  - spec/spec_helper.rb
104
105
  homepage: https://bitbucket.org/netfixllc/izi_json_ld
@@ -126,17 +127,10 @@ specification_version: 4
126
127
  summary: Izi Lightup
127
128
  test_files:
128
129
  - spec/spec_helper.rb
129
- - spec/dummy/app/models/application_record.rb
130
130
  - spec/dummy/app/controllers/application_controller.rb
131
131
  - spec/dummy/app/views/layouts/application.html.erb
132
132
  - spec/dummy/app/assets/config/manifest.js
133
133
  - spec/dummy/app/helpers/application_helper.rb
134
- - spec/dummy/bin/rake
135
- - spec/dummy/bin/setup
136
- - spec/dummy/bin/_guard-core
137
- - spec/dummy/bin/guard
138
- - spec/dummy/bin/rspec
139
- - spec/dummy/bin/rails
140
134
  - spec/dummy/config/routes.rb
141
135
  - spec/dummy/config/environments/production.rb
142
136
  - spec/dummy/config/environments/development.rb
@@ -154,15 +148,15 @@ test_files:
154
148
  - spec/dummy/config.ru
155
149
  - spec/dummy/Rakefile
156
150
  - spec/dummy/db/test.sqlite3
157
- - spec/dummy/Gemfile
151
+ - spec/dummy/db/development.sqlite3
158
152
  - spec/dummy/log/test.log
159
153
  - spec/dummy/log/development.log
160
- - spec/dummy/Gemfile.lock
161
154
  - spec/dummy/tmp/development_secret.txt
162
155
  - spec/rails_helper.rb
163
156
  - spec/entities/aggregate_rating_entity_spec.rb
164
- - spec/entities/service_entity_spec.rb
165
157
  - spec/entities/review_entity_spec.rb
158
+ - spec/entities/breadcrumb_list_entity_spec.rb
166
159
  - spec/entities/rating_entity_spec.rb
167
160
  - spec/entities/offer_entity_spec.rb
161
+ - spec/entities/article_entity_spec.rb
168
162
  - spec/entities/product_entity_spec.rb
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ServiceEntity < ApplicationEntity
4
- extra '@context', 'http://www.schema.org'
5
- extra '@type', 'Service'
6
-
7
- attribute :name, ::IziJsonLd::Types::String
8
- attribute? :review, ::ReviewEntity.optional
9
- attribute? :reviews, ::IziJsonLd::Types::Array.of(::ReviewEntity).optional
10
- end
@@ -1,118 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CriticalHelper
4
- def css_preload(names)
5
- return '' if names.blank?
6
- names = Array.wrap(names)
7
-
8
- link = stylesheet_link_tag(*names, rel: 'preload', as: 'style', onload: 'this.rel="stylesheet"')
9
- noscript = content_tag :noscript do
10
- stylesheet_link_tag(*names)
11
- end
12
-
13
- link + noscript
14
- end
15
-
16
- def inline_js(name)
17
- IziJsonLd::InlineAsset.inline_js(name).html_safe
18
- end
19
-
20
- def inline_css(name)
21
- raw = IziJsonLd::InlineAsset.inline_css(name).html_safe
22
- raw = "<!-- CRIT CSS: #{name} -->".html_safe + raw if Rails.env.development?
23
- raw
24
- end
25
-
26
- def critical_js
27
- inline_js('crit-utils/bundle.js').presence || '<!-- CRIT JS NOT FOUND! -->'.html_safe
28
- end
29
-
30
- def critical_css(params = {})
31
- scope = params.fetch(:scope, 'critical')
32
- name = find_scoped_css(scope)
33
- stylesheets = Array.wrap(params.fetch(:stylesheets, []))
34
- data = StringIO.new
35
-
36
- if name.blank?
37
- # insert stylesheets directly if not crit css
38
- data << '<!-- CRIT CSS NOT FOUND! -->'
39
- data << stylesheet_link_tag(*stylesheets) if stylesheets.present?
40
- else
41
- data << inline_css(name)
42
- data << css_preload(stylesheets) if stylesheets.present?
43
- data << inline_js('crit-utils/measure.js') if Rails.env.development?
44
- end
45
-
46
- data.string.html_safe
47
- end
48
-
49
- def smart_picture(object, *args, **xargs, &block)
50
- return '' if object.blank?
51
-
52
- IziJsonLd::SmartPicture.render(object, *args, **xargs, &block)
53
- end
54
-
55
- def debug_critical_css(scope_name = 'critical')
56
- {
57
- lookup: scoped_css_files(scope_name),
58
- found: find_scoped_css(scope_name)
59
- }.to_json.html_safe
60
- end
61
-
62
- private
63
-
64
- def scoped_css_files(scope_name)
65
- [
66
- "#{controller_path}_#{action_name}",
67
- controller_path,
68
- *scoped_namespace_file(scope_name),
69
- "#{controller_name}_#{action_name}",
70
- controller_name,
71
- scope_name
72
- ].compact.uniq.map { |l| File.join(scope_name, "#{l}.css") }
73
- end
74
-
75
- def scoped_namespace_file(scope_name)
76
- scopes = []
77
- return scopes if controller_path == controller_name
78
-
79
- parts = controller_path.gsub(/\/#{controller_name}$/, '').split('/').reject(&:blank?)
80
- parts.each { |p| scopes.unshift(File.join([scopes.first, p].compact)) }
81
-
82
- scopes.uniq
83
- end
84
-
85
- def find_scoped_css(scope_name)
86
- scoped_css_files(scope_name).detect { |n| asset_exist?(n) }
87
- end
88
-
89
- def fetch_items(object, fields)
90
- Array.wrap(fields).map do |name|
91
- object.respond_to?(name) ? object.public_send(name) : nil
92
- end.compact
93
- end
94
-
95
- def asset_exist?(name)
96
- return find_asset_source(name)&.present? if Rails.env.development?
97
-
98
- manifest.assets.key?(name)
99
- end
100
-
101
- def find_asset_source(name)
102
- return find_sources_fallback(name) if old_manifest?
103
-
104
- manifest.find_sources(name)&.first
105
- end
106
-
107
- def find_sources_fallback(asset_path)
108
- Rails.application.assets.find_asset(asset_path)
109
- end
110
-
111
- def old_manifest?
112
- !manifest.respond_to?(:find_sources)
113
- end
114
-
115
- def manifest
116
- @manifest ||= Rails.application.assets_manifest
117
- end
118
- end