uchi 0.1.2 → 0.1.3
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/CHANGELOG.md +29 -0
- data/docs/fields.md +82 -0
- data/docs/repositories.md +63 -0
- data/test/components/uchi/field/belongs_to_test.rb +3 -3
- data/test/components/uchi/field/blank_test.rb +2 -2
- data/test/components/uchi/field/boolean_test.rb +2 -2
- data/test/components/uchi/field/date_test.rb +2 -2
- data/test/components/uchi/field/date_time_test.rb +2 -2
- data/test/components/uchi/field/has_and_belongs_to_many_test.rb +144 -0
- data/test/components/uchi/field/has_many_test.rb +3 -3
- data/test/components/uchi/field/id_test.rb +2 -2
- data/test/components/uchi/field/number_test.rb +2 -2
- data/test/components/uchi/field/string_test.rb +7 -7
- data/test/components/uchi/field/text_test.rb +160 -0
- data/test/components/uchi/ui/form/input/collection_checkboxes_test.rb +171 -0
- data/test/controllers/uchi/authors_controller_test.rb +5 -4
- data/test/dummy/app/models/author.rb +2 -0
- data/test/dummy/app/models/book.rb +1 -0
- data/test/dummy/app/uchi/repositories/author.rb +2 -2
- data/test/dummy/config/locales/da.yml +1 -0
- data/test/dummy/db/migrate/20251031140958_add_author_books_join_table.rb +9 -0
- data/test/dummy/db/schema.rb +7 -1
- data/test/uchi/field_test.rb +17 -3
- data/test/uchi/repository/translate_test.rb +12 -3
- metadata +16 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 92849fb71ff71c56d796e3e43601dd99eabc565180c902ac129feb25f8cc5fc0
|
|
4
|
+
data.tar.gz: 789df26e207ac6ed7e8857269bafc4bcd06d21c3ec22b6070c1092c222a42d08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 146839a586037ec5e0b713aec63ca699b50346c9d0be98935db42a562fe1f78be1bc6d16d4afe9a4ec38431cb440efb4d63b79527b2873db87ce01225f256153
|
|
7
|
+
data.tar.gz: 002ea4b08886edfefbab4c7580db0524b1776824f2aea0d75077c90ed2331cc3297ed3616121cf6dd86bc9e54cc0556e5d4a00ccc8848a749d1af6b593bf9223
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
### Removed
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [0.1.3]
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Field::Text for multi-line text content like descriptions, biographies, notes, and comments.
|
|
22
|
+
- Field for HasAndBelongsToMany associations.
|
|
23
|
+
- Better blank slate when a RecordsTable has no records; we also no longer show page navigation when there are no records.
|
|
24
|
+
- A changelog!
|
|
25
|
+
- Everything else up until now ;)
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- uchi:controller generator now generates proper controller names when name contains multiple words.
|
data/docs/fields.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Fields
|
|
2
|
+
|
|
3
|
+
## Only show a field on specific pages
|
|
4
|
+
|
|
5
|
+
Use the `on` method to control what pages to show a field on. For example if your id field should only be visible on the index listing, you can configure it as
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
Field::Number.new(:id).on(:index)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Possible actions are
|
|
12
|
+
|
|
13
|
+
- `:index`
|
|
14
|
+
- `:show`
|
|
15
|
+
- `:new`
|
|
16
|
+
- `:edit`
|
|
17
|
+
|
|
18
|
+
The default is to show all fields on all pages.
|
|
19
|
+
|
|
20
|
+
## Search
|
|
21
|
+
|
|
22
|
+
If a repository contains at least one searchable `Field` a search field appears on the index page. By default all text-based fields are considered searchable.
|
|
23
|
+
|
|
24
|
+
The search is fairly naive and is a bunch of `LIKE '%query%'` (`ILIKE` in PostgreSQL) clauses strung together by `OR`.
|
|
25
|
+
|
|
26
|
+
### Disable search
|
|
27
|
+
|
|
28
|
+
To toggle searchability for a field use the `:searchable` option:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
Field::String.new(:password).searchable(false)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Enable search
|
|
35
|
+
|
|
36
|
+
You can also enable search for fields that don't enable it by default:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
Field::Number.new(:id).searchable(true)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Uchi casts whatever datatype the field uses into a string when searching and perform a partial match on it using `LIKE` (`ILIKE` in PostgreSQL), which may or may not yield the results you expect.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Sorting
|
|
46
|
+
|
|
47
|
+
All fields are considered sortable by default. This means that a link to toggle the order of a column appears for all columns on index pages. How to sort a specific field - or to disable it entirely - is configured using the `:sortable` option.
|
|
48
|
+
|
|
49
|
+
### Disable sorting
|
|
50
|
+
|
|
51
|
+
To disable sorting a specific field:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
Field::Number.new(:calculated_sum).sortable(false)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Customize sorting
|
|
58
|
+
|
|
59
|
+
To customize the query used to sort by a given field, pass a lambda to the `sortable` method:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
Field::Number.new(:users_count).sortable(lambda { |query, direction|
|
|
63
|
+
query.joins(:users).group(:id).order("COUNT(users.id) #{direction}")
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The lambda receives 2 arguments:
|
|
68
|
+
|
|
69
|
+
1. `query`: The `ActiveRecord::Relation` that makes up the current database query
|
|
70
|
+
2. `direction`: A symbol indicating what order to sort; either `:asc` or `:desc`.
|
|
71
|
+
|
|
72
|
+
The lambda should return an `ActiveRecord::Relation` with the desired sort order added.
|
|
73
|
+
|
|
74
|
+
### Sorting by columns in another table
|
|
75
|
+
|
|
76
|
+
Thanks to ActiveRecord we can even sort by columns in other tables/models. If you have an `Employee` model that belongs to a `Company` and you want to allow your users to sort the employee list by company name, you can configure the field like this:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
Field::BelongsTo.new(:company).sortable(lambda { |query, direction|
|
|
80
|
+
query.joins(:office).order(:offices => {:name => direction})
|
|
81
|
+
})
|
|
82
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Repositories
|
|
2
|
+
|
|
3
|
+
The cornerstones of Uchi are the repositories. This is where you configure what parts of your models you want to expose and how to do it.
|
|
4
|
+
|
|
5
|
+
## Models
|
|
6
|
+
|
|
7
|
+
There's a one-to-one mapping between a repository and a model. So if you have a `User` model that you want to include in Uchi, you must have a `User` repository as well.
|
|
8
|
+
|
|
9
|
+
## Routes
|
|
10
|
+
|
|
11
|
+
In order to expose your requests to your users, you need a route for each of them. These routes are added to `config/routes.rb` in your main application under the `uchi` namespace:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
namespace :uchi do
|
|
15
|
+
resources :companies
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
See [Rails' routing documentation](https://guides.rubyonrails.org/routing.html) for more details.
|
|
20
|
+
|
|
21
|
+
### Root URL
|
|
22
|
+
|
|
23
|
+
If you want to expose a repository at the root URL (ie `/uchi/`) you can configure a [`root`](https://guides.rubyonrails.org/routing.html#using-root) for the namespace:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
namespace :uchi do
|
|
27
|
+
root "companies#index"
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Default sort order
|
|
32
|
+
|
|
33
|
+
Lists of records in a repository are by default sorted by a column called `id`. To customize the default sort order, which is used when a user hasn’t explicitly chosen to sort by a specific field, you can create a `default_sort_order` method in the repository:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
module Uchi
|
|
37
|
+
class CustomersRepository < Uchi::Repository
|
|
38
|
+
def default_sort_order
|
|
39
|
+
SortOrder.new(:name, :desc)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`default_sort_order` should return a `Uchi::Repository::SortOrder`.
|
|
46
|
+
|
|
47
|
+
## Avoiding n+1
|
|
48
|
+
|
|
49
|
+
To avoid n+1 performance issues on your index pages and other lists, you can set up includes for the repository.
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
module Uchi
|
|
53
|
+
module Repositories
|
|
54
|
+
class User < Repository
|
|
55
|
+
def includes
|
|
56
|
+
[:account]
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
See https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-includes for details.
|
|
@@ -22,7 +22,7 @@ module Uchi
|
|
|
22
22
|
|
|
23
23
|
test "has custom collection_query" do
|
|
24
24
|
custom_query = ->(query) { query.where(active: true) }
|
|
25
|
-
field = Uchi::Field::BelongsTo.new(:book
|
|
25
|
+
field = Uchi::Field::BelongsTo.new(:book).collection_query(custom_query)
|
|
26
26
|
assert_equal custom_query, field.collection_query
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -61,7 +61,7 @@ module Uchi
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
test "#searchable? returns false when explicitly set" do
|
|
64
|
-
field = Uchi::Field::BelongsTo.new(:book
|
|
64
|
+
field = Uchi::Field::BelongsTo.new(:book).searchable(false)
|
|
65
65
|
assert_not field.searchable?
|
|
66
66
|
end
|
|
67
67
|
|
|
@@ -74,7 +74,7 @@ module Uchi
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
test "#sortable? returns false when explicitly set" do
|
|
77
|
-
field = Uchi::Field::BelongsTo.new(:book
|
|
77
|
+
field = Uchi::Field::BelongsTo.new(:book).sortable(false)
|
|
78
78
|
assert_not field.sortable?
|
|
79
79
|
end
|
|
80
80
|
end
|
|
@@ -39,7 +39,7 @@ module Uchi
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
test "#searchable? returns false when explicitly set" do
|
|
42
|
-
field = Uchi::Field::Blank.new(:separator
|
|
42
|
+
field = Uchi::Field::Blank.new(:separator).searchable(false)
|
|
43
43
|
assert_not field.searchable?
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ module Uchi
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
test "#sortable? returns false when explicitly set" do
|
|
55
|
-
field = Uchi::Field::Blank.new(:separator
|
|
55
|
+
field = Uchi::Field::Blank.new(:separator).sortable(false)
|
|
56
56
|
assert_not field.sortable?
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -39,7 +39,7 @@ module Uchi
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
test "#searchable? returns false when explicitly set" do
|
|
42
|
-
field = Uchi::Field::Boolean.new(:active
|
|
42
|
+
field = Uchi::Field::Boolean.new(:active).searchable(false)
|
|
43
43
|
assert_not field.searchable?
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ module Uchi
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
test "#sortable? returns false when explicitly set" do
|
|
55
|
-
field = Uchi::Field::Boolean.new(:active
|
|
55
|
+
field = Uchi::Field::Boolean.new(:active).sortable(false)
|
|
56
56
|
assert_not field.sortable?
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -39,7 +39,7 @@ module Uchi
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
test "#searchable? returns false when explicitly set" do
|
|
42
|
-
field = Uchi::Field::Date.new(:born_on
|
|
42
|
+
field = Uchi::Field::Date.new(:born_on).searchable(false)
|
|
43
43
|
assert_not field.searchable?
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ module Uchi
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
test "#sortable? returns false when explicitly set" do
|
|
55
|
-
field = Uchi::Field::Date.new(:born_on
|
|
55
|
+
field = Uchi::Field::Date.new(:born_on).sortable(false)
|
|
56
56
|
assert_not field.sortable?
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -39,7 +39,7 @@ module Uchi
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
test "#searchable? returns false when explicitly set" do
|
|
42
|
-
field = Uchi::Field::DateTime.new(:created_at
|
|
42
|
+
field = Uchi::Field::DateTime.new(:created_at).searchable(false)
|
|
43
43
|
assert_not field.searchable?
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ module Uchi
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
test "#sortable? returns false when explicitly set" do
|
|
55
|
-
field = Uchi::Field::DateTime.new(:created_at
|
|
55
|
+
field = Uchi::Field::DateTime.new(:created_at).sortable(false)
|
|
56
56
|
assert_not field.sortable?
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
require "ostruct"
|
|
3
|
+
|
|
4
|
+
module Uchi
|
|
5
|
+
class Field
|
|
6
|
+
class HasAndBelongsToManyTest < ActiveSupport::TestCase
|
|
7
|
+
def setup
|
|
8
|
+
@record = Book.new
|
|
9
|
+
@form = OpenStruct.new(object: @record)
|
|
10
|
+
|
|
11
|
+
@repository = Uchi::Repositories::Book.new
|
|
12
|
+
@field = Uchi::Field::HasAndBelongsToMany.new(:authors)
|
|
13
|
+
@field.repository = @repository
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
test "inherits from Uchi::Field" do
|
|
17
|
+
assert_kind_of Uchi::Field, @field
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
test "has default options specific to HasAndBelongsToMany field" do
|
|
21
|
+
assert_not @field.searchable?
|
|
22
|
+
assert @field.sortable?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
test "has custom collection_query" do
|
|
26
|
+
custom_query = ->(query) { query.where(published: true) }
|
|
27
|
+
field = Uchi::Field::HasAndBelongsToMany.new(:categories).collection_query(custom_query)
|
|
28
|
+
assert_equal custom_query, field.collection_query
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
test "uses default collection_query" do
|
|
32
|
+
assert_equal Uchi::Field::HasAndBelongsToMany::DEFAULT_COLLECTION_QUERY, @field.collection_query
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
test "#param_key returns foreign key name" do
|
|
36
|
+
assert_equal :author_ids, @field.param_key
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
test "#permitted_param returns key for strong parameters" do
|
|
40
|
+
assert_equal({author_ids: []}, @field.permitted_param)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
test "#group_as returns :associations" do
|
|
44
|
+
assert_equal :associations, @field.group_as(:show)
|
|
45
|
+
assert_equal :associations, @field.group_as(:edit)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
test "#edit_component returns an instance of Edit component" do
|
|
49
|
+
component = @field.edit_component(form: @form, hint: "Custom hint", label: "Custom label", repository: @repository)
|
|
50
|
+
assert_equal "Custom hint", component.hint
|
|
51
|
+
assert_equal "Custom label", component.label
|
|
52
|
+
assert_equal @field, component.field
|
|
53
|
+
assert_equal @form, component.form
|
|
54
|
+
assert_equal @repository, component.repository
|
|
55
|
+
assert_kind_of Uchi::Field::HasAndBelongsToMany::Edit, component
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
test "#index_component returns an instance of Index component" do
|
|
59
|
+
component = @field.index_component(record: @form.object, repository: @repository)
|
|
60
|
+
assert_equal @field, component.field
|
|
61
|
+
assert_equal @form.object, component.record
|
|
62
|
+
assert_equal @repository, component.repository
|
|
63
|
+
assert_kind_of Uchi::Field::HasAndBelongsToMany::Index, component
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
test "#show_component returns an instance of Show component" do
|
|
67
|
+
component = @field.show_component(record: @form.object, repository: @repository)
|
|
68
|
+
assert_equal @field, component.field
|
|
69
|
+
assert_equal @form.object, component.record
|
|
70
|
+
assert_equal @repository, component.repository
|
|
71
|
+
assert_kind_of Uchi::Field::HasAndBelongsToMany::Show, component
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
test "#searchable? returns false when explicitly set" do
|
|
75
|
+
field = Uchi::Field::HasAndBelongsToMany.new(:categories).searchable(false)
|
|
76
|
+
assert_not field.searchable?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
test "#sortable? returns false when explicitly set" do
|
|
80
|
+
field = Uchi::Field::HasAndBelongsToMany.new(:categories).sortable(false)
|
|
81
|
+
assert_not field.sortable?
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class HasAndBelongsToManyEditTest < ViewComponent::TestCase
|
|
86
|
+
def setup
|
|
87
|
+
@field = Uchi::Field::HasAndBelongsToMany.new(:categories)
|
|
88
|
+
@book = Book.new(original_title: "The Hobbit")
|
|
89
|
+
@repository = Uchi::Repositories::Book.new
|
|
90
|
+
@view_context = ActionController::Base.new.view_context
|
|
91
|
+
|
|
92
|
+
@form = ActionView::Helpers::FormBuilder.new(:book, @book, @view_context, {})
|
|
93
|
+
|
|
94
|
+
@component = Uchi::Field::HasAndBelongsToMany::Edit.new(
|
|
95
|
+
field: @field,
|
|
96
|
+
form: @form,
|
|
97
|
+
hint: "Custom hint",
|
|
98
|
+
label: "Custom label",
|
|
99
|
+
repository: @repository
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
test "inherits from Base component" do
|
|
104
|
+
assert_kind_of Uchi::Field::Base::Edit, @component
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class HasAndBelongsToManyIndexTest < ViewComponent::TestCase
|
|
109
|
+
def setup
|
|
110
|
+
@field = Uchi::Field::HasAndBelongsToMany.new(:categories)
|
|
111
|
+
@book = Book.new(original_title: "The Hobbit")
|
|
112
|
+
@repository = Uchi::Repositories::Book.new
|
|
113
|
+
|
|
114
|
+
@component = Uchi::Field::HasAndBelongsToMany::Index.new(
|
|
115
|
+
field: @field,
|
|
116
|
+
record: @book,
|
|
117
|
+
repository: @repository
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
test "inherits from Base component" do
|
|
122
|
+
assert_kind_of Uchi::Field::Base::Index, @component
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
class HasAndBelongsToManyShowTest < ViewComponent::TestCase
|
|
127
|
+
def setup
|
|
128
|
+
@field = Uchi::Field::HasAndBelongsToMany.new(:categories)
|
|
129
|
+
@book = Book.new(original_title: "The Hobbit")
|
|
130
|
+
@repository = Uchi::Repositories::Book.new
|
|
131
|
+
|
|
132
|
+
@component = Uchi::Field::HasAndBelongsToMany::Show.new(
|
|
133
|
+
field: @field,
|
|
134
|
+
record: @book,
|
|
135
|
+
repository: @repository
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
test "inherits from Base component" do
|
|
140
|
+
assert_kind_of Uchi::Field::Base::Show, @component
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -22,7 +22,7 @@ module Uchi
|
|
|
22
22
|
|
|
23
23
|
test "has custom collection_query" do
|
|
24
24
|
custom_query = ->(query) { query.where(published: true) }
|
|
25
|
-
field = Uchi::Field::HasMany.new(:titles
|
|
25
|
+
field = Uchi::Field::HasMany.new(:titles).collection_query(custom_query)
|
|
26
26
|
assert_equal custom_query, field.collection_query
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -66,12 +66,12 @@ module Uchi
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
test "#searchable? returns false when explicitly set" do
|
|
69
|
-
field = Uchi::Field::HasMany.new(:titles
|
|
69
|
+
field = Uchi::Field::HasMany.new(:titles).searchable(false)
|
|
70
70
|
assert_not field.searchable?
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
test "#sortable? returns false when explicitly set" do
|
|
74
|
-
field = Uchi::Field::HasMany.new(:titles
|
|
74
|
+
field = Uchi::Field::HasMany.new(:titles).sortable(false)
|
|
75
75
|
assert_not field.sortable?
|
|
76
76
|
end
|
|
77
77
|
end
|
|
@@ -42,7 +42,7 @@ module Uchi
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
test "#searchable? returns false when explicitly set" do
|
|
45
|
-
field = Uchi::Field::Id.new(:id
|
|
45
|
+
field = Uchi::Field::Id.new(:id).searchable(false)
|
|
46
46
|
assert_not field.searchable?
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -55,7 +55,7 @@ module Uchi
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
test "#sortable? returns false when explicitly set" do
|
|
58
|
-
field = Uchi::Field::Id.new(:id
|
|
58
|
+
field = Uchi::Field::Id.new(:id).sortable(false)
|
|
59
59
|
assert_not field.sortable?
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -39,7 +39,7 @@ module Uchi
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
test "#searchable? returns false when explicitly set" do
|
|
42
|
-
field = Uchi::Field::Number.new(:age
|
|
42
|
+
field = Uchi::Field::Number.new(:age).searchable(false)
|
|
43
43
|
assert_not field.searchable?
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ module Uchi
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
test "#sortable? returns false when explicitly set" do
|
|
55
|
-
field = Uchi::Field::Number.new(:age
|
|
55
|
+
field = Uchi::Field::Number.new(:age).sortable(false)
|
|
56
56
|
assert_not field.sortable?
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -39,7 +39,7 @@ module Uchi
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
test "#searchable? returns false when explicitly set" do
|
|
42
|
-
field = Uchi::Field::String.new(:name
|
|
42
|
+
field = Uchi::Field::String.new(:name).searchable(false)
|
|
43
43
|
assert_not field.searchable?
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ module Uchi
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
test "#sortable? returns false when explicitly set" do
|
|
55
|
-
field = Uchi::Field::String.new(:name
|
|
55
|
+
field = Uchi::Field::String.new(:name).sortable(false)
|
|
56
56
|
assert_not field.sortable?
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -60,7 +60,7 @@ module Uchi
|
|
|
60
60
|
class StringEditTest < ViewComponent::TestCase
|
|
61
61
|
def setup
|
|
62
62
|
@field = Uchi::Field::String.new(:name)
|
|
63
|
-
@record = Author.new(name: "J.R.R Tolkien")
|
|
63
|
+
@record = Author.new(name: "J.R.R. Tolkien")
|
|
64
64
|
@repository = Uchi::Repositories::Author.new
|
|
65
65
|
@view_context = ActionController::Base.new.view_context
|
|
66
66
|
|
|
@@ -111,7 +111,7 @@ module Uchi
|
|
|
111
111
|
class StringIndexTest < ViewComponent::TestCase
|
|
112
112
|
def setup
|
|
113
113
|
@field = Uchi::Field::String.new(:name)
|
|
114
|
-
@record = Author.new(name: "J.R.R Tolkien")
|
|
114
|
+
@record = Author.new(name: "J.R.R. Tolkien")
|
|
115
115
|
@repository = Uchi::Repositories::Author.new
|
|
116
116
|
|
|
117
117
|
@component = Uchi::Field::String::Index.new(
|
|
@@ -128,14 +128,14 @@ module Uchi
|
|
|
128
128
|
test "renders the field content" do
|
|
129
129
|
result = render_inline(@component)
|
|
130
130
|
|
|
131
|
-
assert_includes result.to_html, "J.R.R Tolkien"
|
|
131
|
+
assert_includes result.to_html, "J.R.R. Tolkien"
|
|
132
132
|
end
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
class StringShowTest < ViewComponent::TestCase
|
|
136
136
|
def setup
|
|
137
137
|
@field = Uchi::Field::String.new(:name)
|
|
138
|
-
@record = Author.new(name: "J.R.R Tolkien")
|
|
138
|
+
@record = Author.new(name: "J.R.R. Tolkien")
|
|
139
139
|
@repository = Uchi::Repositories::Author.new
|
|
140
140
|
|
|
141
141
|
@component = Uchi::Field::String::Show.new(
|
|
@@ -152,7 +152,7 @@ module Uchi
|
|
|
152
152
|
test "renders the field content" do
|
|
153
153
|
result = render_inline(@component)
|
|
154
154
|
|
|
155
|
-
assert_includes result.to_html, "J.R.R Tolkien"
|
|
155
|
+
assert_includes result.to_html, "J.R.R. Tolkien"
|
|
156
156
|
end
|
|
157
157
|
end
|
|
158
158
|
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
require "ostruct"
|
|
3
|
+
|
|
4
|
+
module Uchi
|
|
5
|
+
class Field
|
|
6
|
+
class TextTest < ActiveSupport::TestCase
|
|
7
|
+
def setup
|
|
8
|
+
@field = Uchi::Field::Text.new(:biography)
|
|
9
|
+
@form = OpenStruct.new(object: OpenStruct.new(biography: "Test Biography"))
|
|
10
|
+
@repository = Uchi::Repositories::Author.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
test "inherits from Uchi::Field" do
|
|
14
|
+
assert_kind_of Uchi::Field, @field
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
test "has default options" do
|
|
18
|
+
assert_equal [:edit, :show], @field.on
|
|
19
|
+
assert @field.searchable?
|
|
20
|
+
assert @field.sortable?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
test "#edit_component returns an instance of Edit component" do
|
|
24
|
+
component = @field.edit_component(form: @form, hint: "Custom hint", label: "Custom label", repository: @repository)
|
|
25
|
+
assert_equal "Custom hint", component.hint
|
|
26
|
+
assert_equal "Custom label", component.label
|
|
27
|
+
assert_equal @field, component.field
|
|
28
|
+
assert_equal @form, component.form
|
|
29
|
+
assert_equal @repository, component.repository
|
|
30
|
+
assert_kind_of Uchi::Field::Text::Edit, component
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
test "#index_component returns an instance of Index component" do
|
|
34
|
+
component = @field.index_component(record: @form.object, repository: @repository)
|
|
35
|
+
assert_equal @field, component.field
|
|
36
|
+
assert_equal @form.object, component.record
|
|
37
|
+
assert_equal @repository, component.repository
|
|
38
|
+
assert_kind_of Uchi::Field::Text::Index, component
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
test "#searchable? returns false when explicitly set" do
|
|
42
|
+
field = Uchi::Field::Text.new(:biography).searchable(false)
|
|
43
|
+
assert_not field.searchable?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
test "#show_component returns an instance of Show component" do
|
|
47
|
+
component = @field.show_component(record: @form.object, repository: @repository)
|
|
48
|
+
assert_equal @field, component.field
|
|
49
|
+
assert_equal @form.object, component.record
|
|
50
|
+
assert_equal @repository, component.repository
|
|
51
|
+
assert_kind_of Uchi::Field::Text::Show, component
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
test "#sortable? returns false when explicitly set" do
|
|
55
|
+
field = Uchi::Field::Text.new(:biography).sortable(false)
|
|
56
|
+
assert_not field.sortable?
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class TextEditTest < ViewComponent::TestCase
|
|
61
|
+
def setup
|
|
62
|
+
@field = Uchi::Field::Text.new(:biography)
|
|
63
|
+
@record = Author.new(name: "J.R.R. Tolkien", biography: "Famous author")
|
|
64
|
+
@repository = Uchi::Repositories::Author.new
|
|
65
|
+
@view_context = ActionController::Base.new.view_context
|
|
66
|
+
|
|
67
|
+
@form = ActionView::Helpers::FormBuilder.new(:author, @record, @view_context, {})
|
|
68
|
+
|
|
69
|
+
@component = Uchi::Field::Text::Edit.new(
|
|
70
|
+
field: @field,
|
|
71
|
+
form: @form,
|
|
72
|
+
hint: "Custom hint",
|
|
73
|
+
label: "Custom label",
|
|
74
|
+
repository: @repository
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
test "inherits from Base component" do
|
|
79
|
+
assert_kind_of Uchi::Field::Base::Edit, @component
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
test "renders a textarea field with the field content" do
|
|
83
|
+
render_inline(@component)
|
|
84
|
+
|
|
85
|
+
assert_selector("textarea[name='author[biography]'][rows='8']", text: "Famous author")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
test "renders label with specified text" do
|
|
89
|
+
render_inline(@component)
|
|
90
|
+
|
|
91
|
+
assert_selector("label[for='author_biography']", text: "Custom label")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
test "renders hint when provided" do
|
|
95
|
+
render_inline(@component)
|
|
96
|
+
|
|
97
|
+
assert_selector("p[id=author_biography_hint]", text: "Custom hint")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
test "initializes the input component with the correct options" do
|
|
101
|
+
expected_options = {
|
|
102
|
+
attribute: :biography,
|
|
103
|
+
form: @form,
|
|
104
|
+
label: {content: "Custom label"},
|
|
105
|
+
input: {options: {rows: 8}},
|
|
106
|
+
hint: {content: "Custom hint"}
|
|
107
|
+
}
|
|
108
|
+
assert_equal expected_options, @component.send(:options)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class TextIndexTest < ViewComponent::TestCase
|
|
113
|
+
def setup
|
|
114
|
+
@field = Uchi::Field::Text.new(:biography)
|
|
115
|
+
@record = Author.new(name: "J.R.R. Tolkien", biography: "Famous author of The Lord of the Rings")
|
|
116
|
+
@repository = Uchi::Repositories::Author.new
|
|
117
|
+
|
|
118
|
+
@component = Uchi::Field::Text::Index.new(
|
|
119
|
+
field: @field,
|
|
120
|
+
record: @record,
|
|
121
|
+
repository: @repository
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
test "inherits from Base component" do
|
|
126
|
+
assert_kind_of Uchi::Field::Base::Index, @component
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
test "renders the field content" do
|
|
130
|
+
result = render_inline(@component)
|
|
131
|
+
|
|
132
|
+
assert_includes result.to_html, "Famous author of The Lord of the Rings"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
class TextShowTest < ViewComponent::TestCase
|
|
137
|
+
def setup
|
|
138
|
+
@field = Uchi::Field::Text.new(:biography)
|
|
139
|
+
@record = Author.new(name: "J.R.R. Tolkien", biography: "Famous author of The Lord of the Rings")
|
|
140
|
+
@repository = Uchi::Repositories::Author.new
|
|
141
|
+
|
|
142
|
+
@component = Uchi::Field::Text::Show.new(
|
|
143
|
+
field: @field,
|
|
144
|
+
record: @record,
|
|
145
|
+
repository: @repository
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
test "inherits from Base component" do
|
|
150
|
+
assert_kind_of Uchi::Field::Base::Show, @component
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
test "renders the field content" do
|
|
154
|
+
result = render_inline(@component)
|
|
155
|
+
|
|
156
|
+
assert_includes result.to_html, "Famous author of The Lord of the Rings"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
module Form
|
|
6
|
+
module Input
|
|
7
|
+
class CollectionCheckboxesTest < ViewComponent::TestCase
|
|
8
|
+
class Tag
|
|
9
|
+
attr_accessor :id, :name
|
|
10
|
+
|
|
11
|
+
def initialize(id, name)
|
|
12
|
+
@id = id
|
|
13
|
+
@name = name
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def setup
|
|
18
|
+
@tags = [
|
|
19
|
+
Tag.new(1, "Ruby"),
|
|
20
|
+
Tag.new(2, "Rails"),
|
|
21
|
+
Tag.new(3, "JavaScript")
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
@record = Author.new(name: "Test Author")
|
|
25
|
+
@record.define_singleton_method(:tag_ids) { [1, 3] }
|
|
26
|
+
@record.define_singleton_method(:tag_ids=) { |val| @tag_ids = val }
|
|
27
|
+
|
|
28
|
+
@view_context = ActionController::Base.new.view_context
|
|
29
|
+
@form = ActionView::Helpers::FormBuilder.new(:author, @record, @view_context, {})
|
|
30
|
+
|
|
31
|
+
@component = Uchi::Ui::Form::Input::CollectionCheckboxes.new(
|
|
32
|
+
attribute: :tag_ids,
|
|
33
|
+
collection: @tags,
|
|
34
|
+
form: @form,
|
|
35
|
+
label: "Select Tags",
|
|
36
|
+
hint: "Choose one or more tags",
|
|
37
|
+
value_method: :id,
|
|
38
|
+
text_method: :name
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
test "renders collection checkboxes" do
|
|
43
|
+
render_inline(@component)
|
|
44
|
+
|
|
45
|
+
assert_selector("input[type='hidden'][name='author[tag_ids][]']", visible: :all, count: 1)
|
|
46
|
+
assert_selector("input[type='checkbox'][name='author[tag_ids][]']", count: 3)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
test "renders label when provided" do
|
|
50
|
+
render_inline(@component)
|
|
51
|
+
|
|
52
|
+
assert_selector("label", text: "Select Tags")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
test "does not render label when not provided" do
|
|
56
|
+
component = Uchi::Ui::Form::Input::CollectionCheckboxes.new(
|
|
57
|
+
attribute: :tag_ids,
|
|
58
|
+
collection: @tags,
|
|
59
|
+
form: @form
|
|
60
|
+
)
|
|
61
|
+
render_inline(component)
|
|
62
|
+
|
|
63
|
+
assert_no_selector("label", text: "Select Tags")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
test "renders hint when provided" do
|
|
67
|
+
render_inline(@component)
|
|
68
|
+
|
|
69
|
+
assert_selector("p", text: "Choose one or more tags")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
test "does not render hint when not provided" do
|
|
73
|
+
component = Uchi::Ui::Form::Input::CollectionCheckboxes.new(
|
|
74
|
+
attribute: :tag_ids,
|
|
75
|
+
collection: @tags,
|
|
76
|
+
form: @form
|
|
77
|
+
)
|
|
78
|
+
render_inline(component)
|
|
79
|
+
|
|
80
|
+
assert_no_selector("p", text: "Choose one or more tags")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
test "renders checkbox labels for each item" do
|
|
84
|
+
render_inline(@component)
|
|
85
|
+
|
|
86
|
+
assert_selector("label", text: "Ruby")
|
|
87
|
+
assert_selector("label", text: "Rails")
|
|
88
|
+
assert_selector("label", text: "JavaScript")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
test "renders checkboxes with correct values" do
|
|
92
|
+
render_inline(@component)
|
|
93
|
+
|
|
94
|
+
assert_selector("input[type='checkbox'][value='1']")
|
|
95
|
+
assert_selector("input[type='checkbox'][value='2']")
|
|
96
|
+
assert_selector("input[type='checkbox'][value='3']")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
test "applies disabled state to all checkboxes" do
|
|
100
|
+
component = Uchi::Ui::Form::Input::CollectionCheckboxes.new(
|
|
101
|
+
attribute: :tag_ids,
|
|
102
|
+
collection: @tags,
|
|
103
|
+
form: @form,
|
|
104
|
+
disabled: true
|
|
105
|
+
)
|
|
106
|
+
render_inline(component)
|
|
107
|
+
|
|
108
|
+
assert_selector("input[type='checkbox'][disabled]", count: 3)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
test "renders errors when present" do
|
|
112
|
+
@record.errors.add(:tag_ids, "must be selected")
|
|
113
|
+
render_inline(@component)
|
|
114
|
+
|
|
115
|
+
assert_selector("p", text: /MUST BE SELECTED/i)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
test "applies error styling when errors present" do
|
|
119
|
+
@record.errors.add(:tag_ids, "must be selected")
|
|
120
|
+
component = Uchi::Ui::Form::Input::CollectionCheckboxes.new(
|
|
121
|
+
attribute: :tag_ids,
|
|
122
|
+
collection: @tags,
|
|
123
|
+
form: @form
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
render_inline(component)
|
|
127
|
+
|
|
128
|
+
# Check that error classes are applied to checkboxes
|
|
129
|
+
assert component.errors?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
test "uses custom value and text methods" do
|
|
133
|
+
custom_tags = [
|
|
134
|
+
OpenStruct.new(identifier: "ruby", title: "Ruby Programming"),
|
|
135
|
+
OpenStruct.new(identifier: "rails", title: "Rails Framework")
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
component = Uchi::Ui::Form::Input::CollectionCheckboxes.new(
|
|
139
|
+
attribute: :tag_ids,
|
|
140
|
+
collection: custom_tags,
|
|
141
|
+
form: @form,
|
|
142
|
+
value_method: :identifier,
|
|
143
|
+
text_method: :title
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
render_inline(component)
|
|
147
|
+
|
|
148
|
+
assert_selector("input[type='checkbox'][value='ruby']")
|
|
149
|
+
assert_selector("input[type='checkbox'][value='rails']")
|
|
150
|
+
assert_selector("label", text: "Ruby Programming")
|
|
151
|
+
assert_selector("label", text: "Rails Framework")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
test "handles empty collection" do
|
|
155
|
+
component = Uchi::Ui::Form::Input::CollectionCheckboxes.new(
|
|
156
|
+
attribute: :tag_ids,
|
|
157
|
+
collection: [],
|
|
158
|
+
form: @form
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
render_inline(component)
|
|
162
|
+
|
|
163
|
+
# Should only have the hidden field, no checkboxes
|
|
164
|
+
assert_selector("input[type='hidden'][name='author[tag_ids][]']", visible: :all, count: 1)
|
|
165
|
+
assert_selector("input[type='checkbox']", count: 0)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -47,8 +47,9 @@ module Uchi
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
test "PATCH update redirects to show after successful update" do
|
|
50
|
+
srand(42)
|
|
50
51
|
patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
|
|
51
|
-
assert_redirected_to uchi_author_url(id: @author.id)
|
|
52
|
+
assert_redirected_to uchi_author_url(id: @author.id, uniq: 0.3745401188473625)
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
test "PATCH update responds with 303 after successful update" do
|
|
@@ -65,13 +66,13 @@ module Uchi
|
|
|
65
66
|
test "PATCH update flashes a translated success message after successful update" do
|
|
66
67
|
::I18n.with_locale(:da) do
|
|
67
68
|
patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
|
|
68
|
-
assert_equal "Dine ændringer til forfatteren blev gemt", flash[:
|
|
69
|
+
assert_equal "Dine ændringer til forfatteren blev gemt", flash[:success]
|
|
69
70
|
end
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
test "PATCH update falls back to default success message after successful update" do
|
|
73
74
|
patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
|
|
74
|
-
assert_equal "Your changes have been saved", flash[:
|
|
75
|
+
assert_equal "Your changes have been saved", flash[:success]
|
|
75
76
|
end
|
|
76
77
|
|
|
77
78
|
test "PATCH update rerenders the edit view after unsuccessful update" do
|
|
@@ -97,7 +98,7 @@ module Uchi
|
|
|
97
98
|
test "POST create flashes a translated success message after successful creation" do
|
|
98
99
|
::I18n.with_locale(:da) do
|
|
99
100
|
post uchi_authors_url, params: {author: {name: "New Author"}}
|
|
100
|
-
assert_equal "Forfatteren er blevet tilføjet", flash[:
|
|
101
|
+
assert_equal "Forfatteren er blevet tilføjet", flash[:success]
|
|
101
102
|
end
|
|
102
103
|
end
|
|
103
104
|
|
|
@@ -3,10 +3,10 @@ module Uchi
|
|
|
3
3
|
class Author < Repository
|
|
4
4
|
def fields
|
|
5
5
|
[
|
|
6
|
-
Field::Number.new(:id
|
|
6
|
+
Field::Number.new(:id).on(:index, :show),
|
|
7
7
|
Field::String.new(:name),
|
|
8
8
|
Field::Date.new(:born_on),
|
|
9
|
-
Field::
|
|
9
|
+
Field::Text.new(:biography).on(:edit, :new, :show)
|
|
10
10
|
]
|
|
11
11
|
end
|
|
12
12
|
|
data/test/dummy/db/schema.rb
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
#
|
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
|
12
12
|
|
|
13
|
-
ActiveRecord::Schema[8.0].define(version:
|
|
13
|
+
ActiveRecord::Schema[8.0].define(version: 2025_10_31_140958) do
|
|
14
14
|
create_table "authors", force: :cascade do |t|
|
|
15
15
|
t.string "name"
|
|
16
16
|
t.text "biography"
|
|
@@ -19,6 +19,12 @@ ActiveRecord::Schema[8.0].define(version: 2025_10_05_131811) do
|
|
|
19
19
|
t.datetime "updated_at", null: false
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
create_table "authors_books", id: false, force: :cascade do |t|
|
|
23
|
+
t.integer "author_id", null: false
|
|
24
|
+
t.integer "book_id", null: false
|
|
25
|
+
t.index ["author_id", "book_id"], name: "index_authors_books_on_author_id_and_book_id"
|
|
26
|
+
end
|
|
27
|
+
|
|
22
28
|
create_table "books", force: :cascade do |t|
|
|
23
29
|
t.string "original_title"
|
|
24
30
|
t.datetime "created_at", null: false
|
data/test/uchi/field_test.rb
CHANGED
|
@@ -30,24 +30,38 @@ class UchiFieldTest < ActiveSupport::TestCase
|
|
|
30
30
|
assert_equal :name, @field.param_key
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
test "#permitted_param returns name as symbol" do
|
|
34
|
+
assert_equal :name, @field.permitted_param
|
|
35
|
+
end
|
|
36
|
+
|
|
33
37
|
test "#searchable? returns false by default" do
|
|
34
38
|
assert_not @field.searchable?
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
test "#searchable? returns true when explicitly set" do
|
|
38
|
-
field = Uchi::Field.new(:name
|
|
42
|
+
field = Uchi::Field.new(:name).searchable(true)
|
|
39
43
|
assert field.searchable?
|
|
40
44
|
end
|
|
41
45
|
|
|
46
|
+
test "#searchable? returns default (false) when explicitly set to nil" do
|
|
47
|
+
field = Uchi::Field.new(:name).searchable(nil)
|
|
48
|
+
assert_not field.searchable?
|
|
49
|
+
end
|
|
50
|
+
|
|
42
51
|
test "#sortable? returns true by default" do
|
|
43
52
|
assert @field.sortable?
|
|
44
53
|
end
|
|
45
54
|
|
|
46
55
|
test "#sortable? returns false when explicitly set" do
|
|
47
|
-
field = Uchi::Field.new(:name
|
|
56
|
+
field = Uchi::Field.new(:name).sortable(false)
|
|
48
57
|
assert_not field.sortable?
|
|
49
58
|
end
|
|
50
59
|
|
|
60
|
+
test "#sortable? returns default (true) when explicitly set to nil" do
|
|
61
|
+
field = Uchi::Field.new(:name).sortable(nil)
|
|
62
|
+
assert field.sortable?
|
|
63
|
+
end
|
|
64
|
+
|
|
51
65
|
test "#value uses reader to get value from record" do
|
|
52
66
|
record = OpenStruct.new(name: "Test Name")
|
|
53
67
|
assert_equal "Test Name", @field.value(record)
|
|
@@ -55,7 +69,7 @@ class UchiFieldTest < ActiveSupport::TestCase
|
|
|
55
69
|
|
|
56
70
|
test "#value uses custom reader when provided" do
|
|
57
71
|
custom_reader = ->(record, field_name) { "Custom: #{record.public_send(field_name)}" }
|
|
58
|
-
field = Uchi::Field.new(:name
|
|
72
|
+
field = Uchi::Field.new(:name).reader(custom_reader)
|
|
59
73
|
record = OpenStruct.new(name: "Test")
|
|
60
74
|
|
|
61
75
|
assert_equal "Custom: Test", field.value(record)
|
|
@@ -49,9 +49,11 @@ class UchiRepositoryTranslateTest < ActiveSupport::TestCase
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
test "#destroy_dialog_title returns translation from uchi.repository.author.dialog.destroy.title" do
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
I18n.with_locale(:da) do
|
|
53
|
+
record = Author.new(name: "J. K. Rowling")
|
|
54
|
+
result = @translate.destroy_dialog_title(record)
|
|
55
|
+
assert_equal "Er du sikker på, at du vil slette J. K. Rowling?", result
|
|
56
|
+
end
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
test "#destroy_dialog_title falls back to default translation" do
|
|
@@ -169,6 +171,13 @@ class UchiRepositoryTranslateTest < ActiveSupport::TestCase
|
|
|
169
171
|
assert_equal "Loading...", result
|
|
170
172
|
end
|
|
171
173
|
|
|
174
|
+
test "#no_records_found returns translation from uchi.common.no_records_found" do
|
|
175
|
+
I18n.with_locale(:da) do
|
|
176
|
+
result = @translate.no_records_found
|
|
177
|
+
assert_equal "Ingen resultater", result
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
172
181
|
test "#plural_name returns translation from uchi.repository.author.model with count 2" do
|
|
173
182
|
I18n.with_locale(:da) do
|
|
174
183
|
result = @translate.plural_name
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: uchi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jakob Skjerning
|
|
@@ -9,20 +9,6 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: pagy
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: 43.0.0.rc1
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: 43.0.0.rc1
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: rails
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -65,8 +51,12 @@ dependencies:
|
|
|
65
51
|
- - ">="
|
|
66
52
|
- !ruby/object:Gem::Version
|
|
67
53
|
version: '4.0'
|
|
54
|
+
description: Level up your scaffolds with a modern admin backend framework, designed
|
|
55
|
+
for Rails developers who demand both beauty, functionality, and extensibility. Uchi
|
|
56
|
+
provides a set of components and conventions for creating user interfaces that are
|
|
57
|
+
both powerful and easy to use.
|
|
68
58
|
email:
|
|
69
|
-
- jakob@
|
|
59
|
+
- jakob@substancelab.com
|
|
70
60
|
executables: []
|
|
71
61
|
extensions: []
|
|
72
62
|
extra_rdoc_files: []
|
|
@@ -74,6 +64,9 @@ files:
|
|
|
74
64
|
- ".github/dependabot.yml"
|
|
75
65
|
- ".github/workflows/build.yml"
|
|
76
66
|
- ".github/workflows/lint.yml"
|
|
67
|
+
- CHANGELOG.md
|
|
68
|
+
- docs/fields.md
|
|
69
|
+
- docs/repositories.md
|
|
77
70
|
- package.json
|
|
78
71
|
- sig/uchi.rbs
|
|
79
72
|
- test/components/uchi/field/belongs_to_test.rb
|
|
@@ -81,10 +74,13 @@ files:
|
|
|
81
74
|
- test/components/uchi/field/boolean_test.rb
|
|
82
75
|
- test/components/uchi/field/date_test.rb
|
|
83
76
|
- test/components/uchi/field/date_time_test.rb
|
|
77
|
+
- test/components/uchi/field/has_and_belongs_to_many_test.rb
|
|
84
78
|
- test/components/uchi/field/has_many_test.rb
|
|
85
79
|
- test/components/uchi/field/id_test.rb
|
|
86
80
|
- test/components/uchi/field/number_test.rb
|
|
87
81
|
- test/components/uchi/field/string_test.rb
|
|
82
|
+
- test/components/uchi/field/text_test.rb
|
|
83
|
+
- test/components/uchi/ui/form/input/collection_checkboxes_test.rb
|
|
88
84
|
- test/controllers/uchi/authors_controller_test.rb
|
|
89
85
|
- test/controllers/uchi/repository_controller_test.rb
|
|
90
86
|
- test/controllers/uchi/scoped_repository_controller_test.rb
|
|
@@ -136,6 +132,7 @@ files:
|
|
|
136
132
|
- test/dummy/db/migrate/20251002183635_create_authors.rb
|
|
137
133
|
- test/dummy/db/migrate/20251005131726_create_books.rb
|
|
138
134
|
- test/dummy/db/migrate/20251005131811_create_titles.rb
|
|
135
|
+
- test/dummy/db/migrate/20251031140958_add_author_books_join_table.rb
|
|
139
136
|
- test/dummy/db/schema.rb
|
|
140
137
|
- test/dummy/log/.keep
|
|
141
138
|
- test/dummy/public/400.html
|
|
@@ -162,7 +159,7 @@ files:
|
|
|
162
159
|
homepage: https://github.com/substancelab/uchi
|
|
163
160
|
licenses: []
|
|
164
161
|
metadata:
|
|
165
|
-
homepage_uri: https://
|
|
162
|
+
homepage_uri: https://www.uchiadmin.com/
|
|
166
163
|
source_code_uri: https://github.com/substancelab/uchi
|
|
167
164
|
rdoc_options: []
|
|
168
165
|
require_paths:
|
|
@@ -180,5 +177,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
180
177
|
requirements: []
|
|
181
178
|
rubygems_version: 3.6.9
|
|
182
179
|
specification_version: 4
|
|
183
|
-
summary:
|
|
180
|
+
summary: Build usable and extensible admin panels for your Ruby on Rails application
|
|
181
|
+
in minutes.
|
|
184
182
|
test_files: []
|