compony 0.11.11 → 0.11.13
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.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +145 -106
- data/README.md +12 -1
- data/Rakefile +10 -0
- data/TODO.md +2 -1
- data/VERSION +1 -1
- data/compony.gemspec +5 -3
- data/config/locales/es.yml +46 -0
- data/doc/ComponentGenerator.html +1 -1
- data/doc/Components.html +1 -1
- data/doc/ComponentsGenerator.html +1 -1
- data/doc/Compony/Component.html +1 -1
- data/doc/Compony/ComponentMixins/Default/Labelling.html +1 -1
- data/doc/Compony/ComponentMixins/Default/Standalone/ResourcefulVerbDsl.html +1 -1
- data/doc/Compony/ComponentMixins/Default/Standalone/StandaloneDsl.html +1 -1
- data/doc/Compony/ComponentMixins/Default/Standalone/VerbDsl.html +1 -1
- data/doc/Compony/ComponentMixins/Default/Standalone.html +1 -1
- data/doc/Compony/ComponentMixins/Default.html +1 -1
- data/doc/Compony/ComponentMixins/Resourceful.html +1 -1
- data/doc/Compony/ComponentMixins.html +1 -1
- data/doc/Compony/Components/Buttons/CssButton.html +1 -1
- data/doc/Compony/Components/Buttons/Link.html +1 -1
- data/doc/Compony/Components/Buttons.html +1 -1
- data/doc/Compony/Components/Destroy.html +1 -1
- data/doc/Compony/Components/Edit.html +1 -1
- data/doc/Compony/Components/Form.html +1 -1
- data/doc/Compony/Components/Index.html +1 -1
- data/doc/Compony/Components/List.html +1 -1
- data/doc/Compony/Components/New.html +1 -1
- data/doc/Compony/Components/Show.html +1 -1
- data/doc/Compony/Components/WithForm.html +1 -1
- data/doc/Compony/Components.html +1 -1
- data/doc/Compony/ControllerMixin.html +1 -1
- data/doc/Compony/Engine.html +1 -1
- data/doc/Compony/Intent.html +1 -1
- data/doc/Compony/ManageIntentsDsl.html +2 -2
- data/doc/Compony/MethodAccessibleHash.html +1 -1
- data/doc/Compony/ModelFields/Anchormodel.html +1 -1
- data/doc/Compony/ModelFields/Association.html +1 -1
- data/doc/Compony/ModelFields/Attachment.html +1 -1
- data/doc/Compony/ModelFields/Base.html +8 -2
- data/doc/Compony/ModelFields/Boolean.html +1 -1
- data/doc/Compony/ModelFields/Color.html +1 -1
- data/doc/Compony/ModelFields/Currency.html +1 -1
- data/doc/Compony/ModelFields/Date.html +1 -1
- data/doc/Compony/ModelFields/Datetime.html +1 -1
- data/doc/Compony/ModelFields/Decimal.html +1 -1
- data/doc/Compony/ModelFields/Email.html +1 -1
- data/doc/Compony/ModelFields/Float.html +1 -1
- data/doc/Compony/ModelFields/Integer.html +1 -1
- data/doc/Compony/ModelFields/Percentage.html +1 -1
- data/doc/Compony/ModelFields/Phone.html +1 -1
- data/doc/Compony/ModelFields/RichText.html +1 -1
- data/doc/Compony/ModelFields/String.html +1 -1
- data/doc/Compony/ModelFields/Text.html +1 -1
- data/doc/Compony/ModelFields/Time.html +1 -1
- data/doc/Compony/ModelFields/Url.html +1 -1
- data/doc/Compony/ModelFields.html +1 -1
- data/doc/Compony/ModelMixin.html +1 -1
- data/doc/Compony/NaturalOrdering.html +1 -1
- data/doc/Compony/RequestContext.html +1 -1
- data/doc/Compony/Version.html +1 -1
- data/doc/Compony/ViewHelpers.html +1 -1
- data/doc/Compony/VirtualModel.html +1 -1
- data/doc/Compony.html +1 -1
- data/doc/ComponyController.html +1 -1
- data/doc/_index.html +1 -1
- data/doc/file.CHANGELOG.html +88 -76
- data/doc/file.README.html +21 -11
- data/doc/file.basic_component.html +14 -14
- data/doc/file.cookbook.html +27 -27
- data/doc/file.destroy.html +2 -2
- data/doc/file.dsl_reference.html +199 -199
- data/doc/file.edit.html +3 -3
- data/doc/file.example.html +13 -13
- data/doc/file.example_advanced.html +10 -10
- data/doc/file.feasibility.html +2 -2
- data/doc/file.form.html +7 -7
- data/doc/file.generators.html +3 -3
- data/doc/file.glossary.html +44 -44
- data/doc/file.gotchas.html +35 -35
- data/doc/file.index.html +10 -10
- data/doc/file.inheritance.html +5 -5
- data/doc/file.installation.html +9 -9
- data/doc/file.integrations.html +46 -46
- data/doc/file.intents.html +11 -11
- data/doc/file.internal_datastructures.html +6 -6
- data/doc/file.list.html +35 -35
- data/doc/file.maintaining.html +21 -21
- data/doc/file.model_fields.html +3 -3
- data/doc/file.nesting.html +5 -5
- data/doc/file.new.html +3 -3
- data/doc/file.ownership.html +2 -2
- data/doc/file.patterns.html +50 -50
- data/doc/file.pre_built_components.html +2 -2
- data/doc/file.resourceful.html +5 -5
- data/doc/file.show.html +17 -17
- data/doc/file.standalone.html +12 -12
- data/doc/file.virtual_models.html +2 -2
- data/doc/file.with_form.html +23 -23
- data/doc/index.html +21 -11
- data/doc/top-level-namespace.html +1 -1
- data/lib/compony/model_fields/base.rb +3 -0
- data/spec/README.md +22 -0
- data/spec/compony/component_spec.rb +80 -0
- data/spec/compony/compony_spec.rb +102 -0
- data/spec/compony/intent_spec.rb +94 -0
- data/spec/compony/method_accessible_hash_spec.rb +32 -0
- data/spec/compony/model_fields_spec.rb +70 -0
- data/spec/compony/model_mixin_spec.rb +81 -0
- data/spec/compony/natural_ordering_spec.rb +61 -0
- data/spec/dummy/app/components/statics/home.rb +17 -0
- data/spec/dummy/app/components/statics/secret.rb +15 -0
- data/spec/dummy/app/components/users/destroy.rb +2 -0
- data/spec/dummy/app/components/users/edit.rb +2 -0
- data/spec/dummy/app/components/users/form.rb +14 -0
- data/spec/dummy/app/components/users/index.rb +29 -0
- data/spec/dummy/app/components/users/new.rb +2 -0
- data/spec/dummy/app/components/users/show.rb +26 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/models/ability.rb +7 -0
- data/spec/dummy/app/models/application_record.rb +4 -0
- data/spec/dummy/app/models/user.rb +22 -0
- data/spec/dummy/app/views/layouts/application.html.erb +7 -0
- data/spec/dummy/config/application.rb +20 -0
- data/spec/dummy/config/database.yml +3 -0
- data/spec/dummy/config/environment.rb +3 -0
- data/spec/dummy/config/initializers/simple_form.rb +9 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/schema.rb +14 -0
- data/spec/rails_helper.rb +23 -0
- data/spec/requests/authorization_spec.rb +16 -0
- data/spec/requests/plain_component_spec.rb +9 -0
- data/spec/requests/routing_spec.rb +20 -0
- data/spec/requests/users_crud_spec.rb +99 -0
- metadata +63 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
RSpec.describe Compony::ModelMixin do
|
|
2
|
+
describe '.field' do
|
|
3
|
+
it 'registers fields with the correct types' do
|
|
4
|
+
expect(User.fields[:name]).to be_a(Compony::ModelFields::String)
|
|
5
|
+
expect(User.fields[:age]).to be_a(Compony::ModelFields::Integer)
|
|
6
|
+
expect(User.fields[:comment]).to be_a(Compony::ModelFields::Text)
|
|
7
|
+
expect(User.fields[:email]).to be_a(Compony::ModelFields::Email)
|
|
8
|
+
expect(User.fields[:admin]).to be_a(Compony::ModelFields::Boolean)
|
|
9
|
+
expect(User.fields[:born_on]).to be_a(Compony::ModelFields::Date)
|
|
10
|
+
expect(User.fields[:created_at]).to be_a(Compony::ModelFields::Datetime)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'keeps the field name and model class' do
|
|
14
|
+
field = User.fields[:name]
|
|
15
|
+
expect(field.name).to eq(:name)
|
|
16
|
+
expect(field.model_class).to eq(User)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'rebinds fields to the subclass on inheritance' do
|
|
20
|
+
subclass = Class.new(User)
|
|
21
|
+
expect(subclass.fields[:name].model_class).to eq(subclass)
|
|
22
|
+
expect(User.fields[:name].model_class).to eq(User) # parent untouched
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'does not leak fields defined on a subclass into the parent' do
|
|
26
|
+
subclass = Class.new(User) do
|
|
27
|
+
def self.name = 'SpecialUser'
|
|
28
|
+
field :extra, :string
|
|
29
|
+
end
|
|
30
|
+
expect(subclass.fields).to have_key(:extra)
|
|
31
|
+
expect(User.fields).not_to have_key(:extra)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe 'feasibility' do
|
|
36
|
+
it 'is feasible when no prevention triggers' do
|
|
37
|
+
user = User.create!(name: 'Alice', admin: false)
|
|
38
|
+
expect(user.feasible?(:destroy)).to be(true)
|
|
39
|
+
expect(user.feasibility_messages(:destroy)).to eq([])
|
|
40
|
+
expect(user.full_feasibility_messages(:destroy)).to eq('')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'is infeasible when a prevention triggers' do
|
|
44
|
+
admin = User.create!(name: 'Root', admin: true)
|
|
45
|
+
expect(admin.feasible?(:destroy)).to be(false)
|
|
46
|
+
expect(admin.feasibility_messages(:destroy)).to eq(['Cannot destroy admins'])
|
|
47
|
+
expect(admin.full_feasibility_messages(:destroy)).to eq('Cannot destroy admins.')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'caches the result until recompute is requested' do
|
|
51
|
+
user = User.create!(name: 'Alice', admin: false)
|
|
52
|
+
expect(user.feasible?(:destroy)).to be(true)
|
|
53
|
+
user.admin = true
|
|
54
|
+
expect(user.feasible?(:destroy)).to be(true) # cached
|
|
55
|
+
expect(user.feasible?(:destroy, recompute: true)).to be(false)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'returns true for unknown actions' do
|
|
59
|
+
user = User.create!(name: 'Alice')
|
|
60
|
+
expect(user.feasible?(:frobnicate)).to be(true)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe '.owned_by' do
|
|
65
|
+
it 'stores the owner attribute' do
|
|
66
|
+
klass = Class.new(User) do
|
|
67
|
+
def self.name = 'OwnedUser'
|
|
68
|
+
owned_by :parent
|
|
69
|
+
end
|
|
70
|
+
expect(klass.owner_model_attr).to eq(:parent)
|
|
71
|
+
expect(User.owner_model_attr).to be_nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe '#field' do
|
|
76
|
+
it 'returns the formatted value for a field' do
|
|
77
|
+
user = User.create!(name: 'Alice', age: 30)
|
|
78
|
+
expect(user.field(:name, nil)).to eq('Alice')
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
RSpec.describe Compony::NaturalOrdering do
|
|
2
|
+
subject(:collection) { described_class.new }
|
|
3
|
+
|
|
4
|
+
it 'appends elements in order' do
|
|
5
|
+
collection.natural_push(:a, 1)
|
|
6
|
+
collection.natural_push(:b, 2)
|
|
7
|
+
expect(collection.map(&:name)).to eq(%i[a b])
|
|
8
|
+
expect(collection.map(&:payload)).to eq([1, 2])
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'inserts before a given element' do
|
|
12
|
+
collection.natural_push(:a, 1)
|
|
13
|
+
collection.natural_push(:c, 3)
|
|
14
|
+
collection.natural_push(:b, 2, before: :c)
|
|
15
|
+
expect(collection.map(&:name)).to eq(%i[a b c])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'overwrites an existing element in place' do
|
|
19
|
+
collection.natural_push(:a, 1)
|
|
20
|
+
collection.natural_push(:b, 2)
|
|
21
|
+
collection.natural_push(:a, 99)
|
|
22
|
+
expect(collection.map(&:name)).to eq(%i[a b])
|
|
23
|
+
expect(collection.first.payload).to eq(99)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'keeps old kwargs when overwriting without specifying them' do
|
|
27
|
+
collection.natural_push(:a, 1, hidden: true)
|
|
28
|
+
collection.natural_push(:a, 2)
|
|
29
|
+
expect(collection.first.hidden).to be(true)
|
|
30
|
+
expect(collection.first.payload).to eq(2)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'allows omitting the payload when overwriting' do
|
|
34
|
+
collection.natural_push(:a, 1)
|
|
35
|
+
collection.natural_push(:a, hidden: true)
|
|
36
|
+
expect(collection.first.payload).to eq(1)
|
|
37
|
+
expect(collection.first.hidden).to be(true)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'moves an existing element when overwriting with before:' do
|
|
41
|
+
collection.natural_push(:a, 1)
|
|
42
|
+
collection.natural_push(:b, 2)
|
|
43
|
+
collection.natural_push(:c, 3)
|
|
44
|
+
collection.natural_push(:c, 3, before: :a)
|
|
45
|
+
expect(collection.map(&:name)).to eq(%i[c a b])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'fails when inserting a new element without payload' do
|
|
49
|
+
expect { collection.natural_push(:a) }.to raise_error(RuntimeError, /without a payload/)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'fails when before: refers to a missing element' do
|
|
53
|
+
collection.natural_push(:a, 1)
|
|
54
|
+
expect { collection.natural_push(:b, 2, before: :nope) }.to raise_error(RuntimeError, /not found/)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'stores extra kwargs on the element' do
|
|
58
|
+
collection.natural_push(:a, 1, hidden: true)
|
|
59
|
+
expect(collection.select(&:hidden).map(&:name)).to eq([:a])
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class Components::Statics::Home < Compony::Component
|
|
2
|
+
setup do
|
|
3
|
+
label(:all) { 'Home' }
|
|
4
|
+
|
|
5
|
+
standalone path: 'home' do
|
|
6
|
+
skip_authentication!
|
|
7
|
+
verb :get do
|
|
8
|
+
authorize { true }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
content do
|
|
13
|
+
h1 'Welcome home'
|
|
14
|
+
para 'This is a plain component.'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class Components::Users::Form < Compony::Components::Form
|
|
2
|
+
setup do
|
|
3
|
+
form_fields do
|
|
4
|
+
concat field(:name)
|
|
5
|
+
concat field(:age)
|
|
6
|
+
concat field(:comment)
|
|
7
|
+
concat field(:email)
|
|
8
|
+
concat field(:admin)
|
|
9
|
+
concat field(:born_on)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
schema_fields :name, :age, :comment, :email, :admin, :born_on
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class Components::Users::Index < Compony::Component
|
|
2
|
+
include Compony::ComponentMixins::Resourceful
|
|
3
|
+
|
|
4
|
+
setup do
|
|
5
|
+
label(:all) { 'Users' }
|
|
6
|
+
standalone path: 'users' do
|
|
7
|
+
verb :get do
|
|
8
|
+
authorize { true }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
load_data { @data = User.order(:id) }
|
|
13
|
+
|
|
14
|
+
content do
|
|
15
|
+
h4 'Users:'
|
|
16
|
+
concat render_intent(:new, :users)
|
|
17
|
+
div class: 'users' do
|
|
18
|
+
@data.each do |user|
|
|
19
|
+
div class: 'user' do
|
|
20
|
+
span user.name
|
|
21
|
+
concat render_intent(:show, user, button: { label: { format: :short } })
|
|
22
|
+
concat render_intent(:edit, user, button: { label: { format: :short } })
|
|
23
|
+
concat render_intent(:destroy, user, button: { label: { format: :short } })
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class Components::Users::Show < Compony::Component
|
|
2
|
+
include Compony::ComponentMixins::Resourceful
|
|
3
|
+
|
|
4
|
+
setup do
|
|
5
|
+
label(:short) { |_u| 'Show' }
|
|
6
|
+
label(:long) { |u| "Show user #{u.label}" }
|
|
7
|
+
|
|
8
|
+
standalone path: 'users/show/:id' do
|
|
9
|
+
verb :get do
|
|
10
|
+
authorize { true }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
content do
|
|
15
|
+
h3 @data.label
|
|
16
|
+
table do
|
|
17
|
+
tr do
|
|
18
|
+
@data.fields.each_value { |field| th field.label }
|
|
19
|
+
end
|
|
20
|
+
tr do
|
|
21
|
+
@data.fields.each_value { |field| td field.value_for(@data, controller:) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class User < ApplicationRecord
|
|
2
|
+
field :name, :string
|
|
3
|
+
field :age, :integer
|
|
4
|
+
field :comment, :text
|
|
5
|
+
field :email, :email
|
|
6
|
+
field :admin, :boolean
|
|
7
|
+
field :born_on, :date
|
|
8
|
+
field :margin_relative, :percentage
|
|
9
|
+
field :salary, :currency
|
|
10
|
+
field :homepage, :url
|
|
11
|
+
field :created_at, :datetime
|
|
12
|
+
|
|
13
|
+
validates :name, presence: true
|
|
14
|
+
|
|
15
|
+
prevent :destroy, 'Cannot destroy admins' do
|
|
16
|
+
admin?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def label
|
|
20
|
+
name
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'rails'
|
|
2
|
+
require 'active_record/railtie'
|
|
3
|
+
require 'action_controller/railtie'
|
|
4
|
+
require 'action_view/railtie'
|
|
5
|
+
|
|
6
|
+
require 'compony'
|
|
7
|
+
|
|
8
|
+
module Dummy
|
|
9
|
+
class Application < Rails::Application
|
|
10
|
+
config.root = File.expand_path('..', __dir__)
|
|
11
|
+
config.load_defaults Rails::VERSION::STRING.to_f
|
|
12
|
+
config.eager_load = false
|
|
13
|
+
config.hosts.clear
|
|
14
|
+
config.secret_key_base = 'dummy-secret-key-base-for-tests-only'
|
|
15
|
+
config.logger = Logger.new(nil)
|
|
16
|
+
config.active_support.deprecation = :stderr
|
|
17
|
+
config.action_dispatch.show_exceptions = :none
|
|
18
|
+
config.action_controller.allow_forgery_protection = false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ActiveRecord::Schema.define do
|
|
2
|
+
create_table :users, force: true do |t|
|
|
3
|
+
t.string :name
|
|
4
|
+
t.integer :age
|
|
5
|
+
t.text :comment
|
|
6
|
+
t.string :email
|
|
7
|
+
t.boolean :admin, default: false, null: false
|
|
8
|
+
t.date :born_on
|
|
9
|
+
t.decimal :margin_relative
|
|
10
|
+
t.decimal :salary
|
|
11
|
+
t.string :homepage
|
|
12
|
+
t.timestamps
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
ENV['RAILS_ENV'] = 'test'
|
|
2
|
+
|
|
3
|
+
require_relative 'dummy/config/environment'
|
|
4
|
+
require 'rspec/rails'
|
|
5
|
+
|
|
6
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
|
7
|
+
load File.expand_path('dummy/db/schema.rb', __dir__)
|
|
8
|
+
|
|
9
|
+
RSpec.configure do |config|
|
|
10
|
+
config.use_transactional_fixtures = true
|
|
11
|
+
config.infer_spec_type_from_file_location!
|
|
12
|
+
|
|
13
|
+
config.expect_with :rspec do |expectations|
|
|
14
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config.mock_with :rspec do |mocks|
|
|
18
|
+
mocks.verify_partial_doubles = true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
config.order = :random
|
|
22
|
+
Kernel.srand config.seed
|
|
23
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
RSpec.describe 'Authorization', type: :request do
|
|
2
|
+
it 'denies access when the authorize block returns false' do
|
|
3
|
+
expect { get '/secret' }.to raise_error(CanCan::AccessDenied)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
it 'grants access when the authorize block returns true' do
|
|
7
|
+
get '/home'
|
|
8
|
+
expect(response).to have_http_status(:ok)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'hides buttons pointing to unauthorized components' do
|
|
12
|
+
comp = Components::Statics::Secret.new
|
|
13
|
+
controller = ApplicationController.new
|
|
14
|
+
expect(comp.standalone_access_permitted_for?(controller, verb: :get)).to be(false)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
RSpec.describe 'Plain standalone component', type: :request do
|
|
2
|
+
it 'renders content within the application layout' do
|
|
3
|
+
get '/home'
|
|
4
|
+
expect(response).to have_http_status(:ok)
|
|
5
|
+
expect(response.body).to include('data-layout="application"')
|
|
6
|
+
expect(response.body).to include('<h1>Welcome home</h1>')
|
|
7
|
+
expect(response.body).to include('<p>This is a plain component.</p>')
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
RSpec.describe 'Compony routing', type: :request do
|
|
2
|
+
it 'generates a route per standalone component' do
|
|
3
|
+
routes = Rails.application.routes.routes.map { |r| r.path.spec.to_s }
|
|
4
|
+
expect(routes).to include('/users(.:format)')
|
|
5
|
+
expect(routes).to include('/users/show/:id(.:format)')
|
|
6
|
+
expect(routes).to include('/home(.:format)')
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'creates named path helpers' do
|
|
10
|
+
expect(Rails.application.routes.url_helpers.index_users_comp_path).to eq('/users')
|
|
11
|
+
rescue NoMethodError
|
|
12
|
+
# Fallback: assert helper name from standalone config
|
|
13
|
+
helper = Components::Users::Index.new.standalone_configs.values.first.path_helper_name
|
|
14
|
+
expect(Rails.application.routes.url_helpers.send(:"#{helper}_path")).to eq('/users')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'routes only the configured verbs' do
|
|
18
|
+
expect { post '/users/show/1' }.to raise_error(ActionController::RoutingError)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
RSpec.describe 'Users CRUD via pre-built components', type: :request do
|
|
2
|
+
describe 'index' do
|
|
3
|
+
it 'lists users with buttons' do
|
|
4
|
+
alice = User.create!(name: 'Alice')
|
|
5
|
+
get '/users'
|
|
6
|
+
expect(response).to have_http_status(:ok)
|
|
7
|
+
expect(response.body).to include('Alice')
|
|
8
|
+
expect(response.body).to include("/users/show/#{alice.id}")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'disables the destroy button for admins (feasibility)' do
|
|
12
|
+
User.create!(name: 'Root', admin: true)
|
|
13
|
+
get '/users'
|
|
14
|
+
expect(response.body).to include('Cannot destroy admins.')
|
|
15
|
+
expect(response.body).to include('disabled')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe 'show' do
|
|
20
|
+
it 'renders the fields of the user' do
|
|
21
|
+
user = User.create!(name: 'Alice', age: 30, comment: 'Hi there')
|
|
22
|
+
get "/users/show/#{user.id}"
|
|
23
|
+
expect(response).to have_http_status(:ok)
|
|
24
|
+
expect(response.body).to include('<h3>Alice</h3>')
|
|
25
|
+
expect(response.body).to include('Hi there')
|
|
26
|
+
expect(response.body).to include('30')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'raises RecordNotFound for a missing id' do
|
|
30
|
+
expect { get '/users/show/999999' }.to raise_error(ActiveRecord::RecordNotFound)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe 'new/create' do
|
|
35
|
+
it 'renders the form' do
|
|
36
|
+
get '/users/new'
|
|
37
|
+
expect(response).to have_http_status(:ok)
|
|
38
|
+
expect(response.body).to include('form')
|
|
39
|
+
expect(response.body).to include('user[name]')
|
|
40
|
+
expect(response.body).to include('user[age]')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'creates a user and redirects' do
|
|
44
|
+
expect do
|
|
45
|
+
post '/users/new', params: { user: { name: 'Bob', age: '25', comment: 'New guy', email: 'bob@example.com', admin: '0', born_on: '2000-01-01' } }
|
|
46
|
+
end.to change(User, :count).by(1)
|
|
47
|
+
user = User.order(:id).last
|
|
48
|
+
expect(user.name).to eq('Bob')
|
|
49
|
+
expect(user.age).to eq(25)
|
|
50
|
+
expect(user.born_on).to eq(Date.new(2000, 1, 1))
|
|
51
|
+
expect(response).to have_http_status(:found)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 're-renders the form on validation errors' do
|
|
55
|
+
expect do
|
|
56
|
+
post '/users/new', params: { user: { name: '' } }
|
|
57
|
+
end.not_to change(User, :count)
|
|
58
|
+
expect(response.body).to include('form')
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe 'edit/update' do
|
|
63
|
+
let(:user) { User.create!(name: 'Alice', age: 30) }
|
|
64
|
+
|
|
65
|
+
it 'renders the form with existing values' do
|
|
66
|
+
get "/users/#{user.id}/edit"
|
|
67
|
+
expect(response).to have_http_status(:ok)
|
|
68
|
+
expect(response.body).to include('Alice')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'updates the user and redirects' do
|
|
72
|
+
patch "/users/#{user.id}/edit", params: { user: { name: 'Alicia' } }
|
|
73
|
+
expect(user.reload.name).to eq('Alicia')
|
|
74
|
+
expect(response).to have_http_status(:found)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'does not update on validation errors' do
|
|
78
|
+
patch "/users/#{user.id}/edit", params: { user: { name: '' } }
|
|
79
|
+
expect(user.reload.name).to eq('Alice')
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe 'destroy' do
|
|
84
|
+
it 'asks for confirmation on GET' do
|
|
85
|
+
user = User.create!(name: 'Alice')
|
|
86
|
+
get "/users/#{user.id}/destroy"
|
|
87
|
+
expect(response).to have_http_status(:ok)
|
|
88
|
+
expect(User.exists?(user.id)).to be(true)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'destroys on DELETE and redirects' do
|
|
92
|
+
user = User.create!(name: 'Alice')
|
|
93
|
+
expect do
|
|
94
|
+
delete "/users/#{user.id}/destroy"
|
|
95
|
+
end.to change(User, :count).by(-1)
|
|
96
|
+
expect(response).to have_http_status(:see_other) # 303 forces a GET, required for Turbo
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: compony
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.11.
|
|
4
|
+
version: 0.11.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sandro Kalbermatter
|
|
@@ -52,6 +52,34 @@ dependencies:
|
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: 2.18.0
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec-rails
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '6.1'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '6.1'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: sqlite3
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.7'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.7'
|
|
55
83
|
- !ruby/object:Gem::Dependency
|
|
56
84
|
name: rails
|
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -169,6 +197,7 @@ extensions: []
|
|
|
169
197
|
extra_rdoc_files: []
|
|
170
198
|
files:
|
|
171
199
|
- ".gitignore"
|
|
200
|
+
- ".rspec"
|
|
172
201
|
- ".ruby-version"
|
|
173
202
|
- ".yardopts"
|
|
174
203
|
- CHANGELOG.md
|
|
@@ -184,6 +213,7 @@ files:
|
|
|
184
213
|
- compony.gemspec
|
|
185
214
|
- config/locales/de.yml
|
|
186
215
|
- config/locales/en.yml
|
|
216
|
+
- config/locales/es.yml
|
|
187
217
|
- config/locales/fr.yml
|
|
188
218
|
- config/routes.rb
|
|
189
219
|
- doc/ComponentGenerator.html
|
|
@@ -395,6 +425,38 @@ files:
|
|
|
395
425
|
- lib/generators/components/USAGE
|
|
396
426
|
- lib/generators/components/components_generator.rb
|
|
397
427
|
- logo.svg
|
|
428
|
+
- spec/README.md
|
|
429
|
+
- spec/compony/component_spec.rb
|
|
430
|
+
- spec/compony/compony_spec.rb
|
|
431
|
+
- spec/compony/intent_spec.rb
|
|
432
|
+
- spec/compony/method_accessible_hash_spec.rb
|
|
433
|
+
- spec/compony/model_fields_spec.rb
|
|
434
|
+
- spec/compony/model_mixin_spec.rb
|
|
435
|
+
- spec/compony/natural_ordering_spec.rb
|
|
436
|
+
- spec/dummy/app/components/statics/home.rb
|
|
437
|
+
- spec/dummy/app/components/statics/secret.rb
|
|
438
|
+
- spec/dummy/app/components/users/destroy.rb
|
|
439
|
+
- spec/dummy/app/components/users/edit.rb
|
|
440
|
+
- spec/dummy/app/components/users/form.rb
|
|
441
|
+
- spec/dummy/app/components/users/index.rb
|
|
442
|
+
- spec/dummy/app/components/users/new.rb
|
|
443
|
+
- spec/dummy/app/components/users/show.rb
|
|
444
|
+
- spec/dummy/app/controllers/application_controller.rb
|
|
445
|
+
- spec/dummy/app/models/ability.rb
|
|
446
|
+
- spec/dummy/app/models/application_record.rb
|
|
447
|
+
- spec/dummy/app/models/user.rb
|
|
448
|
+
- spec/dummy/app/views/layouts/application.html.erb
|
|
449
|
+
- spec/dummy/config/application.rb
|
|
450
|
+
- spec/dummy/config/database.yml
|
|
451
|
+
- spec/dummy/config/environment.rb
|
|
452
|
+
- spec/dummy/config/initializers/simple_form.rb
|
|
453
|
+
- spec/dummy/config/routes.rb
|
|
454
|
+
- spec/dummy/db/schema.rb
|
|
455
|
+
- spec/rails_helper.rb
|
|
456
|
+
- spec/requests/authorization_spec.rb
|
|
457
|
+
- spec/requests/plain_component_spec.rb
|
|
458
|
+
- spec/requests/routing_spec.rb
|
|
459
|
+
- spec/requests/users_crud_spec.rb
|
|
398
460
|
licenses: []
|
|
399
461
|
metadata: {}
|
|
400
462
|
rdoc_options: []
|