futurism 1.2.0.pre9 → 1.2.0.pre10

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +24 -0
  3. data/Appraisals~ +24 -0
  4. data/CHANGELOG.md +370 -0
  5. data/Gemfile +15 -0
  6. data/Gemfile.lock +215 -0
  7. data/Gemfile~ +17 -0
  8. data/README.md +44 -9
  9. data/README.md~ +405 -0
  10. data/app/assets/javascripts/futurism.js +191 -0
  11. data/app/assets/javascripts/futurism.min.js +2 -0
  12. data/app/assets/javascripts/futurism.min.js.map +1 -0
  13. data/app/assets/javascripts/futurism.umd.js +188 -0
  14. data/app/assets/javascripts/futurism.umd.min.js +2 -0
  15. data/app/assets/javascripts/futurism.umd.min.js.map +1 -0
  16. data/{lib → app/channels}/futurism/channel.rb +1 -1
  17. data/bin/rails +25 -0
  18. data/bin/standardize +5 -0
  19. data/bin/test +5 -0
  20. data/futurism.gemspec +38 -0
  21. data/futurism.gemspec~ +30 -0
  22. data/lib/futurism/configuration.rb +21 -0
  23. data/lib/futurism/engine.rb +19 -0
  24. data/lib/futurism/helpers.rb +1 -1
  25. data/lib/futurism/importmap.rb +2 -0
  26. data/lib/futurism/version.rb +1 -1
  27. data/lib/futurism.rb +2 -1
  28. data/package.json +47 -0
  29. data/package.json~ +51 -0
  30. data/rollup.config.js +77 -0
  31. data/rollup.config.js~ +73 -0
  32. data/test/cable/channel_test.rb +319 -0
  33. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  34. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  35. data/test/dummy/app/controllers/application_controller.rb +2 -0
  36. data/test/dummy/app/controllers/home_controller.rb +6 -0
  37. data/test/dummy/app/controllers/posts_controller.rb +59 -0
  38. data/test/dummy/app/helpers/application_helper.rb +2 -0
  39. data/test/dummy/app/helpers/posts_helper.rb +2 -0
  40. data/test/dummy/app/jobs/application_job.rb +7 -0
  41. data/test/dummy/app/models/action_item.rb +2 -0
  42. data/test/dummy/app/models/application_record.rb +3 -0
  43. data/test/dummy/app/models/post.rb +2 -0
  44. data/test/dummy/config/application.rb +29 -0
  45. data/test/dummy/config/boot.rb +5 -0
  46. data/test/dummy/config/environment.rb +5 -0
  47. data/test/dummy/config/environments/development.rb +40 -0
  48. data/test/dummy/config/environments/production.rb +94 -0
  49. data/test/dummy/config/environments/test.rb +39 -0
  50. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  51. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  52. data/test/dummy/config/initializers/content_security_policy.rb +28 -0
  53. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  54. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  55. data/test/dummy/config/initializers/inflections.rb +16 -0
  56. data/test/dummy/config/initializers/mime_types.rb +4 -0
  57. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  58. data/test/dummy/config/puma.rb +38 -0
  59. data/test/dummy/config/routes.rb +12 -0
  60. data/test/dummy/config/spring.rb +6 -0
  61. data/test/dummy/db/migrate/20200711122838_create_posts.rb +9 -0
  62. data/test/dummy/db/migrate/2021042923813_create_action_items.rb +9 -0
  63. data/test/dummy/db/schema.rb +27 -0
  64. data/test/futurism_test.rb +28 -0
  65. data/test/helper/helper_test.rb +206 -0
  66. data/test/integration/navigation_test.rb +7 -0
  67. data/test/resolver/controller/renderer_test.rb +120 -0
  68. data/test/resolver/controller_test.rb +26 -0
  69. data/test/test_helper.rb +14 -0
  70. data/yarn.lock +3263 -0
  71. metadata +122 -18
  72. data/config/routes.rb +0 -2
  73. data/lib/futurism/channel.rb~ +0 -31
  74. data/lib/futurism/helpers.rb~ +0 -112
  75. data/lib/futurism/options_transformer.rb~ +0 -0
  76. data/lib/futurism/resolver/controller/renderer.rb~ +0 -76
  77. data/lib/futurism/resolver/controller.rb~ +0 -17
  78. data/lib/futurism/resolver/resources.rb~ +0 -101
  79. data/lib/futurism/version.rb~ +0 -3
  80. data/lib/futurism.rb~ +0 -30
  81. data/lib/tasks/futurism_tasks.rake +0 -39
  82. data/lib/tasks/futurism_tasks.rake~ +0 -39
data/lib/futurism.rb CHANGED
@@ -1,13 +1,14 @@
1
1
  require "rails"
2
+
2
3
  require "action_cable"
3
4
  require "cable_ready"
5
+ require "futurism/configuration"
4
6
  require "futurism/engine"
5
7
  require "futurism/message_verifier"
6
8
  require "futurism/options_transformer"
7
9
  require "futurism/resolver/resources"
8
10
  require "futurism/resolver/controller"
9
11
  require "futurism/resolver/controller/renderer"
10
- require "futurism/channel"
11
12
  require "futurism/helpers"
12
13
 
13
14
  module Futurism
data/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@stimulus_reflex/futurism",
3
+ "version": "1.2.0-pre10",
4
+ "description": "Lazy-load Rails partials via CableReady",
5
+ "main": "./dist/futurism.umd.min.js",
6
+ "module": "./dist/futurism.min.js",
7
+ "files": [
8
+ "dist/*",
9
+ "javascript/*"
10
+ ],
11
+ "scripts": {
12
+ "test": "yarn run mocha",
13
+ "lint": "yarn run prettier-standard:check",
14
+ "format": "yarn run prettier-standard:format",
15
+ "prettier-standard:check": "yarn run prettier-standard --check ./javascript/**/*.js rollup.config.js",
16
+ "prettier-standard:format": "yarn run prettier-standard ./javascript/**/*.js rollup.config.js",
17
+ "build": "yarn rollup -c",
18
+ "watch": "yarn rollup -wc"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/stimulusreflex/futurism.git"
23
+ },
24
+ "keywords": [
25
+ "cable_ready",
26
+ "lazy",
27
+ "loading"
28
+ ],
29
+ "author": "Julian Rubisch <julian.rubisch@hey.com>",
30
+ "license": "MIT",
31
+ "bugs": {
32
+ "url": "https://github.com/stimulusreflex/futurism/issues"
33
+ },
34
+ "homepage": "https://github.com/stimulusreflex/futurism#readme",
35
+ "dependencies": {
36
+ "cable_ready": "^5.0.0-pre9"
37
+ },
38
+ "devDependencies": {
39
+ "@rollup/plugin-commonjs": "^21.0.3",
40
+ "@rollup/plugin-json": "^4.1.0",
41
+ "@rollup/plugin-node-resolve": "^13.1.3",
42
+ "mocha": "^8.0.1",
43
+ "prettier-standard": "^16.4.1",
44
+ "rollup": "^2.70.1",
45
+ "rollup-plugin-terser": "^7.0.2"
46
+ }
47
+ }
data/package.json~ ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@stimulus_reflex/futurism",
3
+ "version": "1.2.0-pre10",
4
+ "description": "Lazy-load Rails partials via CableReady",
5
+ "main": "./dist/futurism.umd.min.js",
6
+ "module": "./dist/futurism.min.js",
7
+ "files": [
8
+ "dist/*",
9
+ "javascript/*"
10
+ ],
11
+ "scripts": {
12
+ "test": "yarn run mocha",
13
+ "lint": "yarn run prettier-standard:check",
14
+ "format": "yarn run prettier-standard:format",
15
+ "prettier-standard:check": "yarn run prettier-standard --check ./javascript/**/*.js rollup.config.js",
16
+ "prettier-standard:format": "yarn run prettier-standard ./javascript/**/*.js rollup.config.js",
17
+ "build": "yarn rollup -c",
18
+ "watch": "yarn rollup -wc"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/stimulusreflex/futurism.git"
23
+ },
24
+ "keywords": [
25
+ "cable_ready",
26
+ "lazy",
27
+ "loading"
28
+ ],
29
+ "author": "Julian Rubisch <julian.rubisch@hey.com>",
30
+ "license": "MIT",
31
+ "bugs": {
32
+ "url": "https://github.com/stimulusreflex/futurism/issues"
33
+ },
34
+ "homepage": "https://github.com/stimulusreflex/futurism#readme",
35
+ "dependencies": {
36
+ <<<<<<< Updated upstream
37
+ "cable_ready": "^5.0.0-pre9"
38
+ =======
39
+ "cable_ready": "5.0.0-pre9"
40
+ >>>>>>> Stashed changes
41
+ },
42
+ "devDependencies": {
43
+ "@rollup/plugin-commonjs": "^21.0.3",
44
+ "@rollup/plugin-json": "^4.1.0",
45
+ "@rollup/plugin-node-resolve": "^13.1.3",
46
+ "mocha": "^8.0.1",
47
+ "prettier-standard": "^16.4.1",
48
+ "rollup": "^2.70.1",
49
+ "rollup-plugin-terser": "^7.0.2"
50
+ }
51
+ }
data/rollup.config.js ADDED
@@ -0,0 +1,77 @@
1
+ import resolve from '@rollup/plugin-node-resolve'
2
+ import commonjs from '@rollup/plugin-commonjs'
3
+ import json from '@rollup/plugin-json'
4
+ import { terser } from 'rollup-plugin-terser'
5
+
6
+ const pretty = () => {
7
+ return terser({
8
+ mangle: false,
9
+ compress: false,
10
+ format: {
11
+ beautify: true,
12
+ indent_level: 2
13
+ }
14
+ })
15
+ }
16
+
17
+ const minify = () => {
18
+ return terser({
19
+ mangle: true,
20
+ compress: true
21
+ })
22
+ }
23
+
24
+ const esConfig = {
25
+ format: 'es',
26
+ inlineDynamicImports: true
27
+ }
28
+
29
+ const umdConfig = {
30
+ name: 'Futurism',
31
+ format: 'umd',
32
+ exports: 'named',
33
+ globals: {
34
+ cable_ready: 'CableReady'
35
+ }
36
+ }
37
+
38
+ const distFolders = ['dist/', 'app/assets/javascripts/']
39
+
40
+ const output = distFolders
41
+ .map(distFolder => [
42
+ {
43
+ ...esConfig,
44
+ file: `${distFolder}/futurism.js`,
45
+ plugins: [pretty()]
46
+ },
47
+ {
48
+ ...esConfig,
49
+ file: `${distFolder}/futurism.min.js`,
50
+ sourcemap: true,
51
+ plugins: [minify()]
52
+ },
53
+ {
54
+ ...umdConfig,
55
+ file: `${distFolder}/futurism.umd.js`,
56
+ plugins: [pretty()]
57
+ },
58
+ {
59
+ ...umdConfig,
60
+ file: `${distFolder}/futurism.umd.min.js`,
61
+ sourcemap: true,
62
+ plugins: [minify()]
63
+ }
64
+ ])
65
+ .flat()
66
+
67
+ export default [
68
+ {
69
+ external: ['cable_ready'],
70
+ input: 'javascript/index.js',
71
+ output,
72
+ plugins: [commonjs(), resolve(), json()],
73
+ watch: {
74
+ include: 'javascript/**'
75
+ }
76
+ }
77
+ ]
data/rollup.config.js~ ADDED
@@ -0,0 +1,73 @@
1
+ import resolve from '@rollup/plugin-node-resolve'
2
+ import commonjs from '@rollup/plugin-commonjs'
3
+ import json from '@rollup/plugin-json'
4
+ import { terser } from 'rollup-plugin-terser'
5
+
6
+ const pretty = () => {
7
+ return terser({
8
+ mangle: false,
9
+ compress: false,
10
+ format: {
11
+ beautify: true,
12
+ indent_level: 2
13
+ }
14
+ })
15
+ }
16
+
17
+ const minify = () => {
18
+ return terser({
19
+ mangle: true,
20
+ compress: true
21
+ })
22
+ }
23
+
24
+ const esConfig = {
25
+ format: 'es',
26
+ inlineDynamicImports: true
27
+ }
28
+
29
+ const umdConfig = {
30
+ name: 'Futurism',
31
+ format: 'umd',
32
+ exports: 'named'
33
+ }
34
+
35
+ const distFolders = ['dist/', 'app/assets/javascripts/']
36
+
37
+ const output = distFolders
38
+ .map(distFolder => [
39
+ {
40
+ ...esConfig,
41
+ file: `${distFolder}/futurism.js`,
42
+ plugins: [pretty()]
43
+ },
44
+ {
45
+ ...esConfig,
46
+ file: `${distFolder}/futurism.min.js`,
47
+ sourcemap: true,
48
+ plugins: [minify()]
49
+ },
50
+ {
51
+ ...umdConfig,
52
+ file: `${distFolder}/futurism.umd.js`,
53
+ plugins: [pretty()]
54
+ },
55
+ {
56
+ ...umdConfig,
57
+ file: `${distFolder}/futurism.umd.min.js`,
58
+ sourcemap: true,
59
+ plugins: [minify()]
60
+ }
61
+ ])
62
+ .flat()
63
+
64
+ export default [
65
+ {
66
+ input: 'javascript/index.js',
67
+ output,
68
+ plugins: [commonjs(), resolve(), json()],
69
+ watch: {
70
+ include: 'javascript/**'
71
+ }
72
+ }
73
+ ]
@@ -0,0 +1,319 @@
1
+ require "test_helper"
2
+
3
+ def with_mocked_renderer
4
+ renderer = Minitest::Mock.new
5
+
6
+ Futurism::Resolver::Controller::Renderer.stub(:for, renderer) do
7
+ yield(renderer)
8
+ end
9
+ end
10
+
11
+ def with_mocked_cable_ready
12
+ cable_ready_mock = MiniTest::Mock.new
13
+ cable_ready_channel = MiniTest::Mock.new
14
+ cable_ready_channel.expect(:outer_html, nil, [Hash])
15
+ cable_ready_channel.expect(:outer_html, nil, [Hash])
16
+
17
+ cable_ready_mock.expect(:[], cable_ready_channel, ["1"])
18
+ cable_ready_mock.expect(:[], cable_ready_channel, ["1"])
19
+ cable_ready_mock.expect(:broadcast, nil)
20
+ cable_ready_mock.expect(:broadcast, nil)
21
+ cable_ready_mock.expect(:broadcast, nil)
22
+
23
+ CableReady::Broadcaster.alias_method(:orig_cable_ready, :cable_ready)
24
+
25
+ CableReady::Broadcaster.define_method(:cable_ready) do
26
+ cable_ready_mock
27
+ end
28
+
29
+ yield cable_ready_mock
30
+
31
+ CableReady::Broadcaster.undef_method(:cable_ready)
32
+
33
+ CableReady::Broadcaster.alias_method(:cable_ready, :orig_cable_ready)
34
+ end
35
+
36
+ class Futurism::ChannelTest < ActionCable::Channel::TestCase
37
+ include Futurism::Helpers
38
+ include ActionView::Helpers
39
+ include ActionView::Context
40
+ include CableReady::Broadcaster
41
+
42
+ setup do
43
+ stub_connection(env: {"SCRIPT_NAME" => "/cable"}, identifiers: [:current_user], current_user: Struct.new(:id)[1])
44
+ end
45
+
46
+ test "subscribed" do
47
+ subscribe(channel: "Futurism::Channel")
48
+
49
+ assert subscription.confirmed?
50
+
51
+ assert_has_stream "Futurism::Channel:1"
52
+ end
53
+
54
+ test "broadcasts a rendered model after receiving signed params" do
55
+ with_mocked_renderer do |mock_renderer|
56
+ post = Post.create title: "Lorem"
57
+ fragment = Nokogiri::HTML.fragment(futurize(post) {})
58
+ signed_params_array = fragment.children.map { |element| element["data-signed-params"] }
59
+ sgids = fragment.children.map { |element| element["data-sgid"] }
60
+ subscribe
61
+
62
+ mock_renderer.expect :render, "<tag></tag>", [post]
63
+
64
+ perform :receive, {"signed_params" => signed_params_array, "sgids" => sgids}
65
+
66
+ assert_mock mock_renderer
67
+ end
68
+ end
69
+
70
+ test "broadcasts an ActiveRecord::Relation" do
71
+ with_mocked_renderer do |mock_renderer|
72
+ post1 = Post.create(title: "Lorem")
73
+ post2 = Post.create(title: "Ipsum")
74
+
75
+ fragment = Nokogiri::HTML.fragment(futurize(Post.all) {})
76
+ signed_params_array = fragment.children.map { |element| element["data-signed-params"] }
77
+ sgids = fragment.children.map { |element| element["data-sgid"] }
78
+ subscribe
79
+
80
+ mock_renderer
81
+ .expect(:render, "<tag></tag>", [post1])
82
+ .expect :render, "<tag></tag>", [post2]
83
+
84
+ perform :receive, {"signed_params" => signed_params_array, "sgids" => sgids}
85
+
86
+ assert_mock mock_renderer
87
+ end
88
+ end
89
+
90
+ test "broadcasts a rendered partial after receiving signed params" do
91
+ with_mocked_renderer do |mock_renderer|
92
+ post = Post.create title: "Lorem"
93
+ fragment = Nokogiri::HTML.fragment(futurize(partial: "posts/card", locals: {post: post}) {})
94
+ signed_params = fragment.children.first["data-signed-params"]
95
+ subscribe
96
+
97
+ mock_renderer
98
+ .expect(:render, "<tag></tag>", [partial: "posts/card", locals: {post: post}])
99
+
100
+ perform :receive, {"signed_params" => [signed_params]}
101
+
102
+ assert_mock mock_renderer
103
+ end
104
+ end
105
+
106
+ test "broadcasts a rendered partial after receiving the shorthand syntax" do
107
+ with_mocked_renderer do |mock_renderer|
108
+ post = Post.create title: "Lorem"
109
+ fragment = Nokogiri::HTML.fragment(futurize("posts/card", post: post) {})
110
+ signed_params = fragment.children.first["data-signed-params"]
111
+ subscribe
112
+
113
+ mock_renderer.expect(:render, "<tag></tag>", [partial: "posts/card", locals: {post: post}])
114
+ perform :receive, {"signed_params" => [signed_params]}
115
+
116
+ assert_mock mock_renderer
117
+ end
118
+ end
119
+
120
+ test "broadcasts a rendered partial after receiving the shorthand syntax with html options" do
121
+ with_mocked_renderer do |mock_renderer|
122
+ post = Post.create title: "Lorem"
123
+ fragment = Nokogiri::HTML.fragment(futurize("posts/card", post: post, html_options: {style: "color: green"}) {})
124
+ signed_params = fragment.children.first["data-signed-params"]
125
+ subscribe
126
+
127
+ mock_renderer.expect(:render, "<tag></tag>", [partial: "posts/card", locals: {post: post}])
128
+
129
+ perform :receive, {"signed_params" => [signed_params]}
130
+
131
+ assert_mock mock_renderer
132
+ end
133
+ end
134
+
135
+ test "broadcasts a collection" do
136
+ with_mocked_renderer do |mock_renderer|
137
+ Post.create title: "Lorem"
138
+ Post.create title: "Ipsum"
139
+ fragment = Nokogiri::HTML.fragment(futurize(partial: "posts/card", collection: Post.all, locals: {important_local: "needed to render"}) {})
140
+ subscribe
141
+
142
+ mock_renderer
143
+ .expect(:render, "<tag></tag>", [partial: "posts/card", locals: {post: Post.first, important_local: "needed to render", post_counter: 0}])
144
+ .expect(:render, "<tag></tag>", [partial: "posts/card", locals: {post: Post.last, important_local: "needed to render", post_counter: 1}])
145
+
146
+ signed_params = fragment.children.first["data-signed-params"]
147
+ perform :receive, {"signed_params" => [signed_params]}
148
+
149
+ signed_params = fragment.children.last["data-signed-params"]
150
+ perform :receive, {"signed_params" => [signed_params]}
151
+
152
+ assert_mock mock_renderer
153
+ end
154
+ end
155
+
156
+ test "broadcasts a collection (with multi-word class)" do
157
+ with_mocked_renderer do |mock_renderer|
158
+ ActionItem.create description: "Do this"
159
+ ActionItem.create description: "Do that"
160
+ fragment = Nokogiri::HTML.fragment(futurize(partial: "posts/card", collection: ActionItem.all, locals: {important_local: "needed to render"}) {})
161
+
162
+ subscribe
163
+
164
+ mock_renderer
165
+ .expect(:render, "<tag></tag>", [partial: "posts/card", locals: {action_item: ActionItem.first, important_local: "needed to render", action_item_counter: 0}])
166
+ .expect(:render, "<tag></tag>", [partial: "posts/card", locals: {action_item: ActionItem.last, important_local: "needed to render", action_item_counter: 1}])
167
+
168
+ signed_params = fragment.children.first["data-signed-params"]
169
+ perform :receive, {"signed_params" => [signed_params]}
170
+
171
+ signed_params = fragment.children.last["data-signed-params"]
172
+ perform :receive, {"signed_params" => [signed_params]}
173
+
174
+ assert_mock mock_renderer
175
+ end
176
+ end
177
+
178
+ test "broadcasts a collection with :as" do
179
+ with_mocked_renderer do |mock_renderer|
180
+ Post.create title: "Lorem"
181
+ Post.create title: "Ipsum"
182
+ fragment = Nokogiri::HTML.fragment(futurize(partial: "posts/card", collection: Post.all, as: :post_item) {})
183
+ subscribe
184
+
185
+ mock_renderer.expect(:render, "<tag></tag>", [partial: "posts/card", locals: {post_item: Post.first, post_item_counter: 0}])
186
+ signed_params = fragment.children.first["data-signed-params"]
187
+ perform :receive, {"signed_params" => [signed_params]}
188
+
189
+ mock_renderer.expect(:render, "<tag></tag>", [partial: "posts/card", locals: {post_item: Post.last, post_item_counter: 1}])
190
+ signed_params = fragment.children.last["data-signed-params"]
191
+ perform :receive, {"signed_params" => [signed_params]}
192
+
193
+ assert_mock mock_renderer
194
+ end
195
+ end
196
+
197
+ test "broadcasts elements of a collection immediately" do
198
+ with_mocked_cable_ready do |cable_ready_mock|
199
+ Post.create title: "Lorem"
200
+ Post.create title: "Ipsum"
201
+ fragment = Nokogiri::HTML.fragment(futurize(partial: "posts/card", collection: Post.all, broadcast_each: true, locals: {important_local: "needed to render"}) {})
202
+ subscribe
203
+
204
+ signed_params_1 = fragment.children.first["data-signed-params"]
205
+ broadcast_each_1 = fragment.children.first["data-broadcast-each"]
206
+ signed_params_2 = fragment.children.last["data-signed-params"]
207
+ broadcast_each_2 = fragment.children.last["data-broadcast-each"]
208
+ perform :receive, {"signed_params" => [signed_params_1, signed_params_2], "broadcast_each" => [broadcast_each_1, broadcast_each_2]}
209
+
210
+ assert_mock cable_ready_mock
211
+ end
212
+ end
213
+
214
+ test "broadcasts an inline rendered text" do
215
+ fragment = Nokogiri::HTML.fragment(futurize(inline: "<%= 1 + 2 %>") {})
216
+ signed_params = fragment.children.first["data-signed-params"]
217
+ subscribe(channel: "Futurism::Channel")
218
+
219
+ assert_cable_ready_operation_on("Futurism::Channel:1", operation: "outerHtml", selector: "[data-signed-params='#{signed_params}']", html: "3") do
220
+ perform :receive, {"signed_params" => [signed_params]}
221
+ end
222
+ end
223
+
224
+ test "broadcasts a correctly formed path" do
225
+ post = Post.create title: "Lorem"
226
+ fragment = Nokogiri::HTML.fragment(futurize(partial: "posts/card", locals: {post: post}) {})
227
+ signed_params = fragment.children.first["data-signed-params"]
228
+ subscribe(channel: "Futurism::Channel")
229
+
230
+ assert_cable_ready_operation_on("Futurism::Channel:1", operation: "outerHtml",
231
+ selector: "[data-signed-params='#{signed_params}']",
232
+ html: "<div class=\"card\">\n Lorem\n <a href=\"/posts/1/edit\">Edit</a>\n</div>\n") do
233
+ perform :receive, {"signed_params" => [signed_params]}
234
+ end
235
+ end
236
+
237
+ test "passes parsed params to controller render" do
238
+ with_mocked_renderer do |mock_renderer|
239
+ post = Post.create title: "Lorem"
240
+ fragment = Nokogiri::HTML.fragment(futurize(post) {})
241
+ signed_params_array = fragment.children.map { |element| element["data-signed-params"] }
242
+ sgids = fragment.children.map { |element| element["data-sgid"] }
243
+ urls = Array.new(fragment.children.length, "http://www.example.org/route?param1=true&param2=1234")
244
+ subscribe
245
+
246
+ mock_renderer.expect(:render, "<tag></tag>", [post])
247
+
248
+ perform :receive, {"signed_params" => signed_params_array, "sgids" => sgids, "urls" => urls}
249
+
250
+ assert_mock mock_renderer
251
+ end
252
+ end
253
+
254
+ test "renders error message when rendering invalid partial error" do
255
+ fragment = Nokogiri::HTML.fragment(futurize(partial: "INVALID/PARTIAL") {})
256
+ signed_params = fragment.children.first["data-signed-params"]
257
+ subscribe(channel: "Futurism::Channel")
258
+
259
+ assert_cable_ready_operation_on("Futurism::Channel:1", operation: "outerHtml",
260
+ selector: "[data-signed-params='#{signed_params}']",
261
+ html: /Missing partial INVALID\/_PARTIAL/) do
262
+ perform :receive, {"signed_params" => [signed_params]}
263
+ end
264
+ end
265
+
266
+ test "renders error message when wrong variable name" do
267
+ Post.create title: "Lorem"
268
+ fragment = Nokogiri::HTML.fragment(futurize(partial: "posts/card", collection: Post.all, as: :wrong_variable_name) {})
269
+ signed_params = fragment.children.first["data-signed-params"]
270
+ subscribe(channel: "Futurism::Channel")
271
+
272
+ assert_cable_ready_operation_on("Futurism::Channel:1", operation: "outerHtml",
273
+ selector: "[data-signed-params='#{signed_params}']",
274
+ html: /undefined local variable or method/) do
275
+ perform :receive, {"signed_params" => [signed_params]}
276
+ end
277
+ end
278
+
279
+ def assert_cable_ready_operation_on(stream, operation:, selector:, html:, &block)
280
+ data = {
281
+ "cableReady" => true,
282
+ "operations" => [{
283
+ "selector" => selector,
284
+ "html" => html,
285
+ "operation" => operation
286
+ }]
287
+ }
288
+
289
+ old_messages = broadcasts(stream)
290
+ clear_messages(stream)
291
+
292
+ assert_nothing_raised(&block)
293
+
294
+ new_messages = broadcasts(stream)
295
+ clear_messages(stream)
296
+
297
+ # Restore all sent messages
298
+ (old_messages + new_messages).each { |m| pubsub_adapter.broadcast(stream, m) }
299
+
300
+ message = new_messages.find { |msg| cable_ready_match?(ActiveSupport::JSON.decode(msg), data) }
301
+
302
+ assert message, "No messages sent with #{data} to #{stream}"
303
+ end
304
+
305
+ def cable_ready_match?(message, matcher)
306
+ return true if message == matcher
307
+
308
+ first_matching_operation = ["operations", 0]
309
+
310
+ matcher_operation = matcher.dig(*first_matching_operation)
311
+ message_operation = message.dig(*first_matching_operation)
312
+
313
+ message.dig("cableReady") == true &&
314
+ (matcher_operation.dig("selector") === message_operation.dig("selector") ||
315
+ matcher_operation.dig("selector").match(message_operation.dig("selector"))) &&
316
+ (matcher_operation.dig("html") === message_operation.dig("html") ||
317
+ matcher_operation.dig("html").match(message_operation.dig("html")))
318
+ end
319
+ end
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Connection < ActionCable::Connection::Base
3
+ end
4
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,6 @@
1
+ class HomeController < ApplicationController
2
+ # GET /home
3
+ def index
4
+ head :no_content
5
+ end
6
+ end
@@ -0,0 +1,59 @@
1
+ class PostsController < ApplicationController
2
+ before_action :set_post, only: [:show, :edit, :update, :destroy]
3
+
4
+ # GET /posts
5
+ def index
6
+ @posts = Post.all
7
+ end
8
+
9
+ # GET /posts/1
10
+ def show
11
+ end
12
+
13
+ # GET /posts/new
14
+ def new
15
+ @post = Post.new
16
+ end
17
+
18
+ # GET /posts/1/edit
19
+ def edit
20
+ end
21
+
22
+ # POST /posts
23
+ def create
24
+ @post = Post.new(post_params)
25
+
26
+ if @post.save
27
+ redirect_to @post, notice: "Post was successfully created."
28
+ else
29
+ render :new
30
+ end
31
+ end
32
+
33
+ # PATCH/PUT /posts/1
34
+ def update
35
+ if @post.update(post_params)
36
+ redirect_to @post, notice: "Post was successfully updated."
37
+ else
38
+ render :edit
39
+ end
40
+ end
41
+
42
+ # DELETE /posts/1
43
+ def destroy
44
+ @post.destroy
45
+ redirect_to posts_url, notice: "Post was successfully destroyed."
46
+ end
47
+
48
+ private
49
+
50
+ # Use callbacks to share common setup or constraints between actions.
51
+ def set_post
52
+ @post = Post.find(params[:id])
53
+ end
54
+
55
+ # Only allow a trusted parameter "white list" through.
56
+ def post_params
57
+ params.require(:post).permit(:title)
58
+ end
59
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,2 @@
1
+ module PostsHelper
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
@@ -0,0 +1,2 @@
1
+ class ActionItem < ApplicationRecord
2
+ end