Skip to content

Commit

Permalink
WIP: Introduce suspenders:install:web generator and application tem…
Browse files Browse the repository at this point in the history
…plate

Create generator to invoke all necessary generators. We add it to the
`install` namespace to provide flexibility should we add other
installation options, such as ones for API Only applications.

We manually invoke generators rather than using `generate
"suspenders:generator"`. This is because in those cases, the generator
is actually run, which slows down the test suite dramatically.

More importantly, this allows us  to use [Mocha][] to mock generators in
an effort to improve testing. By mocking the generators, we ensure
they're not actually invoked, which would result in a slow test. Also,
it would mean we would need to build up extensive `prepare_destination`
and `restore_destination` method declarations.

[Mocha]: https://github.com/freerange/mocha
  • Loading branch information
stevepolitodesign committed Mar 22, 2024
1 parent 52e99e9 commit a6e3706
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 1 deletion.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Unreleased
* Introduce `suspenders:email` generator
* Introduce `suspenders:testing` generator
* Introduce `suspenders:prerequisites` generator
* Introduce `suspenders:install:web` generator

20230113.0 (January, 13, 2023)

Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,24 @@ if you like missing deadlines.

## Usage

### Existing Rails Applications

```
group :development, :test do
gem "suspenders"
end
```

```
bin/rails g suspenders:all
bin/rails g suspenders:install:web
```

### New Rails Applications

```
rails new my_app \
-d=postgresql \
-m=https://raw.githubusercontent.com/thoughtbot/suspenders/lib/install/web.rb
```

## Generators
Expand Down
49 changes: 49 additions & 0 deletions lib/generators/suspenders/install/web_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require "generators/suspenders/prerequisites_generator"
require "generators/suspenders/accessibility_generator"
require "generators/suspenders/styles_generator"
require "generators/suspenders/advisories_generator"
require "generators/suspenders/inline_svg_generator"
require "generators/suspenders/factories_generator"
require "generators/suspenders/jobs_generator"
require "generators/suspenders/lint_generator"
require "generators/suspenders/rake_generator"
require "generators/suspenders/views_generator"
require "generators/suspenders/setup_generator"
require "generators/suspenders/tasks_generator"
require "generators/suspenders/email_generator"
require "generators/suspenders/testing_generator"

module Suspenders
module Generators
module Install
class WebGenerator < Rails::Generators::Base
include Suspenders::Generators::APIAppUnsupported

def invoke_generators
# This needs to go first, since it configures `.node-version`
Suspenders::Generators::PrerequisitesGenerator.new.invoke_all

Suspenders::Generators::AccessibilityGenerator.new.invoke_all
Suspenders::Generators::StylesGenerator.new.invoke_all
Suspenders::Generators::AdvisoriesGenerator.new.invoke_all
Suspenders::Generators::InlineSvgGenerator.new.invoke_all
Suspenders::Generators::FactoriesGenerator.new.invoke_all

# Needs to be invoked before StylesGenerator, since that generator
# creates Procfile.dev
Suspenders::Generators::JobsGenerator.new.invoke_all
Suspenders::Generators::RakeGenerator.new.invoke_all
Suspenders::Generators::ViewsGenerator.new.invoke_all
Suspenders::Generators::SetupGenerator.new.invoke_all
Suspenders::Generators::TasksGenerator.new.invoke_all
Suspenders::Generators::EmailGenerator.new.invoke_all
Suspenders::Generators::TestingGenerator.new.invoke_all

# Needs to be invoked last, since it fixes any liting violations
# caused by the previous generators.
Suspenders::Generators::LintGenerator.new.invoke_all
end
end
end
end
end
9 changes: 9 additions & 0 deletions lib/install/web.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
after_bundle do
gem_group :development, :test do
gem "suspenders", github: "thoughtbot/suspenders", branch: "suspenders-3-0-0-web-generator"
end

run "bundle install"

generate "suspenders:install:web"
end
3 changes: 3 additions & 0 deletions lib/suspenders/generators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

module Suspenders
module Generators

CSS_OPTIONS = %w[tailwind postcss].freeze

module Helpers
def default_test_suite?
File.exist? Rails.root.join("test")
Expand Down
96 changes: 96 additions & 0 deletions test/generators/suspenders/install/web_generator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
require "test_helper"
require "generators/suspenders/install/web_generator"

module Suspenders
module Generators
module Install
class WebGeneratorTest < Rails::Generators::TestCase
include Suspenders::TestHelpers

tests Suspenders::Generators::Install::WebGenerator
destination Rails.root
setup :prepare_destination
teardown :restore_destination

test "raises if API only application" do
within_api_only_app do
assert_raises Suspenders::Generators::APIAppUnsupported::Error do
run_generator
end
end
end

test "invokes generators" do
prerequisites_generator_mock = mock("prerequisites_generator")
Suspenders::Generators::PrerequisitesGenerator.stubs(:new).returns(prerequisites_generator_mock)

accessibility_generator_mock = mock("accessibility_generator")
Suspenders::Generators::AccessibilityGenerator.stubs(:new).returns(accessibility_generator_mock)

styles_generator_mock = mock("styles_generator")
Suspenders::Generators::StylesGenerator.stubs(:new).returns(styles_generator_mock)

advisories_generator_mock = mock("advisories_generator")
Suspenders::Generators::AdvisoriesGenerator.stubs(:new).returns(advisories_generator_mock)

inline_svg_generator_mock = mock("inline_svg_generator")
Suspenders::Generators::InlineSvgGenerator.stubs(:new).returns(inline_svg_generator_mock)

facories_generator_mock = mock("facories_generator")
Suspenders::Generators::FactoriesGenerator.stubs(:new).returns(facories_generator_mock)

jobs_generator_mock = mock("jobs_generator")
Suspenders::Generators::JobsGenerator.stubs(:new).returns(jobs_generator_mock)

lint_generator_mock = mock("lint_generator")
Suspenders::Generators::LintGenerator.stubs(:new).returns(lint_generator_mock)

rake_generator_mock = mock("rake_generator")
Suspenders::Generators::RakeGenerator.stubs(:new).returns(rake_generator_mock)

views_generator_mock = mock("views_generator")
Suspenders::Generators::ViewsGenerator.stubs(:new).returns(views_generator_mock)

setup_generator_mock = mock("setup_generator")
Suspenders::Generators::SetupGenerator.stubs(:new).returns(setup_generator_mock)

tasks_generator_mock = mock("tasks_generator")
Suspenders::Generators::TasksGenerator.stubs(:new).returns(tasks_generator_mock)

email_generator_mock = mock("email_generator")
Suspenders::Generators::EmailGenerator.stubs(:new).returns(email_generator_mock)

testing_generator_mock = mock("testing_generator")
Suspenders::Generators::TestingGenerator.stubs(:new).returns(testing_generator_mock)

prerequisites_generator_mock.expects(:invoke_all).once
accessibility_generator_mock.expects(:invoke_all).once
styles_generator_mock.expects(:invoke_all).once
advisories_generator_mock.expects(:invoke_all).once
inline_svg_generator_mock.expects(:invoke_all).once
facories_generator_mock.expects(:invoke_all).once
jobs_generator_mock.expects(:invoke_all).once
lint_generator_mock.expects(:invoke_all).once
rake_generator_mock.expects(:invoke_all).once
views_generator_mock.expects(:invoke_all).once
setup_generator_mock.expects(:invoke_all).once
tasks_generator_mock.expects(:invoke_all).once
email_generator_mock.expects(:invoke_all).once
testing_generator_mock.expects(:invoke_all).once

generator_class.new([], css: "tailwind").invoke_generators
end

private

def prepare_destination
touch "Gemfile"
end

def restore_destination
remove_file_if_exists "Gemfile"
end
end
end
end
end

0 comments on commit a6e3706

Please sign in to comment.