breezy 0.9.0 → 0.14.0

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 (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