diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/Gemfile b/Gemfile index bd6515a..e72f7a8 100644 --- a/Gemfile +++ b/Gemfile @@ -46,9 +46,12 @@ gem 'jquery-ui-rails' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'factory_bot_rails' + gem 'faker' end group :development do + gem 'rspec-rails', '~> 3.7' # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 3.3.0' gem 'listen', '>= 3.0.5', '< 3.2' diff --git a/Gemfile.lock b/Gemfile.lock index 8f8bcad..bf759fa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -79,8 +79,16 @@ GEM coffee-script-source (1.12.2) concurrent-ruby (1.0.5) crass (1.0.3) + diff-lcs (1.3) erubi (1.7.1) execjs (2.7.0) + factory_bot (4.11.0) + activesupport (>= 3.0.0) + factory_bot_rails (4.11.0) + factory_bot (~> 4.11.0) + railties (>= 3.0.0) + faker (1.9.1) + i18n (>= 0.7) ffi (1.9.23) foundation-rails (6.4.3.0) railties (>= 3.1.0) @@ -155,6 +163,23 @@ GEM rb-fsevent (0.10.3) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-rails (3.8.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) ruby_dep (1.5.0) rubyzip (1.2.1) sass (3.4.25) @@ -214,6 +239,8 @@ DEPENDENCIES capybara (~> 2.15) chromedriver-helper coffee-rails (~> 4.2) + factory_bot_rails + faker foundation-rails jbuilder (~> 2.5) jquery-rails @@ -222,6 +249,7 @@ DEPENDENCIES pg puma (~> 3.11) rails (~> 5.2.0.rc1) + rspec-rails (~> 3.7) sass-rails (~> 5.0) selenium-webdriver spring @@ -235,4 +263,4 @@ RUBY VERSION ruby 2.5.0p0 BUNDLED WITH - 1.16.1 + 1.16.4 diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 2fc78bf..d5307d9 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -1,2 +1,3 @@ +@import 'installations'; @import 'foundation_and_overrides'; @import 'consulproject' diff --git a/app/assets/stylesheets/installations.scss b/app/assets/stylesheets/installations.scss new file mode 100644 index 0000000..d586bee --- /dev/null +++ b/app/assets/stylesheets/installations.scss @@ -0,0 +1,13 @@ +.installations { + .top-bar { + height: auto; + + ul { + background-color: unset; + + .button { + color: white; + } + } + } +} diff --git a/app/controllers/installations_controller.rb b/app/controllers/installations_controller.rb index d03a5e2..00bb9e6 100644 --- a/app/controllers/installations_controller.rb +++ b/app/controllers/installations_controller.rb @@ -8,4 +8,27 @@ def show @installation = Installation.find(params[:id]) end -end \ No newline at end of file + def new + @installation = Installation.new + end + + def create + @installation = Installation.new(installation_params) + + respond_to do |format| + if @installation.save + format.html { redirect_to @installation, notice: 'installation was successfully created.' } + format.json { render :show, status: :created, location: @installation } + else + format.html { render :new } + format.json { render json: @installation.errors, status: :unprocessable_entity } + end + end + end + + private + def installation_params + params.require(:installation).permit(:name, :repo, :website, :contact_name, :conact_email, :location, :organization_type, :status, :notes) + end + +end diff --git a/app/helpers/installations_helper.rb b/app/helpers/installations_helper.rb new file mode 100644 index 0000000..9903190 --- /dev/null +++ b/app/helpers/installations_helper.rb @@ -0,0 +1,15 @@ +module InstallationsHelper + + def organization_type_for_select + Installation.organization_types.map do |organization_type, _| + [I18n.t("activerecord.attributes.installation.organization_types.#{organization_type}"), organization_type] + end + end + + def installation_status_for_select + Installation.installation_statuses.map do |installation_status, _| + [I18n.t("activerecord.attributes.installation.installation_statuses.#{installation_status}"), installation_status] + end + end + +end diff --git a/app/models/installation.rb b/app/models/installation.rb index 4735a9b..898cc17 100644 --- a/app/models/installation.rb +++ b/app/models/installation.rb @@ -1,8 +1,23 @@ class Installation < ActiveRecord::Base delegate :last_commit, :lines_diff, :files_changed, :diff_url, to: :github + enum organization_type: { + government: 'government', + ngo: 'ngo', + university: 'university', + school: 'school', + other: 'other', + } + enum installation_status: { + in_progress: 'in_progress', + in_production: 'in_production', + } + + validates :name, presence: true + validates :repo, presence: true, uniqueness: true + def github Installation::Github.new(self) end -end \ No newline at end of file +end diff --git a/app/views/installations/_form.html.erb b/app/views/installations/_form.html.erb new file mode 100644 index 0000000..9cbe081 --- /dev/null +++ b/app/views/installations/_form.html.erb @@ -0,0 +1,110 @@ +<%= form_with(model: installation, local: true) do |form| %> + <% if installation.errors.any? %> +
+

<%= pluralize(installation.errors.count, "error") %> prohibited this installation from being saved:

+ + + +
+ <% end %> + +
+
+ <%= form.label :name, class: "text-right middle" %> +
+ +
+ <%= form.text_field :name %> +
+
+ +
+
+ <%= form.label :repo, class: "text-right middle" %> +
+ +
+ <%= form.text_field :repo %> +
+
+ +
+
+ <%= form.label :website, class: "text-right middle" %> +
+ +
+ <%= form.text_field :website %> +
+
+ +
+
+ <%= form.label :contact_name, class: "text-right middle" %> +
+ +
+ <%= form.text_field :contact_name %> +
+
+ +
+
+ <%= form.label :conact_email, class: "text-right middle" %> +
+ +
+ <%= form.text_field :conact_email %> +
+
+ +
+
+ <%= form.label :location, class: "text-right middle" %> +
+ +
+ <%= form.text_field :location %> +
+
+ +
+
+ <%= form.label :organization_type, class: "text-right middle" %> +
+ +
+ <%= form.select :organization_type, organization_type_for_select %> +
+
+ +
+
+ <%= form.label :status, class: "text-right middle" %> +
+ +
+ <%= form.select :status, installation_status_for_select %> +
+
+ +
+
+ <%= form.label :notes, class: "text-right middle" %> +
+ +
+ <%= form.text_field :notes %> +
+
+ +
+ <%= form.submit class: 'button' %> +
+<% end %> diff --git a/app/views/installations/index.html.erb b/app/views/installations/index.html.erb index 01c2b1a..79355ef 100644 --- a/app/views/installations/index.html.erb +++ b/app/views/installations/index.html.erb @@ -1,19 +1,30 @@ -
+
-
- +
+
+
+ -

- <%= image_tag('icons/terminal.svg', alt: "") %> - <%= t("installations.index.title") %> -

+

+ <%= image_tag('icons/terminal.svg', alt: "") %> + <%= t("installations.index.title") %> +

+
+
+ +
+
diff --git a/app/views/installations/new.html.erb b/app/views/installations/new.html.erb new file mode 100644 index 0000000..7003747 --- /dev/null +++ b/app/views/installations/new.html.erb @@ -0,0 +1,17 @@ +
+
+
+

New Installation

+
+
+ +
+
+ <%= render 'form', installation: @installation %> +
+ +
+ <%= link_to 'Back', installations_path %> +
+
+
diff --git a/config/application.rb b/config/application.rb index bf3d64a..b406d66 100644 --- a/config/application.rb +++ b/config/application.rb @@ -15,5 +15,7 @@ class Application < Rails::Application # Application configuration can go into files in config/initializers # -- all .rb files in that directory are automatically loaded after loading # the framework and any gems in your application. + + config.active_record.schema_format = :sql end end diff --git a/config/routes.rb b/config/routes.rb index 82aad0e..9a433fd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,5 @@ Rails.application.routes.draw do - resources :installations, only: [:index, :show] + resources :installations, only: [:index, :show, :new, :create] root 'installations#index' get '/dashboard' => 'installations#index' end diff --git a/db/migrate/20180828221528_add_field_to_installation.rb b/db/migrate/20180828221528_add_field_to_installation.rb new file mode 100644 index 0000000..335140d --- /dev/null +++ b/db/migrate/20180828221528_add_field_to_installation.rb @@ -0,0 +1,51 @@ +class AddFieldToInstallation < ActiveRecord::Migration[5.2] + def up + ActiveRecord::Base.connection.execute <<-SQL + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'organization_type') THEN + CREATE TYPE organization_type AS ENUM ('government', 'ngo', 'university', 'school', 'other'); + END IF; + + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'installation_status') THEN + CREATE TYPE installation_status AS ENUM ('in_progress', 'in_production'); + END IF; + END + $$; + SQL + + if table_exists? :installations + add_column :installations, :website, :string + add_column :installations, :contact_name, :string + add_column :installations, :conact_email, :string + add_column :installations, :location, :string + add_column :installations, :organization_type, :organization_type + add_column :installations, :status, :installation_status + add_column :installations, :notes, :text + end + end + + def down + remove_column :installations, :website if column_exists? :installations, :website + remove_column :installations, :contact_name if column_exists? :installations, :contact_name + remove_column :installations, :conact_email if column_exists? :installations, :conact_email + remove_column :installations, :location if column_exists? :installations, :location + remove_column :installations, :organization_type if column_exists? :installations, :organization_type + remove_column :installations, :status if column_exists? :installations, :status + remove_column :installations, :notes if column_exists? :installations, :notes + + ActiveRecord::Base.connection.execute <<-SQL + DO $$ + BEGIN + IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'organization_type') THEN + DROP TYPE organization_type; + END IF; + + IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'installation_status') THEN + DROP TYPE installation_status; + END IF; + END + $$; + SQL + end +end diff --git a/spec/controllers/installations_controller_spec.rb b/spec/controllers/installations_controller_spec.rb new file mode 100644 index 0000000..7715b75 --- /dev/null +++ b/spec/controllers/installations_controller_spec.rb @@ -0,0 +1,65 @@ +require 'rails_helper' + +RSpec.describe InstallationsController, type: :controller do + + let(:valid_session) { {} } + let(:installation) { create(:installation) } + + describe "GET #index" do + it "returns a success response" do + get :index, params: {}, session: valid_session + expect(response).to be_successful + end + end + + describe "GET #show" do + it "returns a success response" do + get :show, params: {id: installation.id.to_param}, session: valid_session + expect(response).to be_successful + end + end + + describe "GET #new" do + it "returns a success response" do + get :new, params: {}, session: valid_session + expect(response).to be_successful + end + end + + describe "POST #create" do + let(:installation_params) { + { + name: Faker::Lorem.sentence, + repo: Faker::Internet.url('github.com'), + website: Faker::Internet.url, + contact_name: Faker::Name.name_with_middle, + conact_email: Faker::Internet.email, + location: Faker::Address.full_address, + organization_type: Installation.organization_types.keys.sample, + status: Installation.installation_statuses.keys.sample, + notes: Faker::Lorem.paragraph, + } + } + + context "with valid params" do + it "creates a new Installation" do + expect { + post :create, params: {installation: installation_params}, session: valid_session + }.to change(Installation, :count).by(1) + end + + it "redirects to the created installation" do + post :create, params: {installation: installation_params}, session: valid_session + expect(response).to redirect_to(Installation.last) + end + end + + context "with invalid params" do + it "returns a success response (i.e. to display the 'new' template)" do + post :create, params: {installation: {foo: 'foo', bar: 'bar'}}, session: valid_session + expect(response).to be_successful + end + end + end + +end diff --git a/spec/factories/installations.rb b/spec/factories/installations.rb new file mode 100644 index 0000000..1d1e9f0 --- /dev/null +++ b/spec/factories/installations.rb @@ -0,0 +1,13 @@ +FactoryBot.define do + factory :installation do + name { Faker::Lorem.sentence } + repo { Faker::Internet.url('github.com') } + website { Faker::Internet.url } + contact_name { Faker::Name.name_with_middle } + conact_email { Faker::Internet.email } + location { Faker::Address.full_address } + organization_type { Installation.organization_types.keys.sample } + status { Installation.installation_statuses.keys.sample } + notes { Faker::Lorem.paragraph } + end +end diff --git a/spec/features/installations/installations_spec.rb b/spec/features/installations/installations_spec.rb new file mode 100644 index 0000000..4e946a2 --- /dev/null +++ b/spec/features/installations/installations_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +feature 'Installations management' do + before(:each) { + create_list(:installation, 3) + + Installation.any_instance.stub(:last_commit).and_return(Faker::Date.between(2.days.ago, Date.today)) + Installation.any_instance.stub(:github).and_return(self) + Installation.any_instance.stub(:lines_diff).and_return(Faker::Number.number(2)) + Installation.any_instance.stub(:diff_url).and_return(Faker::Internet.url('github.com')) + Installation.any_instance.stub(:files_changed).and_return([Faker::File.file_name]) + } + + scenario 'Index', :js do + + visit installations_path + + expect(page).to have_link(I18n.t('helpers.link.new', model: 'Installation'), href: new_installation_path) + expect(page).to have_css "h2.installation__header--title", count: 3 + + end + + scenario 'New', :js do + visit new_installation_path + + fill_in :name.to_s.humanize, with: Faker::Lorem.sentence + fill_in :repo.to_s.humanize, with: Faker::Internet.url('github.com') + fill_in :website.to_s.humanize, with: Faker::Internet.url + fill_in :contact_name.to_s.humanize, with: Faker::Name.name_with_middle + fill_in :conact_email.to_s.humanize, with: Faker::Internet.email + fill_in :location.to_s.humanize, with: Faker::Address.full_address + find_field(:organization_type.to_s.humanize).select(I18n.t('activerecord.attributes.installation.organization_types.government')) + find_field(:status.to_s.humanize).select(I18n.t('activerecord.attributes.installation.installation_statuses.in_progress')) + fill_in :notes.to_s.humanize, with: Faker::Lorem.paragraph + + click_button I18n.t('helpers.submit.create', model: 'Installation') + + expect(page).to have_content I18n.t('installations.show.files_changed') + expect(page).to have_link I18n.t('installations.show.changes_in_github') + end +end diff --git a/spec/models/installation_spec.rb b/spec/models/installation_spec.rb new file mode 100644 index 0000000..721c5f1 --- /dev/null +++ b/spec/models/installation_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +RSpec.describe Installation, type: :model do + + it { is_expected.to respond_to(:name) } + it { is_expected.to respond_to(:repo) } + it { is_expected.to respond_to(:created_at) } + it { is_expected.to respond_to(:updated_at) } + it { is_expected.to respond_to(:website) } + it { is_expected.to respond_to(:contact_name) } + it { is_expected.to respond_to(:conact_email) } + it { is_expected.to respond_to(:location) } + it { is_expected.to respond_to(:organization_type) } + it { is_expected.to respond_to(:status) } + it { is_expected.to respond_to(:notes) } + + it "is valid with valid attributes" do + expect(Installation.new(name: 'foo', repo: 'bar')).to be_valid + end + + it "is not valid with empty attributes" do + expect(Installation.new).not_to be_valid + end + + it "is not valid with repeated 'repo' attribute" do + Installation.new(repo: 'foo') + expect(Installation.new(repo: 'foo')).not_to be_valid + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000..55a6fb6 --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,39 @@ +ENV['RAILS_ENV'] ||= 'test' + +require 'spec_helper' +require File.expand_path('../../config/environment', __FILE__) +abort("The Rails environment is running in production mode!") if Rails.env.production? + +require 'rspec/rails' +require 'capybara/rails' +require 'capybara/rspec' + +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + puts e.to_s.strip + exit 1 +end + +RSpec.configure do |config| + config.fixture_path = "#{::Rails.root}/spec/fixtures" + config.use_transactional_fixtures = true + config.infer_spec_type_from_file_location! + config.filter_rails_from_backtrace! +end + +Capybara.register_driver :chrome do |app| + Capybara::Selenium::Driver.new(app, browser: :chrome) +end + +Capybara.register_driver :headless_chrome do |app| + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( + chromeOptions: { args: %w(headless no-sandbox window-size=1200,600) } + ) + + Capybara::Selenium::Driver.new( + app, + browser: :chrome, + desired_capabilities: capabilities + ) +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..94bf118 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,16 @@ +require 'factory_bot_rails' + +RSpec.configure do |config| + config.include FactoryBot::Syntax::Methods + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + mocks.syntax = :should + end + + config.shared_context_metadata_behavior = :apply_to_host_groups + +end diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb deleted file mode 100644 index d19212a..0000000 --- a/test/application_system_test_case.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "test_helper" - -class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - driven_by :selenium, using: :chrome, screen_size: [1400, 1400] -end diff --git a/test/controllers/.keep b/test/controllers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/.keep b/test/fixtures/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/helpers/.keep b/test/helpers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/integration/.keep b/test/integration/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/mailers/.keep b/test/mailers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/models/.keep b/test/models/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/system/.keep b/test/system/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 3ab84e3..0000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,10 +0,0 @@ -ENV['RAILS_ENV'] ||= 'test' -require_relative '../config/environment' -require 'rails/test_help' - -class ActiveSupport::TestCase - # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. - fixtures :all - - # Add more helper methods to be used by all tests here... -end