breezy 0.9.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/lib/breezy.rb +10 -15
  3. data/lib/breezy/helpers.rb +541 -11
  4. data/lib/breezy/redirection.rb +30 -0
  5. data/lib/breezy/xhr_headers.rb +0 -10
  6. data/lib/generators/rails/breezy_generator.rb +13 -4
  7. data/lib/generators/rails/templates/_form.json.props +13 -0
  8. data/lib/generators/rails/templates/controller.rb.tt +0 -3
  9. data/lib/generators/rails/templates/edit.json.props +14 -0
  10. data/lib/generators/rails/templates/index.json.props +13 -0
  11. data/lib/generators/rails/templates/new.json.props +15 -0
  12. data/lib/generators/rails/templates/{show.js.props → show.json.props} +0 -2
  13. data/lib/generators/rails/templates/web/base.jsx +3 -4
  14. data/lib/generators/rails/templates/web/edit.html.erb +11 -0
  15. data/lib/generators/rails/templates/web/edit.jsx +22 -27
  16. data/lib/generators/rails/templates/web/index.html.erb +10 -0
  17. data/lib/generators/rails/templates/web/index.jsx +13 -9
  18. data/lib/generators/rails/templates/web/new.html.erb +11 -0
  19. data/lib/generators/rails/templates/web/new.jsx +19 -24
  20. data/lib/generators/rails/templates/web/show.html.erb +12 -0
  21. data/lib/generators/rails/templates/web/show.jsx +3 -3
  22. data/lib/install/templates/web/action_creators.js +14 -0
  23. data/lib/install/templates/web/actions.js +3 -0
  24. data/lib/install/templates/web/application.js +30 -5
  25. data/lib/install/templates/web/application.json.props +25 -0
  26. data/lib/install/templates/web/initializer.rb +1 -6
  27. data/lib/install/templates/web/reducer.js +28 -0
  28. data/lib/install/web.rb +27 -40
  29. data/lib/tasks/install.rake +11 -7
  30. data/test/helpers_test.rb +23 -0
  31. data/test/render_test.rb +25 -92
  32. data/test/test_helper.rb +2 -2
  33. metadata +32 -30
  34. data/app/views/breezy/response.html.erb +0 -0
  35. data/lib/breezy/configuration.rb +0 -35
  36. data/lib/breezy/render.rb +0 -37
  37. data/lib/generators/rails/templates/edit.js.props +0 -16
  38. data/lib/generators/rails/templates/index.js.props +0 -14
  39. data/lib/generators/rails/templates/new.js.props +0 -4
  40. data/lib/generators/rails/templates/web/form.jsx +0 -31
  41. data/lib/install/templates/web/babelrc +0 -33
  42. data/test/breezy_test.rb +0 -125
  43. data/test/configuration_test.rb +0 -36
@@ -0,0 +1,3 @@
1
+ // Example:
2
+ //
3
+ // export const CLEAR_FORM_ERRORS = 'CLEAR_FORM_ERRORS'
@@ -1,18 +1,21 @@
1
1
  import React from 'react'
2
2
  import {combineReducers, createStore, applyMiddleware, compose} from 'redux'
3
+ import reduceReducers from 'reduce-reducers'
3
4
  import thunk from 'redux-thunk'
4
5
  import { Provider } from 'react-redux'
5
6
  import { render } from 'react-dom'
6
- import createHistory from 'history/createBrowserHistory'
7
+ import { createBrowserHistory } from 'history'
7
8
  import Breezy from '@jho406/breezy'
8
9
  import Nav from '@jho406/breezy/dist/NavComponent'
10
+ import ujsHandlers from '@jho406/breezy/dist/utils/ujs'
11
+ import applicationReducer from './reducer'
9
12
 
10
13
  // Mapping between your props template to Component
11
14
  // e.g {'posts/new': PostNew}
12
- const screenToComponentMapping = {
15
+ const identifierToComponentMapping = {
13
16
  }
14
17
 
15
- const history = createHistory({})
18
+ const history = createBrowserHistory({})
16
19
  const initialPage = window.BREEZY_INITIAL_PAGE_STATE
17
20
  const baseUrl = ''
18
21
 
@@ -27,20 +30,30 @@ const {reducer, initialState, initialPageKey, connect} = Breezy.start({
27
30
 
28
31
  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
29
32
 
33
+ const {
34
+ breezy: breezyReducer,
35
+ pages: pagesReducer,
36
+ } = reducer
37
+
30
38
  const store = createStore(
31
39
  combineReducers({
32
- ...reducer,
40
+ breezy: breezyReducer,
41
+ pages: reduceReducers(pagesReducer, applicationReducer),
33
42
  }),
34
43
  initialState,
35
44
  composeEnhancers(applyMiddleware(thunk))
36
45
  )
37
46
 
47
+ const navigatorRef = React.createRef()
48
+
38
49
  connect(store)
39
50
 
40
51
  class App extends React.Component {
41
52
  render() {
42
53
  return <Provider store={store}>
43
54
  <Nav
55
+ store={store}
56
+ ref={navigatorRef}
44
57
  mapping={this.props.mapping}
45
58
  history={history}
46
59
  initialPageKey={initialPageKey}
@@ -50,5 +63,17 @@ class App extends React.Component {
50
63
  }
51
64
 
52
65
  document.addEventListener("DOMContentLoaded", function() {
53
- render(<App mapping={screenToComponentMapping}/>, document.getElementById('app'))
66
+ const appEl = document.getElementById('app')
67
+ if (appEl) {
68
+ const {onClick, onSubmit} = ujsHandlers({
69
+ navigatorRef,
70
+ store,
71
+ ujsAttributePrefix: 'data-bz'
72
+ })
73
+
74
+ appEl.addEventListener('click', onClick)
75
+ appEl.addEventListener('submit', onSubmit)
76
+
77
+ render(<App mapping={identifierToComponentMapping}/>, appEl)
78
+ }
54
79
  })
@@ -0,0 +1,25 @@
1
+ path = param_to_search_path(params[:bzq])
2
+
3
+ json.data(search: path) do
4
+ yield json
5
+ end
6
+
7
+ json.component_identifier local_assigns[:virtual_path_of_template]
8
+ json.defers json.deferred!
9
+ json.fragments json.fragments!
10
+ json.assets [
11
+ asset_pack_path('application.js'),
12
+ asset_path('application.css')
13
+ ]
14
+
15
+ if protect_against_forgery?
16
+ json.csrf_token form_authenticity_token
17
+ end
18
+
19
+ if path
20
+ json.action 'graft'
21
+ json.path search_path_to_camelized_param(path)
22
+ end
23
+
24
+ json.flash flash.to_h
25
+
@@ -1,6 +1 @@
1
- require 'breezy_template/core_ext'
2
-
3
- Breezy.configure do |config|
4
- config.track_sprockets_assets = ['application.js', 'application.css']
5
- config.track_pack_assets = ['application.js']
6
- end
1
+ require 'props_template/core_ext'
@@ -0,0 +1,28 @@
1
+ // Example:
2
+ //
3
+ // import {
4
+ // CLEAR_FORM_ERRORS
5
+ // } from './actions'
6
+ // import produce from "immer"
7
+ //
8
+ // export default function (state = {}, action) {
9
+ // switch(action.type) {
10
+ // case CLEAR_FORM_ERRORS: {
11
+ // const {pageKey} = action.payload
12
+ //
13
+ // return produce(state, draft => {
14
+ // const currentPage = draft[pageKey]
15
+ // delete currentPage.errors
16
+ // })
17
+ // }
18
+ // default:
19
+ // return state
20
+ // }
21
+ // }
22
+
23
+ export default function (state = {}, action) {
24
+ switch(action.type) {
25
+ default:
26
+ return state
27
+ }
28
+ }
@@ -1,14 +1,11 @@
1
1
  require "webpacker/configuration"
2
2
 
3
- babelrc = Rails.root.join(".babelrc")
3
+ babel_config = Rails.root.join("babel.config.js")
4
4
 
5
5
  def append_js_tags
6
6
  app_html = 'app/views/layouts/application.html.erb'
7
7
  js_tag = <<-JS_TAG
8
-
9
- <script type="text/javascript">
10
- window.BREEZY_INITIAL_PAGE_STATE=<%= breezy_snippet %>;
11
- </script>
8
+ <%= yield :initial_state %>
12
9
  JS_TAG
13
10
 
14
11
  inject_into_file app_html, after: '<head>' do
@@ -24,60 +21,49 @@ def add_member_methods
24
21
  inject_into_file "app/models/application_record.rb", after: "class ApplicationRecord < ActiveRecord::Base\n" do
25
22
  <<-RUBY
26
23
  def self.member_at(index)
27
- offset(index).limit(1)
24
+ offset(index).limit(1).first
28
25
  end
29
26
 
30
27
  def self.member_by(attr, value)
31
- find_by(Hash[attr, val])
28
+ find_by(Hash[attr, value])
32
29
  end
33
-
34
30
  RUBY
35
31
  end
36
32
  end
37
33
 
38
-
39
- if File.exist?(babelrc)
40
- react_babelrc = JSON.parse(File.read(babelrc))
41
- react_babelrc["presets"] ||= []
42
- react_babelrc["plugins"] ||= []
43
-
44
- if !react_babelrc["presets"].include?("react")
45
- react_babelrc["presets"].push("react")
46
- say "Copying react preset to your .babelrc file"
47
-
48
- File.open(babelrc, "w") do |f|
49
- f.puts JSON.pretty_generate(react_babelrc)
50
- end
51
- end
52
-
53
- if !react_babelrc["plugins"].any?{|plugin| Array(plugin).include?("module-resolver")}
54
- react_babelrc["plugins"].push(["module-resolver", {
34
+ say "Copying module-resolver preset to your babel.config.js"
35
+ resolver_snippet = <<~JAVASCRIPT
36
+ [
37
+ require('babel-plugin-module-resolver').default, {
55
38
  "root": ["./app"],
56
39
  "alias": {
57
40
  "views": "./app/views",
58
41
  "components": "./app/components",
59
- "javascripts": "./app/javascripts"
42
+ "javascript": "./app/javascript"
60
43
  }
61
- }])
44
+ }
45
+ ],
46
+ JAVASCRIPT
47
+ insert_into_file "babel.config.js", resolver_snippet, after: /plugins: \[\n/
62
48
 
63
- say "Copying module-resolver preset to your .babelrc file"
49
+ say "Copying application.js file to #{Webpacker.config.source_entry_path}"
50
+ copy_file "#{__dir__}/templates/web/application.js", "#{Webpacker.config.source_entry_path}/application.js"
64
51
 
65
- File.open(babelrc, "w") do |f|
66
- f.puts JSON.pretty_generate(react_babelrc)
67
- end
68
- end
52
+ say "Copying reducer.js file to #{Webpacker.config.source_entry_path}"
53
+ copy_file "#{__dir__}/templates/web/reducer.js", "#{Webpacker.config.source_entry_path}/reducer.js"
69
54
 
70
- else
71
- say "Copying .babelrc to app root directory"
72
- copy_file "#{__dir__}/templates/web/babelrc", ".babelrc"
73
- end
55
+ say "Copying action_creators.js file to #{Webpacker.config.source_entry_path}"
56
+ copy_file "#{__dir__}/templates/web/action_creators.js", "#{Webpacker.config.source_entry_path}/action_creators.js"
74
57
 
75
- say "Copying application.js file to #{Webpacker.config.source_entry_path}"
76
- copy_file "#{__dir__}/templates/web/application.js", "#{Webpacker.config.source_entry_path}/application.js"
58
+ say "Copying actions.js file to #{Webpacker.config.source_entry_path}"
59
+ copy_file "#{__dir__}/templates/web/actions.js", "#{Webpacker.config.source_entry_path}/actions.js"
77
60
 
78
61
  say "Copying Breezy initializer"
79
62
  copy_file "#{__dir__}/templates/web/initializer.rb", "config/initializers/breezy.rb"
80
63
 
64
+ say "Copying application.json.props"
65
+ copy_file "#{__dir__}/templates/web/application.json.props", "app/views/layouts/application.json.props"
66
+
81
67
  say "Appending js tags to your application.html.erb"
82
68
  append_js_tags
83
69
 
@@ -85,9 +71,10 @@ say "Adding required member methods to ApplicationRecord"
85
71
  add_member_methods
86
72
 
87
73
  say "Installing React, Redux, and Breezy"
88
- run "yarn add babel-plugin-module-resolver babel-preset-react formik history prop-types react-redux redux-thunk redux react react-dom @jho406/breezy --save"
74
+ run "yarn add babel-plugin-module-resolver babel-preset-react history prop-types react-redux redux-thunk redux reduce-reducers react react-dom immer @jho406/breezy --save"
89
75
 
90
- say "Updating webpack paths to include .jsx file extension"
76
+ say "Updating webpack config to include .jsx file extension and resolved_paths"
91
77
  insert_into_file Webpacker.config.config_path, " - .jsx\n", after: /extensions:\n/
78
+ insert_into_file Webpacker.config.config_path, "'app/views', 'app/components'", after: /resolved_paths: \[/
92
79
 
93
80
  say "Webpacker now supports breezy.js 🎉", :green
@@ -10,6 +10,16 @@ namespace :breezy do
10
10
  end
11
11
  end
12
12
 
13
+ desc "Verifies if any version of react is in package.json"
14
+ task :verify_react do
15
+ package_json = JSON.parse(File.read(Rails.root.join("package.json")))
16
+
17
+ if package_json['dependencies']['react'].nil?
18
+ $stderr.puts "React not installed. Did you run `rails webpacker:install:react`?"
19
+ $stderr.puts "Exiting!" && exit!
20
+ end
21
+ end
22
+
13
23
  desc "Verifies webpacker has been installed"
14
24
  task "verify_webpacker" do
15
25
  begin
@@ -23,16 +33,10 @@ namespace :breezy do
23
33
 
24
34
  namespace :install do
25
35
  desc "Install everything needed for breezy web"
26
- task 'web' => ["breezy:verify_webpacker", "webpacker:verify_install"] do
36
+ task 'web' => ["breezy:verify_webpacker", "webpacker:verify_install", "breezy:verify_react"] do
27
37
  template = File.expand_path("../install/web.rb", __dir__)
28
38
  exec "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{template}"
29
39
  end
30
-
31
- desc "Install everything needed for breezy mobile"
32
- task 'mobile' => ["breezy:verify_yarn"] do
33
- template = File.expand_path("../install/mobile.rb", __dir__)
34
- exec "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{template}"
35
- end
36
40
  end
37
41
  end
38
42
 
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ class HelpersTest < ActiveSupport::TestCase
4
+ include Breezy::Helpers
5
+
6
+ test 'clean_bzq returns nil if qry is nil' do
7
+ qry = nil
8
+
9
+ assert_nil param_to_search_path(qry)
10
+ end
11
+
12
+ test 'clean_bzq returns a refined qry' do
13
+ qry = 'foo...bar/?)()-'
14
+
15
+ assert_equal param_to_search_path(qry), ['foo', 'bar']
16
+ end
17
+
18
+ test 'camelize_path' do
19
+ path = ['foo_bar', 'foo_bar=1', 'foo_baz_roo']
20
+
21
+ assert_equal search_path_to_camelized_param(path), 'fooBar.fooBar=1.fooBazRoo'
22
+ end
23
+ end
@@ -4,14 +4,14 @@ class RenderController < TestController
4
4
  require 'action_view/testing/resolvers'
5
5
 
6
6
  append_view_path(ActionView::FixtureResolver.new(
7
- 'render/action.js.breezy' => 'json.author "john smith"',
8
- 'render/action.html.erb' => 'john smith',
9
- 'render/implied_render_with_breezy.js.breezy' => 'json.author "john smith"',
10
- 'render/implied_render_with_breezy.html.erb' => 'john smith',
7
+ 'render/simple_render_with_breezy.json.props' => 'json.author "john smith"',
8
+ 'render/simple_render_with_breezy_with_bad_layout.json.props' => 'json.author "john smith"',
9
+ 'layouts/application.json.props' => 'json.data {yield json}',
10
+ 'layouts/does_not_exist.html.erb' => '',
11
11
  'layouts/application.html.erb' => <<~HTML
12
12
  <html>
13
13
  <head>
14
- <script><%= breezy_snippet %></script>
14
+ <script><%= @initial_state.strip.html_safe %></script>
15
15
  </head>
16
16
  <body><%=yield%></body>
17
17
  </html>
@@ -20,21 +20,18 @@ class RenderController < TestController
20
20
 
21
21
  layout 'application'
22
22
 
23
- before_action :use_breezy, only: [:simple_render_with_breezy, :implied_render_with_breezy]
24
-
25
23
  def render_action
26
24
  render :action
27
25
  end
28
26
 
29
27
  def simple_render_with_breezy
30
- render :action
28
+ @initial_state = render_to_string(formats: [:json], layout: true)
29
+ render inline: '', layout: true
31
30
  end
32
31
 
33
- def implied_render_with_breezy
34
- end
35
-
36
- def render_action_with_breezy_false
37
- render :action
32
+ def simple_render_with_breezy_with_bad_layout
33
+ @initial_state = render_to_string(formats: [:json], layout: 'does_not_exist')
34
+ render inline: '', layout: true
38
35
  end
39
36
 
40
37
  def form_authenticity_token
@@ -47,102 +44,38 @@ class RenderTest < ActionController::TestCase
47
44
 
48
45
 
49
46
  setup do
50
- Breezy.configuration.track_sprockets_assets = ['app.js']
51
- Breezy.configuration.track_pack_assets = ['app.js']
52
- end
53
-
54
- teardown do
55
- Breezy.configuration.track_sprockets_assets = []
56
- Breezy.configuration.track_pack_assets = []
57
- end
58
-
59
- test "render action via get" do
60
- get :render_action
61
- assert_normal_render 'john smith'
47
+ if Rails.version >= '6'
48
+ # In rails 6, the fixture orders the templates based on their appearance in the handler
49
+ # This doesn't happen IRL, so I'm going to explicitly set the handler here.
50
+ #
51
+ # Note that the original is the following
52
+ # @controller.lookup_context.handlers = [:raw, :breezy, :erb, :js, :html, :builder, :ruby]
53
+ @controller.lookup_context.handlers = [:props, :erb]
54
+ end
62
55
  end
63
56
 
64
57
  test "simple render with breezy" do
65
58
  get :simple_render_with_breezy
66
- assert_breezy_html({author: "john smith"}, screen: 'render/action')
67
- end
68
-
69
- test "implied render with breezy" do
70
- get :implied_render_with_breezy
71
- assert_breezy_html({author: "john smith"}, screen: 'render/implied_render_with_breezy')
72
- end
73
-
74
- test "simple render with breezy via get js" do
75
- @request.accept = 'application/javascript'
76
- get :simple_render_with_breezy
77
- assert_breezy_js({author: "john smith"})
78
- end
79
-
80
- test "render action via xhr and get js" do
81
- @request.accept = 'application/javascript'
82
- get :simple_render_with_breezy, xhr: true
83
- assert_breezy_js({author: "john smith"})
84
- end
85
-
86
- test "render with breezy false" do
87
- get :render_action_with_breezy_false
88
- assert_normal_render("john smith")
89
- end
90
-
91
- test "render with breezy false via xhr get" do
92
- @request.accept = 'text/html'
93
- get :render_action_with_breezy_false, xhr: true
94
- assert_normal_render("john smith")
95
- end
96
-
97
- test "render action via xhr and put" do
98
- @request.accept = 'text/html'
99
- put :render_action, xhr: true
100
- assert_normal_render 'john smith'
101
- end
102
59
 
103
- private
104
-
105
- def assert_breezy_html(content, opts={})
106
60
  assert_response 200
107
-
108
61
  rendered = <<~HTML
109
62
  <html>
110
63
  <head>
111
- <script>(function(){var joints={};var cache={};var defers=[];return ({"data":#{content.to_json},"screen":"#{opts[:screen]}","csrf_token":"secret","assets":["/app.js"],"joints":joints,"defers":defers});})();</script>
64
+ <script>{"data":{"author":"john smith"}}</script>
112
65
  </head>
113
66
  <body></body>
114
67
  </html>
115
68
  HTML
116
69
 
117
70
  assert_equal rendered, @response.body
118
- assert_equal 'text/html', @response.content_type
119
- end
120
-
121
- def assert_breezy_js(content)
122
- assert_response 200
123
- assert_equal '(function(){var joints={};var cache={};var defers=[];return ({"data":' + content.to_json + ',"screen":"render/action","csrf_token":"secret","assets":["/app.js"],"joints":joints,"defers":defers});})()', @response.body
124
- assert_equal 'text/javascript', @response.content_type
125
- end
126
-
127
- def assert_breezy_replace_js(content)
128
- assert_response 200
129
- assert_equal 'Breezy.replace((function(){return ({"data":' + content.to_json + ',"csrf_token":"secret","assets":["/app.js"]});})());', @response.body
130
- assert_equal 'text/javascript', @response.content_type
71
+ assert_equal 'text/html', @response.media_type
131
72
  end
132
73
 
133
- def assert_normal_render(content)
134
- assert_response 200
135
-
136
- rendered = <<~HTML
137
- <html>
138
- <head>
139
- <script></script>
140
- </head>
141
- <body>#{content}</body>
142
- </html>
143
- HTML
74
+ test "simple render when the layout doesn't exist" do
75
+ err = assert_raise ActionView::MissingTemplate do |e|
76
+ get :simple_render_with_breezy_with_bad_layout
77
+ end
144
78
 
145
- assert_equal rendered, @response.body
146
- assert_equal 'text/html', @response.content_type
79
+ assert_equal(true, err.message.starts_with?('Missing template layouts/does_not_exist with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:props, :erb]}.'))
147
80
  end
148
81
  end