react_to_rails 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26bd74fbf51bf21c1df28eafd788693daea911a55489b1bf348b8fcdf1a3c8da
4
- data.tar.gz: 408f21559f0fa31e3519a65b8851cac8d689ab161bc2c0ad13e95e06f28069db
3
+ metadata.gz: 2fbb427b028dbd40b73febc052ca5305da89181f686e7d247f297bf27dae42d7
4
+ data.tar.gz: 83c00c172263ac594204cab088dd0ffc61d0700bbc157cd7667e07d859fcb2f5
5
5
  SHA512:
6
- metadata.gz: ab2435b3ee8b82b7f0ad4904efffeef7fed9f3b9bfc3b9a8f121ad83c5d493e321532fe122befeb2a5d9ef7e9a613104912a1cccefd5460664cf35b771d30763
7
- data.tar.gz: 25afc03e34d5ad5f438106b7b589c88a90c5511809a9b2217108a6e7779f60bdc821c7d1ec452d792f6fcc52433c5bdcbc38200fb8e9be378b30ec9aa7ec6bd4
6
+ metadata.gz: c48d2b54304140d1b711f57f4690b80e5e53c3a1c5d1c5456d6546a2e2f719832b8f4768061e561aa59131dd73536590459e95ca9052937e051216d90ba9a994
7
+ data.tar.gz: 9851cae0047b631dd49e78146f777d7df5b5fa8b1c8912a934b7f222b4c3d7358f285a28d821430c7cfaec0dfb1ee71621faec8e6b66ddf639809abe9aed6231
data/README.md CHANGED
@@ -4,17 +4,19 @@ This gem converts React components to Rails [View Components](https://viewcompon
4
4
 
5
5
  ## Background
6
6
 
7
- I created this tool use with [Tailwind Plus](https://tailwindcss.com/plus/) (formerly Tailwind UI).
7
+ I created this tool for use with [Tailwind Plus](https://tailwindcss.com/plus/) (formerly Tailwind UI).
8
8
 
9
9
  Tailwind Plus is a commercial product which provides high quality components for React and Vue.
10
10
 
11
- There are also plain HTML versions but they are awkward to work with, e.g.:
11
+ There are also plain HTML versions but they are awkward to work with for Rails, e.g.:
12
12
 
13
13
  - You need to add ERB `<%= %>` placeholders for any content you're passing in.
14
14
  - The HTML is static so is missing any loops, conditionals, etc
15
15
  - Icons are converted to verbose SVG markup
16
16
 
17
- This tool generates a View Component based on the supplied React component which should be much more convenient for Rails.
17
+ This tool generates a View Component based on the supplied React component which should be much more convenient for Rails. It also generates a corresponding test.
18
+
19
+ It's likely that you'll need to still do _some_ work, but it should save a lot of time.
18
20
 
19
21
  For more details of the conversion process, you can look at the [prompt](./lib/react_to_rails/convert.rb).
20
22
 
@@ -88,8 +90,8 @@ The tool will output a report, and generate the view component files:
88
90
 
89
91
  And here is the generated code:
90
92
 
91
- * [app/component/pricing_component.html.erb](./examples/pricing_component.html.erb).
92
- * [app/components/pricing_component.rb](./examples/pricing_component.rb)
93
+ * [app/component/pricing_component.html.erb](./test/approvals/pricing_component.html.erb).
94
+ * [app/components/pricing_component.rb](./test/approvals/pricing_component.rb)
93
95
 
94
96
  ## Limitations
95
97
 
@@ -0,0 +1,42 @@
1
+ require "test_helper"
2
+
3
+ class PricingComponentTest < ActiveSupport::TestCase
4
+ include ViewComponent::TestHelpers
5
+
6
+ test "it renders pricing tiers correctly" do
7
+ tiers = [
8
+ {
9
+ name: 'Hobby',
10
+ id: 'tier-hobby',
11
+ href: '#',
12
+ price_monthly: '$29',
13
+ description: "The perfect plan if you're just getting started with our product.",
14
+ features: ['25 products', 'Up to 10,000 subscribers', 'Advanced analytics', '24-hour support response time'],
15
+ featured: false
16
+ },
17
+ {
18
+ name: 'Enterprise',
19
+ id: 'tier-enterprise',
20
+ href: '#',
21
+ price_monthly: '$99',
22
+ description: 'Dedicated support and infrastructure for your company.',
23
+ features: [
24
+ 'Unlimited products',
25
+ 'Unlimited subscribers',
26
+ 'Advanced analytics',
27
+ 'Dedicated support representative',
28
+ 'Marketing automations',
29
+ 'Custom integrations'
30
+ ],
31
+ featured: true
32
+ }
33
+ ]
34
+ render_inline(PricingComponent.new(tiers: tiers))
35
+ assert_selector "h2", text: "Pricing"
36
+ assert_selector "h3", text: "Hobby"
37
+ assert_selector "h3", text: "Enterprise"
38
+ assert_selector "span", text: "$29"
39
+ assert_selector "span", text: "$99"
40
+ assert_selector "a", text: "Get started today"
41
+ end
42
+ end
@@ -22,8 +22,12 @@ module ReactToRails
22
22
  exit 1
23
23
  end
24
24
 
25
- client = OpenAI::Client.new
26
- ReactToRails::Convert.for_path(path, client: client, name: name)
25
+ convert = ReactToRails::Convert.for_path(path, name: name)
26
+ convert.call
27
+
28
+ File.write("app/components/#{convert.component_file_name}.rb", convert.view_component_ruby_code)
29
+ File.write("app/components/#{convert.component_file_name}.html.erb", convert.erb_template)
30
+ File.write("test/components/#{convert.component_file_name}_test.rb", convert.view_component_test_ruby_code)
27
31
  rescue OptionParser::InvalidOption => e
28
32
  puts "Error: #{e.message}"
29
33
  puts
@@ -8,19 +8,52 @@ module ReactToRails
8
8
  required :summary, String, doc: "A summary of what was done"
9
9
  required :erb_template, String
10
10
  required :view_component_ruby_code, String
11
+ required :view_component_test_ruby_code, String
11
12
  required :demo_erb_code, String
12
13
  end
13
14
 
15
+ class Client
16
+ def initialize(openai_client: nil)
17
+ @openai_client = openai_client || OpenAI::Client.new
18
+ end
19
+
20
+ def call(prompt)
21
+ response = openai_client.responses.create(
22
+ model: :"gpt-4.1",
23
+ temperature: 0,
24
+ input: [
25
+ {role: "user", content: prompt}
26
+ ],
27
+ text: StructuredResponse
28
+ )
29
+
30
+ JSON.parse(response.output.first.content.first.to_json).fetch("parsed")
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :openai_client
36
+ end
37
+
14
38
  class Convert
15
- def initialize(react_content, client:, name:)
39
+ attr_reader :react_content,
40
+ :client,
41
+ :name,
42
+ :summary,
43
+ :view_component_ruby_code,
44
+ :view_component_test_ruby_code,
45
+ :demo_erb_code,
46
+ :erb_template
47
+
48
+ def initialize(react_content, name:, client: nil)
16
49
  @react_content = react_content
17
- @client = client
18
50
  @name = name
51
+ @client = client || Client.new
19
52
  end
20
53
 
21
- def self.for_path(path, client:, name:)
54
+ def self.for_path(path, name:)
22
55
  react_content = File.read(path)
23
- new(react_content, client: client, name: name).call
56
+ new(react_content, name: name)
24
57
  end
25
58
 
26
59
  def call
@@ -30,34 +63,28 @@ module ReactToRails
30
63
  puts
31
64
  puts "Waiting for OpenAI to respond..."
32
65
 
33
- response = client.responses.create(
34
- model: :"gpt-4.1",
35
- temperature: 0,
36
- input: [
37
- {role: "user", content: prompt}
38
- ],
39
- text: StructuredResponse
40
- )
41
-
42
- puts "### RESPONSE ###"
43
-
44
- response = JSON.parse(response.output.first.content.first.to_json).fetch("parsed")
66
+ response = Client.new.call(prompt)
45
67
 
46
68
  @summary = response.fetch("summary")
47
69
  @view_component_ruby_code = response.fetch("view_component_ruby_code")
48
70
  @erb_template = response.fetch("erb_template")
49
71
  @demo_erb_code = response.fetch("demo_erb_code")
72
+ @view_component_test_ruby_code = response.fetch("view_component_test_ruby_code")
50
73
 
51
- component_file_name = @name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
52
- File.write("app/components/#{component_file_name}.rb", @view_component_ruby_code)
53
- File.write("app/components/#{component_file_name}.html.erb", @erb_template)
54
74
  puts @summary
75
+
76
+ return if @erb_template == "" || @view_component_ruby_code == ""
77
+
55
78
  puts
56
79
  puts "### EXAMPLE USAGE ###"
57
80
  puts
58
81
  puts @demo_erb_code
59
82
  end
60
83
 
84
+ def component_file_name
85
+ @name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
86
+ end
87
+
61
88
  private
62
89
 
63
90
  def prompt
@@ -98,6 +125,32 @@ module ReactToRails
98
125
  <%= render(MessageComponent.new(name: name)) %>
99
126
  ```
100
127
 
128
+ Also generate a basic component test, for example:
129
+
130
+ ```
131
+ require "test_helper"
132
+
133
+ class ExampleComponent < ActiveSupport::TestCase
134
+ include ViewComponent::TestHelpers
135
+
136
+ test "it renders correctly" do
137
+ title = "Component title"
138
+ description = "Component description"
139
+ people = [
140
+ { name: 'Andy', title: 'Developer' }
141
+ ]
142
+ example_component = ExampleComponent.new(title: title, description: description, people: people)
143
+
144
+ render_inline(example_component)
145
+
146
+ assert_selector "h1", text: title
147
+ assert_selector "p", text: description
148
+ assert_selector "td", text: people[0][:name]
149
+ assert_selector "td", text: people[0][:title]
150
+ end
151
+ end
152
+ ```
153
+
101
154
  Notes:
102
155
 
103
156
  * If the code contains components from `@headlessui/react' then explain that is not supported and don't attempt to convert.
@@ -110,17 +163,17 @@ module ReactToRails
110
163
  * Replace `<button>` tags with Rails `buton_tag` helper calls.
111
164
  * Replace `onClick` events with theoretical Stimulus calls.
112
165
  * Assume a heroicons helper is available, e.g.: <%= heroicon "magnifying-glass", options: { class: "text-primary-500" } %>
113
- * Asssume a `class_names` helper is available which works just like React's `classNames` helper.
114
- * If the component relies on JavaScript, suggest to use Stimulus or Turbo instead. Also provide https://github.com/excid3/tailwindcss-stimulus-components as a reference.
166
+ * Assume a `class_names` helper is available which works just like React's `classNames` helper.
167
+ * If the component relies on client-side behaviour, suggest to use Stimulus or Turbo instead. Also provide https://github.com/excid3/tailwindcss-stimulus-components as a reference.
115
168
  * If the code constains instructions such as "This example requires updating your template" then report those details.
116
- * Let me know if any parts were not possible to accurately convert.
169
+ * Don't add HTML comment that weren't in the original.
170
+ * Let the user know if any parts were not possible to accurately convert.
171
+ * If `html_safe` is used then warn the user to check for potential security issues.
117
172
 
118
173
  ```
119
174
  #{react_content}
120
175
  ```
121
176
  EOS
122
177
  end
123
-
124
- attr_reader :react_content, :client, :name, :summary, :view_component_ruby_code, :demo_erb_code, :erb_template
125
178
  end
126
179
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReactToRails
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: react_to_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Waite
@@ -48,9 +48,10 @@ files:
48
48
  - LICENSE.txt
49
49
  - README.md
50
50
  - Rakefile
51
+ - approvals/pricing_component.html.erb
52
+ - approvals/pricing_component.rb
53
+ - approvals/pricing_component_test.rb
51
54
  - examples/pricing.jsx
52
- - examples/pricing_component.html.erb
53
- - examples/pricing_component.rb
54
55
  - exe/react_to_rails
55
56
  - lib/react_to_rails.rb
56
57
  - lib/react_to_rails/cli.rb
File without changes
File without changes