breezy 0.9.0 → 0.10.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4bd43b7f1b0d29427aa03dc2ba8602072a208ae9
4
- data.tar.gz: 8e5b735ee64a73a2ce2e327b4a917cadc79800ef
3
+ metadata.gz: ef5641dfcac8d3779955b4d9d8689e3861b5ddb9
4
+ data.tar.gz: 0bd4e918973827e046047b707b3e050490f35d98
5
5
  SHA512:
6
- metadata.gz: b3712cc47cec6d73de6d61d6f5adc655e28c7e58e7c02795d8e6eef45ddeab6f05dd7eb24b6791259d5e04d2c0e419379ce685a94251cbce2fe1838ecbd4607a
7
- data.tar.gz: adce7cc2ca1af9d817b1d53b0f890507d1208935ea7908dac8134799dbe5ecabbd93edfdb4ffcad4df27ff4087a22606a82757059459336a6ebcec23b672385c
6
+ metadata.gz: 278846c43c35a39e366dabb92e1b62b608349ace0dab982d87542b3a35ea76a6fbbdc6f42b8aa8fcdaebe6aec8024a327282565f6e3bc91bd842ba30e19d6172
7
+ data.tar.gz: 27dfef5d1aa6ab733e40d4cf3ad4ef43cfda050b397741b3cc3198373276b679daf0dca189de979798a0cd81ec2434b7060827081db751a4177736564d897da6
@@ -15,7 +15,7 @@ module Breezy
15
15
  filter = request.params[:_bz]
16
16
 
17
17
  if filter
18
- filter.gsub(/[^\da-zA-Z\=\.]+/, '')
18
+ filter.gsub(/[^\da-zA-Z\_\=\.]+/, '')
19
19
  end
20
20
  end
21
21
  end
@@ -12,5 +12,5 @@ json.attributes_for_form do
12
12
  <%- end -%>
13
13
  end
14
14
 
15
- json.post_path <%= singular_table_name%>_path(@<%=singular_table_name%>)
16
- json.posts_path <%= plural_table_name %>_path
15
+ json.<%= singular_table_name%>_path <%= singular_table_name%>_path(@<%=singular_table_name%>)
16
+ json.<%= plural_table_name %>_path <%= plural_table_name %>_path
@@ -5,10 +5,10 @@ json.<%= plural_table_name %> do
5
5
  <%- attributes_list_with_timestamps.each do |attr| -%>
6
6
  json.<%=attr%> <%= singular_table_name %>.<%=attr%>
7
7
  <%- end -%>
8
- json.edit_post_path edit_<%=singular_table_name%>_path(<%=singular_table_name%>)
9
- json.post_path <%=singular_table_name%>_path(<%=singular_table_name%>)
8
+ json.edit_<%=singular_table_name%>_path edit_<%=singular_table_name%>_path(<%=singular_table_name%>)
9
+ json.<%=singular_table_name%>_path <%=singular_table_name%>_path(<%=singular_table_name%>)
10
10
  end
11
11
  end
12
12
 
13
13
 
14
- json.new_post_path new_<%= singular_table_name %>_path
14
+ json.new_<%= singular_table_name %>_path new_<%= singular_table_name %>_path
@@ -1,12 +1,11 @@
1
1
  import React from 'react'
2
- import {withBrowserBehavior} from '@jho406/breezy'
2
+ import {enhanceVisitWithBrowserBehavior} from '@jho406/breezy'
3
3
 
4
4
  export default class extends React.Component {
5
5
  constructor (props) {
6
6
  super(props)
7
- const {visit, remote} = withBrowserBehavior(props.visit, props.remote)
8
- this.visit = visit.bind(this)
9
- this.remote = remote.bind(this)
7
+ const visit = enhanceVisitWithBrowserBehavior(props.visit)
8
+ this.enhancedVisit = visit.bind(this)
10
9
  }
11
10
  }
12
11
 
@@ -3,19 +3,20 @@ import {mapStateToProps, mapDispatchToProps} from '@jho406/breezy'
3
3
  import {connect} from 'react-redux'
4
4
  import BaseScreen from 'components/BaseScreen'
5
5
  import <%= plural_table_name.camelize %>Form from 'components/<%= plural_table_name.camelize %>Form'
6
+ import * as applicationActionCreators from 'javascript/packs/action_creators'
6
7
 
7
8
  class <%= plural_table_name.camelize %>Edit extends BaseScreen {
8
9
  formRef = React.createRef()
9
10
 
10
11
  handleSubmit = (values, {setSubmitting}) => {
11
- this.props.delInPage({pageKey: this.props.pageKey, keypath: 'errors'})
12
+ this.props.clearFormErrors(this.props.pageKey)
12
13
 
13
14
  const options = {
14
15
  method:'PUT',
15
16
  body: JSON.stringify(values),
16
17
  }
17
18
 
18
- this.visit(this.props.<%= singular_table_name %>, options).then( rsp => {
19
+ this.enhancedVisit(this.props.<%= singular_table_name.camelize(:lower) %>, options).then( rsp => {
19
20
  setSubmitting(false)
20
21
  if (this.props.errors) {
21
22
  this.formRef.current.setErrors(this.props.errors)
@@ -28,11 +29,11 @@ class <%= plural_table_name.camelize %>Edit extends BaseScreen {
28
29
  <div>
29
30
  <<%= plural_table_name.camelize %>Form
30
31
  onSubmit={this.handleSubmit}
31
- initialValues={this.props.attributes_for_form}
32
+ initialValues={this.props.attributesForForm}
32
33
  ref={this.formRef}
33
34
  />
34
- <a onClick={ e => this.visit(this.props.post_path)}>Show</a>
35
- <a onClick={ e => this.visit(this.props.posts_path)}>Back</a>
35
+ <a onClick={ e => this.enhancedVisit(this.props.<%= singular_table_name.camelize(:lower) %>Path)}>Show</a>
36
+ <a onClick={ e => this.enhancedVisit(this.props.<%= plural_table_name.camelize(:lower) %>Path)}>Back</a>
36
37
  </div>
37
38
  )
38
39
  }
@@ -40,7 +41,7 @@ class <%= plural_table_name.camelize %>Edit extends BaseScreen {
40
41
 
41
42
  export default connect(
42
43
  mapStateToProps,
43
- mapDispatchToProps
44
+ {...mapDispatchToProps, ...applicationActionCreators}
44
45
  )(<%= plural_table_name.camelize %>Edit)
45
46
 
46
47
 
@@ -4,7 +4,7 @@ import { Formik, Form, Field } from 'formik';
4
4
  export default React.forwardRef(
5
5
  ({initialValues = {
6
6
  <%- attributes_list.select{|attr| attr != :id }.each do |attr| -%>
7
- <%=attr.to_s%>:'',
7
+ <%=attr.camelize(:lower).to_s%>:'',
8
8
  <%- end -%>
9
9
  }, onSubmit}, ref) => {
10
10
  return (
@@ -17,7 +17,7 @@ export default React.forwardRef(
17
17
  <Form>
18
18
  <%- attributes_list.select{|attr| attr != :id }.each do |attr| -%>
19
19
  <Field type="text" name="<%=attr.to_s%>" />
20
- {errors.<%=attr.to_s%> && touched.<%=attr.to_s%> && errors.<%=attr.to_s%>}
20
+ {errors.<%=attr.camelize(:lower).to_s%> && touched.<%=attr.camelize(:lower).to_s%> && errors.<%=attr.camelize(:lower).to_s%>}
21
21
  <%- end -%>
22
22
 
23
23
  <button type="submit" disabled={isSubmitting}>
@@ -5,19 +5,19 @@ import BaseScreen from 'components/BaseScreen'
5
5
 
6
6
  class <%= plural_table_name.camelize %>Index extends BaseScreen {
7
7
  static defaultProps = {
8
- <%= plural_table_name %>: []
8
+ <%= plural_table_name.camelize(:lower) %>: []
9
9
  }
10
10
 
11
11
  render () {
12
- const <%= singular_table_name %>Items = this.props.<%= plural_table_name %>.map((<%= singular_table_name %>, key) => {
12
+ const <%= singular_table_name.camelize(:lower) %>Items = this.props.<%= plural_table_name.camelize(:lower) %>.map((<%= singular_table_name.camelize(:lower) %>, key) => {
13
13
  return (
14
- <tr key={<%= singular_table_name %>.id}>
14
+ <tr key={<%= singular_table_name.camelize(:lower) %>.id}>
15
15
  <%- attributes_list.select{|attr| attr != :id }.each do |attr| -%>
16
- <td>{<%=singular_table_name%>.<%=attr%>}</td>
16
+ <td>{<%=singular_table_name.camelize(:lower)%>.<%=attr.camelize(:lower)%>}</td>
17
17
  <%- end -%>
18
- <td><a onClick={ e => this.visit(<%=singular_table_name%>.post_path)}>Show</a></td>
19
- <td><a onClick={ e => this.visit(<%=singular_table_name%>.edit_post_path)}>Edit</a></td>
20
- <td><a onClick={ e => this.visit(<%=singular_table_name%>.post_path, {method: 'DELETE'})}>Delete</a></td>
18
+ <td><a onClick={ e => this.enhancedVisit(<%=singular_table_name%>.<%=singular_table_name.camelize(:lower)%>Path)}>Show</a></td>
19
+ <td><a onClick={ e => this.enhancedVisit(<%=singular_table_name%>.edit<%=singular_table_name.camelize%>Path)}>Edit</a></td>
20
+ <td><a onClick={ e => this.enhancedVisit(<%=singular_table_name%>.<%=singular_table_name.camelize(:lower)%>Path, {method: 'DELETE'})}>Delete</a></td>
21
21
  </tr>
22
22
  )
23
23
  })
@@ -43,7 +43,7 @@ class <%= plural_table_name.camelize %>Index extends BaseScreen {
43
43
  </tbody>
44
44
  </table>
45
45
  <br />
46
- <a onClick={ e => this.visit(this.props.new_post_path)}>New <%= singular_table_name.capitalize %></a>
46
+ <a onClick={ e => this.enhancedVisit(this.props.new<%= singular_table_name.camelize %>Path)}>New <%= singular_table_name.capitalize %></a>
47
47
  </div>
48
48
  )
49
49
  }
@@ -3,19 +3,20 @@ import {mapStateToProps, mapDispatchToProps} from '@jho406/breezy'
3
3
  import {connect} from 'react-redux'
4
4
  import BaseScreen from 'components/BaseScreen'
5
5
  import <%= plural_table_name.camelize %>Form from 'components/<%= plural_table_name.camelize %>Form'
6
+ import * as applicationActionCreators from 'javascript/packs/action_creators'
6
7
 
7
8
  class <%= plural_table_name.camelize %>New extends BaseScreen {
8
9
  formRef = React.createRef()
9
10
 
10
11
  handleSubmit = (values, {setSubmitting}) => {
11
- this.props.delInPage({pageKey: this.props.pageKey, keypath: 'errors'})
12
+ this.props.clearFormErrors(this.props.pageKey)
12
13
 
13
14
  const options = {
14
15
  method:'POST',
15
16
  body: JSON.stringify(values),
16
17
  }
17
18
 
18
- this.visit(this.props.<%= plural_table_name %>_path, options).then( rsp => {
19
+ this.enhancedVisit(this.props.<%= plural_table_name.camelize(:lower) %>Path, options).then( rsp => {
19
20
  setSubmitting(false)
20
21
  if (this.props.errors) {
21
22
  this.formRef.current.setErrors(this.props.errors)
@@ -30,7 +31,7 @@ class <%= plural_table_name.camelize %>New extends BaseScreen {
30
31
  onSubmit={this.handleSubmit}
31
32
  ref={this.formRef}
32
33
  />
33
- <a onClick={ e => this.visit(this.props.<%= plural_table_name %>_path)}>Back</a>
34
+ <a onClick={ e => this.enhancedVisit(this.props.<%= plural_table_name.camelize(:lower) %>Path)}>Back</a>
34
35
  </div>
35
36
  )
36
37
  }
@@ -38,6 +39,6 @@ class <%= plural_table_name.camelize %>New extends BaseScreen {
38
39
 
39
40
  export default connect(
40
41
  mapStateToProps,
41
- mapDispatchToProps
42
+ {...mapDispatchToProps, ...applicationActionCreators}
42
43
  )(<%= plural_table_name.camelize %>New)
43
44
 
@@ -11,11 +11,11 @@ class <%= plural_table_name.camelize %>Show extends BaseScreen {
11
11
  <%- attributes_list_with_timestamps.select{|attr| attr != :id }.each do |attr| -%>
12
12
  <p>
13
13
  <strong><%= attr.capitalize %>:</strong>
14
- {this.props.<%=attr%>}
14
+ {this.props.<%=attr.camelize(:lower)%>}
15
15
  </p>
16
16
  <%- end -%>
17
- <a onClick={ e => this.visit(this.props.edit_<%= singular_table_name %>_path)}>Edit</a>
18
- <a onClick={ e => this.visit(this.props.<%= plural_table_name %>_path )}>Back</a>
17
+ <a onClick={ e => this.enhancedVisit(this.props.edit<%= singular_table_name.camelize %>Path)}>Edit</a>
18
+ <a onClick={ e => this.enhancedVisit(this.props.<%= plural_table_name.camelize(:lower) %>Path )}>Back</a>
19
19
  </div>
20
20
  )
21
21
  }
@@ -0,0 +1,12 @@
1
+ import {
2
+ CLEAR_FORM_ERRORS
3
+ } from './actions'
4
+
5
+ export function clearFormErrors(pageKey) {
6
+ return {
7
+ type: CLEAR_FORM_ERRORS,
8
+ payload: {
9
+ pageKey,
10
+ }
11
+ }
12
+ }
@@ -0,0 +1 @@
1
+ export const CLEAR_FORM_ERRORS = 'CLEAR_FORM_ERRORS'
@@ -1,11 +1,14 @@
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
7
  import createHistory from 'history/createBrowserHistory'
7
8
  import Breezy from '@jho406/breezy'
8
9
  import Nav from '@jho406/breezy/dist/NavComponent'
10
+ import applicationReducer from './reducer'
11
+
9
12
 
10
13
  // Mapping between your props template to Component
11
14
  // e.g {'posts/new': PostNew}
@@ -27,9 +30,15 @@ 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))
@@ -41,6 +50,7 @@ class App extends React.Component {
41
50
  render() {
42
51
  return <Provider store={store}>
43
52
  <Nav
53
+ store={store}
44
54
  mapping={this.props.mapping}
45
55
  history={history}
46
56
  initialPageKey={initialPageKey}
@@ -18,7 +18,9 @@
18
18
  ["module-resolver", {
19
19
  "root": ["./app"],
20
20
  "alias": {
21
- "views": "./app/views"
21
+ "views": "./app/views",
22
+ "components": "./app/components",
23
+ "javascript": "./app/javascript"
22
24
  }
23
25
  }],
24
26
  "transform-object-rest-spread",
@@ -0,0 +1,19 @@
1
+ import {
2
+ CLEAR_FORM_ERRORS
3
+ } from './actions'
4
+ import produce from "immer"
5
+
6
+ export default function (state = {}, action) {
7
+ switch(action.type) {
8
+ case CLEAR_FORM_ERRORS: {
9
+ const {pageKey} = action.payload
10
+
11
+ return produce(state, draft => {
12
+ const currentPage = draft[pageKey]
13
+ delete currentPage.errors
14
+ })
15
+ }
16
+ default:
17
+ return state
18
+ }
19
+ }
@@ -24,7 +24,7 @@ def add_member_methods
24
24
  inject_into_file "app/models/application_record.rb", after: "class ApplicationRecord < ActiveRecord::Base\n" do
25
25
  <<-RUBY
26
26
  def self.member_at(index)
27
- offset(index).limit(1)
27
+ offset(index).limit(1).first
28
28
  end
29
29
 
30
30
  def self.member_by(attr, value)
@@ -56,7 +56,7 @@ if File.exist?(babelrc)
56
56
  "alias": {
57
57
  "views": "./app/views",
58
58
  "components": "./app/components",
59
- "javascripts": "./app/javascripts"
59
+ "javascript": "./app/javascript"
60
60
  }
61
61
  }])
62
62
 
@@ -75,6 +75,15 @@ end
75
75
  say "Copying application.js file to #{Webpacker.config.source_entry_path}"
76
76
  copy_file "#{__dir__}/templates/web/application.js", "#{Webpacker.config.source_entry_path}/application.js"
77
77
 
78
+ say "Copying reducer.js file to #{Webpacker.config.source_entry_path}"
79
+ copy_file "#{__dir__}/templates/web/reducer.js", "#{Webpacker.config.source_entry_path}/reducer.js"
80
+
81
+ say "Copying action_creators.js file to #{Webpacker.config.source_entry_path}"
82
+ copy_file "#{__dir__}/templates/web/action_creators.js", "#{Webpacker.config.source_entry_path}/action_creators.js"
83
+
84
+ say "Copying actions.js file to #{Webpacker.config.source_entry_path}"
85
+ copy_file "#{__dir__}/templates/web/actions.js", "#{Webpacker.config.source_entry_path}/actions.js"
86
+
78
87
  say "Copying Breezy initializer"
79
88
  copy_file "#{__dir__}/templates/web/initializer.rb", "config/initializers/breezy.rb"
80
89
 
@@ -85,7 +94,7 @@ say "Adding required member methods to ApplicationRecord"
85
94
  add_member_methods
86
95
 
87
96
  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"
97
+ run "yarn add babel-plugin-module-resolver babel-preset-react formik history prop-types react-redux redux-thunk redux reduce-reducers react react-dom immer @jho406/breezy --save"
89
98
 
90
99
  say "Updating webpack paths to include .jsx file extension"
91
100
  insert_into_file Webpacker.config.config_path, " - .jsx\n", after: /extensions:\n/
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ class HelpersTest < ActiveSupport::TestCase
4
+ include Breezy::Helpers
5
+ attr_reader :request
6
+
7
+ class Request
8
+ attr_reader :params
9
+ def initialize(params = {})
10
+ @params = params
11
+ end
12
+ end
13
+
14
+ test 'breezy_filter returns a valid _bz param' do
15
+ @request = Request.new({:_bz => 'foo.bar.baz_baz'})
16
+
17
+ assert_equal breezy_filter, 'foo.bar.baz_baz'
18
+ end
19
+
20
+ test 'breezy_filter removes invalid _bz param chars' do
21
+ @request = Request.new({:_bz => 'foo.bar/?)()-'})
22
+
23
+ assert_equal breezy_filter, 'foo.bar'
24
+ end
25
+
26
+ test 'breezy_filter return nil when no params are present' do
27
+ @request = Request.new({})
28
+
29
+ assert_nil breezy_filter
30
+ end
31
+ end
@@ -108,7 +108,7 @@ class RenderTest < ActionController::TestCase
108
108
  rendered = <<~HTML
109
109
  <html>
110
110
  <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>
111
+ <script>(function(){var fragments={};var lastFragmentName;var lastFragmentPath;var cache={};var defers=[];return ({"data":#{content.to_json},"screen":"#{opts[:screen]}","fragments":fragments,"privateOpts":{"csrfToken":"secret","assets":["/app.js"],"lastFragmentName":lastFragmentName,"lastFragmentPath":lastFragmentPath,"defers":defers}});})();</script>
112
112
  </head>
113
113
  <body></body>
114
114
  </html>
@@ -120,13 +120,13 @@ class RenderTest < ActionController::TestCase
120
120
 
121
121
  def assert_breezy_js(content)
122
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
123
+ assert_equal '(function(){var fragments={};var lastFragmentName;var lastFragmentPath;var cache={};var defers=[];return ({"data":' + content.to_json + ',"screen":"render/action","fragments":fragments,"privateOpts":{"csrfToken":"secret","assets":["/app.js"],"lastFragmentName":lastFragmentName,"lastFragmentPath":lastFragmentPath,"defers":defers}});})()', @response.body
124
124
  assert_equal 'text/javascript', @response.content_type
125
125
  end
126
126
 
127
127
  def assert_breezy_replace_js(content)
128
128
  assert_response 200
129
- assert_equal 'Breezy.replace((function(){return ({"data":' + content.to_json + ',"csrf_token":"secret","assets":["/app.js"]});})());', @response.body
129
+ assert_equal 'Breezy.replace((function(){return ({"data":' + content.to_json + ',"csrfToken":"secret","assets":["/app.js"]});})());', @response.body
130
130
  assert_equal 'text/javascript', @response.content_type
131
131
  end
132
132
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: breezy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johny Ho
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-30 00:00:00.000000000 Z
11
+ date: 2019-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 0.9.0
33
+ version: 0.10.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 0.9.0
40
+ version: 0.10.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: webpacker
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -177,14 +177,18 @@ files:
177
177
  - lib/generators/rails/templates/web/index.jsx
178
178
  - lib/generators/rails/templates/web/new.jsx
179
179
  - lib/generators/rails/templates/web/show.jsx
180
+ - lib/install/templates/web/action_creators.js
181
+ - lib/install/templates/web/actions.js
180
182
  - lib/install/templates/web/application.js
181
183
  - lib/install/templates/web/babelrc
182
184
  - lib/install/templates/web/initializer.rb
185
+ - lib/install/templates/web/reducer.js
183
186
  - lib/install/web.rb
184
187
  - lib/tasks/install.rake
185
188
  - test/breezy_test.rb
186
189
  - test/configuration_test.rb
187
190
  - test/engine_test.rb
191
+ - test/helpers_test.rb
188
192
  - test/render_test.rb
189
193
  - test/test_helper.rb
190
194
  homepage: https://github.com/jho406/breezy/
@@ -214,6 +218,7 @@ summary: Rails integration for BreezyJS
214
218
  test_files:
215
219
  - test/render_test.rb
216
220
  - test/configuration_test.rb
221
+ - test/helpers_test.rb
217
222
  - test/engine_test.rb
218
223
  - test/test_helper.rb
219
224
  - test/breezy_test.rb