uchi 0.1.0 → 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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +17 -0
  3. data/.github/workflows/build.yml +23 -0
  4. data/.github/workflows/lint.yml +30 -0
  5. data/CHANGELOG.md +29 -0
  6. data/docs/fields.md +82 -0
  7. data/docs/repositories.md +63 -0
  8. data/package.json +31 -0
  9. data/test/components/uchi/field/belongs_to_test.rb +134 -0
  10. data/test/components/uchi/field/blank_test.rb +119 -0
  11. data/test/components/uchi/field/boolean_test.rb +163 -0
  12. data/test/components/uchi/field/date_test.rb +163 -0
  13. data/test/components/uchi/field/date_time_test.rb +152 -0
  14. data/test/components/uchi/field/has_and_belongs_to_many_test.rb +144 -0
  15. data/test/components/uchi/field/has_many_test.rb +138 -0
  16. data/test/components/uchi/field/id_test.rb +113 -0
  17. data/test/components/uchi/field/number_test.rb +163 -0
  18. data/test/components/uchi/field/string_test.rb +159 -0
  19. data/test/components/uchi/field/text_test.rb +160 -0
  20. data/test/components/uchi/ui/form/input/collection_checkboxes_test.rb +171 -0
  21. data/test/controllers/uchi/authors_controller_test.rb +120 -0
  22. data/test/controllers/uchi/repository_controller_test.rb +93 -0
  23. data/test/controllers/uchi/scoped_repository_controller_test.rb +73 -0
  24. data/test/dummy/Rakefile +6 -0
  25. data/test/dummy/app/assets/images/.keep +0 -0
  26. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  27. data/test/dummy/app/controllers/application_controller.rb +4 -0
  28. data/test/dummy/app/controllers/concerns/.keep +0 -0
  29. data/test/dummy/app/controllers/uchi/authors_controller.rb +7 -0
  30. data/test/dummy/app/controllers/uchi/books_controller.rb +7 -0
  31. data/test/dummy/app/controllers/uchi/titles_controller.rb +7 -0
  32. data/test/dummy/app/helpers/application_helper.rb +2 -0
  33. data/test/dummy/app/jobs/application_job.rb +7 -0
  34. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  35. data/test/dummy/app/models/application_record.rb +3 -0
  36. data/test/dummy/app/models/author.rb +5 -0
  37. data/test/dummy/app/models/book.rb +4 -0
  38. data/test/dummy/app/models/concerns/.keep +0 -0
  39. data/test/dummy/app/models/title.rb +3 -0
  40. data/test/dummy/app/uchi/repositories/author.rb +20 -0
  41. data/test/dummy/app/uchi/repositories/book.rb +16 -0
  42. data/test/dummy/app/uchi/repositories/title.rb +17 -0
  43. data/test/dummy/app/views/layouts/application.html.erb +27 -0
  44. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  45. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  46. data/test/dummy/app/views/pwa/manifest.json.erb +22 -0
  47. data/test/dummy/app/views/pwa/service-worker.js +26 -0
  48. data/test/dummy/bin/dev +2 -0
  49. data/test/dummy/bin/rails +4 -0
  50. data/test/dummy/bin/rake +4 -0
  51. data/test/dummy/bin/setup +34 -0
  52. data/test/dummy/config/application.rb +29 -0
  53. data/test/dummy/config/boot.rb +5 -0
  54. data/test/dummy/config/cable.yml +10 -0
  55. data/test/dummy/config/database.yml +32 -0
  56. data/test/dummy/config/environment.rb +5 -0
  57. data/test/dummy/config/environments/development.rb +69 -0
  58. data/test/dummy/config/environments/production.rb +89 -0
  59. data/test/dummy/config/environments/test.rb +53 -0
  60. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  61. data/test/dummy/config/initializers/filter_parameter_logging.rb +8 -0
  62. data/test/dummy/config/initializers/inflections.rb +16 -0
  63. data/test/dummy/config/locales/da.yml +52 -0
  64. data/test/dummy/config/locales/en.yml +31 -0
  65. data/test/dummy/config/puma.rb +38 -0
  66. data/test/dummy/config/routes.rb +9 -0
  67. data/test/dummy/config/storage.yml +34 -0
  68. data/test/dummy/config.ru +6 -0
  69. data/test/dummy/db/migrate/20251002183635_create_authors.rb +11 -0
  70. data/test/dummy/db/migrate/20251005131726_create_books.rb +9 -0
  71. data/test/dummy/db/migrate/20251005131811_create_titles.rb +11 -0
  72. data/test/dummy/db/migrate/20251031140958_add_author_books_join_table.rb +9 -0
  73. data/test/dummy/db/schema.rb +44 -0
  74. data/test/dummy/log/.keep +0 -0
  75. data/test/dummy/public/400.html +114 -0
  76. data/test/dummy/public/404.html +114 -0
  77. data/test/dummy/public/406-unsupported-browser.html +114 -0
  78. data/test/dummy/public/422.html +114 -0
  79. data/test/dummy/public/500.html +114 -0
  80. data/test/dummy/public/icon.png +0 -0
  81. data/test/dummy/public/icon.svg +3 -0
  82. data/test/dummy/storage/.keep +0 -0
  83. data/test/dummy/test/fixtures/authors.yml +11 -0
  84. data/test/dummy/test/models/author_test.rb +7 -0
  85. data/test/dummy/tmp/.keep +0 -0
  86. data/test/dummy/tmp/pids/.keep +0 -0
  87. data/test/dummy/tmp/storage/.keep +0 -0
  88. data/test/test_helper.rb +15 -0
  89. data/test/uchi/field_test.rb +77 -0
  90. data/test/uchi/i18n_test.rb +18 -0
  91. data/test/uchi/repository/routes_test.rb +49 -0
  92. data/test/uchi/repository/translate_test.rb +272 -0
  93. data/test/uchi/repository_test.rb +137 -0
  94. data/test/uchi/sort_order_test.rb +47 -0
  95. data/test/uchi_test.rb +7 -0
  96. metadata +147 -10
  97. data/README.md +0 -35
  98. data/Rakefile +0 -6
  99. data/lib/uchi/version.rb +0 -5
  100. data/lib/uchi.rb +0 -8
@@ -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
@@ -0,0 +1,120 @@
1
+ require "test_helper"
2
+
3
+ module Uchi
4
+ class AuthorsControllerTest < ActionDispatch::IntegrationTest
5
+ setup do
6
+ @author = Author.create!(name: "Test Author")
7
+ end
8
+
9
+ test "GET edit renders successfully" do
10
+ get edit_uchi_author_url(id: @author.id)
11
+ assert_response :success
12
+ end
13
+
14
+ test "GET edit renders the edit view" do
15
+ get edit_uchi_author_url(id: @author.id)
16
+ assert_template :edit
17
+ end
18
+
19
+ test "GET index responds successfully" do
20
+ get uchi_authors_url
21
+ assert_response :success
22
+ end
23
+
24
+ test "GET index renders the index view" do
25
+ get uchi_authors_url
26
+ assert_template :index
27
+ end
28
+
29
+ test "GET new renders successfully" do
30
+ get new_uchi_author_url
31
+ assert_response :success
32
+ end
33
+
34
+ test "GET new renders the new view" do
35
+ get new_uchi_author_url
36
+ assert_template :new
37
+ end
38
+
39
+ test "GET show responds successfully" do
40
+ get uchi_author_url(id: @author.id)
41
+ assert_response :success
42
+ end
43
+
44
+ test "GET show renders the show view" do
45
+ get uchi_author_url(id: @author.id)
46
+ assert_template :show
47
+ end
48
+
49
+ test "PATCH update redirects to show after successful update" do
50
+ srand(42)
51
+ patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
52
+ assert_redirected_to uchi_author_url(id: @author.id, uniq: 0.3745401188473625)
53
+ end
54
+
55
+ test "PATCH update responds with 303 after successful update" do
56
+ patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
57
+ assert_response :see_other
58
+ end
59
+
60
+ test "PATCH update changes the author's attributes" do
61
+ patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
62
+ @author.reload
63
+ assert_equal "Updated Name", @author.name
64
+ end
65
+
66
+ test "PATCH update flashes a translated success message after successful update" do
67
+ ::I18n.with_locale(:da) do
68
+ patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
69
+ assert_equal "Dine ændringer til forfatteren blev gemt", flash[:success]
70
+ end
71
+ end
72
+
73
+ test "PATCH update falls back to default success message after successful update" do
74
+ patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
75
+ assert_equal "Your changes have been saved", flash[:success]
76
+ end
77
+
78
+ test "PATCH update rerenders the edit view after unsuccessful update" do
79
+ patch uchi_author_url(id: @author.id), params: {author: {name: ""}}
80
+ assert_template :edit
81
+ end
82
+
83
+ test "PATCH responds with 422 after unsuccessful update" do
84
+ patch uchi_author_url(id: @author.id), params: {author: {name: ""}}
85
+ assert_response :unprocessable_entity
86
+ end
87
+
88
+ test "POST create redirects to show after successful creation" do
89
+ post uchi_authors_url, params: {author: {name: "New Author"}}
90
+ assert_redirected_to uchi_author_url(id: Author.last.id)
91
+ end
92
+
93
+ test "POST create responds with 303 after successful creation" do
94
+ post uchi_authors_url, params: {author: {name: "New Author"}}
95
+ assert_response :see_other
96
+ end
97
+
98
+ test "POST create flashes a translated success message after successful creation" do
99
+ ::I18n.with_locale(:da) do
100
+ post uchi_authors_url, params: {author: {name: "New Author"}}
101
+ assert_equal "Forfatteren er blevet tilføjet", flash[:success]
102
+ end
103
+ end
104
+
105
+ test "POST create creates a new author" do
106
+ post uchi_authors_url, params: {author: {name: "New Author"}}
107
+ assert_equal "New Author", Author.last.name
108
+ end
109
+
110
+ test "POST create rerenders the new view after unsuccessful creation" do
111
+ post uchi_authors_url, params: {author: {name: ""}}
112
+ assert_template :new
113
+ end
114
+
115
+ test "POST create responds with 422 after unsuccessful creation" do
116
+ post uchi_authors_url, params: {author: {name: ""}}
117
+ assert_response :unprocessable_entity
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,93 @@
1
+ require "test_helper"
2
+
3
+ module Uchi
4
+ class RepositoryControllerTest < ActionDispatch::IntegrationTest
5
+ setup do
6
+ @book = Book.create!(original_title: "The Hobbit")
7
+ end
8
+
9
+ test "DELETE destroy deletes the record" do
10
+ delete uchi_book_url(id: @book.id)
11
+
12
+ assert_not Book.exists?(@book.id)
13
+ end
14
+
15
+ test "DELETE destroy redirects to index" do
16
+ delete uchi_book_url(id: @book.id)
17
+
18
+ assert_redirected_to uchi_books_url
19
+ end
20
+
21
+ test "GET edit responds successfully" do
22
+ get edit_uchi_book_url(id: @book.id)
23
+ assert_response :success
24
+ end
25
+
26
+ test "GET edit renders a form for updating the model" do
27
+ get edit_uchi_book_url(id: @book.id)
28
+ assert_select "form[action=?][method='post']", uchi_book_path(id: @book.id)
29
+ end
30
+
31
+ test "GET edit links back to the model" do
32
+ get edit_uchi_book_url(id: @book.id)
33
+ assert_select "a[href=?]", uchi_book_path(id: @book.id), text: "Cancel"
34
+ end
35
+
36
+ test "GET index links to show for each record" do
37
+ get uchi_books_url
38
+
39
+ assert_select "tr td a[data-turbo-frame='_top'][href=?]", uchi_book_path(id: @book.id)
40
+ end
41
+
42
+ test "GET index links to edit for each record" do
43
+ get uchi_books_url
44
+ assert_select "tr td a[data-turbo-frame='_top'][href=?]", edit_uchi_book_path(id: @book.id)
45
+ end
46
+
47
+ test "GET new responds successfully" do
48
+ get new_uchi_book_url
49
+ assert_response :success
50
+ end
51
+
52
+ test "GET new renders a form to create a new model" do
53
+ get new_uchi_book_url
54
+ assert_select "form[action=?][method='post']", uchi_books_path
55
+ end
56
+
57
+ test "GET new links back to the index" do
58
+ get new_uchi_book_url
59
+ assert_select "a[href=?]", uchi_books_path, text: "Cancel"
60
+ end
61
+
62
+ test "GET show responds successfully" do
63
+ get uchi_book_url(id: @book.id)
64
+ assert_response :success
65
+ end
66
+
67
+ test "GET show renders the show view" do
68
+ get uchi_book_url(id: @book.id)
69
+ assert_template :show
70
+ end
71
+
72
+ test "GET show includes a button to delete the record" do
73
+ get uchi_book_url(id: @book.id)
74
+ assert_select "form[action=?]", uchi_book_path(id: @book.id), text: "Delete"
75
+ end
76
+
77
+ test "GET show includes a turbo-frame that loads the titles in a scoped index view" do
78
+ get uchi_book_url(id: @book.id)
79
+
80
+ assert_select "turbo-frame[src]" do |node_set|
81
+ tag = node_set.first
82
+ src = tag.attributes["src"].value
83
+ expected_src = uchi_titles_path(scope: {
84
+ model: "Book",
85
+ id: @book.id,
86
+ field: "titles",
87
+ inverse_of: "book"
88
+ })
89
+ assert_equal expected_src, src
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,73 @@
1
+ require "test_helper"
2
+
3
+ module Uchi
4
+ class ScopedRepositoryControllerTest < ActionDispatch::IntegrationTest
5
+ setup do
6
+ @book = Book.create!(original_title: "The Hobbit")
7
+ @dk_title = Title.create!(book: @book, locale: "da-DK", title: "Hobbitten")
8
+ @de_title = Title.create!(locale: "de-DE", title: "Der Hobbit", book: @book)
9
+
10
+ @scope = {model: "Book", id: @book.id, field: "titles"}
11
+ end
12
+
13
+ test "GET edit responds successfully" do
14
+ get edit_uchi_title_url(id: @dk_title.id, scope: @scope)
15
+ assert_response :success
16
+ end
17
+
18
+ test "GET edit renders a form for updating the model" do
19
+ get edit_uchi_title_url(id: @dk_title.id, scope: @scope)
20
+ assert_select "form[action=?][method='post']", uchi_title_path(id: @dk_title.id)
21
+ end
22
+
23
+ test "GET edit links back to the scoped model" do
24
+ get edit_uchi_title_url(id: @dk_title.id, scope: @scope)
25
+ assert_select "a[href=?]", uchi_book_path(id: @book.id), text: "Cancel"
26
+ end
27
+
28
+ test "GET new responds successfully" do
29
+ get new_uchi_title_url(scope: @scope)
30
+ assert_response :success
31
+ end
32
+
33
+ test "GET new renders a form to create a new model" do
34
+ get new_uchi_title_url(scope: @scope)
35
+ assert_select "form[action=?][method='post']", uchi_titles_path
36
+ end
37
+
38
+ test "GET new links back to the scoped model" do
39
+ get new_uchi_title_url(scope: @scope)
40
+ assert_select "a[href=?]", uchi_book_path(id: @book.id), text: "Cancel"
41
+ end
42
+
43
+ test "GET index responds successfully" do
44
+ get uchi_titles_url(scope: @scope)
45
+ assert_response :success
46
+ end
47
+
48
+ test "GET index renders the index view" do
49
+ get uchi_titles_url(scope: @scope)
50
+ assert_template :index
51
+ end
52
+
53
+ test "GET index includes a turbo-frame" do
54
+ get uchi_titles_url(scope: @scope)
55
+ assert_select "turbo-frame"
56
+ end
57
+
58
+ test "GET index lists only the records associated with the parent record" do
59
+ other_book = Book.create!(original_title: "1984")
60
+ Title.create!(locale: "de-DE", title: "1984", book: other_book)
61
+
62
+ get uchi_titles_url(scope: @scope.merge(inverse_of: "book"))
63
+
64
+ assert_equal assigns(:records), [@dk_title, @de_title]
65
+ end
66
+
67
+ test "GET index does not include the field for the parent record" do
68
+ get uchi_titles_url(scope: @scope.merge(inverse_of: "book"))
69
+
70
+ assert_not assigns(:columns).find { |column| column.name == :book }
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative "config/application"
5
+
6
+ Rails.application.load_tasks
File without changes
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
3
+ allow_browser versions: :modern
4
+ end
File without changes
@@ -0,0 +1,7 @@
1
+ module Uchi
2
+ class AuthorsController < Uchi::RepositoryController
3
+ def repository_class
4
+ Uchi::Repositories::Author
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Uchi
2
+ class BooksController < Uchi::RepositoryController
3
+ def repository_class
4
+ Uchi::Repositories::Book
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Uchi
2
+ class TitlesController < Uchi::RepositoryController
3
+ def repository_class
4
+ Uchi::Repositories::Title
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,7 @@
1
+ class ApplicationJob < ActiveJob::Base
2
+ # Automatically retry jobs that encountered a deadlock
3
+ # retry_on ActiveRecord::Deadlocked
4
+
5
+ # Most jobs are safe to ignore if the underlying records are no longer available
6
+ # discard_on ActiveJob::DeserializationError
7
+ end