diff --git a/Gemfile b/Gemfile index 0b71dab..aaecd97 100644 --- a/Gemfile +++ b/Gemfile @@ -19,10 +19,14 @@ gem "validate_url", "~> 1.0" gem "rexml", ">= 3.3.2" group :development, :test do + gem "byebug", "~> 11.1", ">= 11.1.3" + gem "database_cleaner-active_record", "1.8.0.beta" gem "debug", "~> 1.9", ">= 1.9.2", platforms: %i[mri windows] gem "dotenv", "~> 3.1", ">= 3.1.2" gem "factory_bot_rails", "~> 6.4", ">= 6.4.3" gem "faker", "~> 3.4" + gem "mocha", "~> 1.2", ">= 1.2.1" + gem "rails-controller-testing", "~> 0.0.3" gem "rubocop-rails-omakase", "~> 1.0", require: false, group: [:development] gem "minitest-reporters", "~> 1.6", ">= 1.6.1" end diff --git a/Gemfile.lock b/Gemfile.lock index dcfca61..f90fa71 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -98,11 +98,16 @@ GEM bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) thor (~> 1.0) + byebug (11.1.3) childprocess (5.0.0) colorize (1.1.0) concurrent-ruby (1.2.3) connection_pool (2.4.1) crass (1.0.6) + database_cleaner (1.8.5) + database_cleaner-active_record (1.8.0.beta) + activerecord + database_cleaner (~> 1.8.0.beta) date (3.3.4) debug (1.9.2) irb (~> 1.10) @@ -171,6 +176,7 @@ GEM builder minitest (>= 5.0) ruby-progressbar + mocha (1.16.1) msgpack (1.7.2) mutex_m (0.2.0) net-imap (0.4.13) @@ -236,6 +242,8 @@ GEM activesupport (= 7.1.3.4) bundler (>= 1.15.0) railties (= 7.1.3.4) + rails-controller-testing (0.0.3) + rails (>= 4.2) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -351,7 +359,9 @@ DEPENDENCIES bootsnap (~> 1.18, >= 1.18.3) brakeman bundler-audit (~> 0.9.1) + byebug (~> 11.1, >= 11.1.3) colorize (~> 1.1) + database_cleaner-active_record (= 1.8.0.beta) debug (~> 1.9, >= 1.9.2) devise (~> 4.9) dotenv (~> 3.1, >= 3.1.2) @@ -362,10 +372,12 @@ DEPENDENCIES jbuilder (~> 2.12) letter_opener (~> 1.10) minitest-reporters (~> 1.6, >= 1.6.1) + mocha (~> 1.2, >= 1.2.1) pg (~> 1.5, >= 1.5.6) phlex-rails (~> 1.2) puma (~> 6.4, >= 6.4.2) rails (~> 7.1, >= 7.1.3.4) + rails-controller-testing (~> 0.0.3) rexml (>= 3.3.2) rubocop (~> 1.63, >= 1.63.5) rubocop-factory_bot (~> 2.25, >= 2.25.1) diff --git a/README.md b/README.md index 4d1a6ba..65eb0ff 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Job Tracker is a simple, powerful, and user-friendly web application designed to help job seekers efficiently manage their job search process. Built with Ruby on Rails and enhanced with modern web technologies, this tool streamlines the often overwhelming task of tracking multiple job applications. -UI Screenshot +UI Screenshot ## Key Features diff --git a/app/controllers/job_applications_controller.rb b/app/controllers/job_applications_controller.rb index c56b68f..3e162bb 100644 --- a/app/controllers/job_applications_controller.rb +++ b/app/controllers/job_applications_controller.rb @@ -96,7 +96,7 @@ def set_job_application end def job_application_params - params.require(:job_application).permit(:date_applied, :company_name, :method_of_contact, :email_address, :point_of_contact, :website_link, :position_type, :position_title) + params.require(:job_application).permit(:date_applied, :company_name, :method_of_contact, :email_address, :point_of_contact, :website_link, :position_type, :position_title, :claimed_for_unemployment, :status) end def filter_and_sort_job_applications @@ -105,6 +105,9 @@ def filter_and_sort_job_applications job_applications = job_applications.search(params[:search]) if params[:search].present? job_applications = job_applications.by_method_of_contact(params[:method_of_contact]) if params[:method_of_contact].present? job_applications = job_applications.by_position_type(params[:position_type]) if params[:position_type].present? + job_applications = job_applications.claimed_for_unemployment if params[:claimed_for_unemployment] == "true" + job_applications = job_applications.not_claimed_for_unemployment if params[:claimed_for_unemployment] == "false" + job_applications = job_applications.by_status(params[:status]) if params[:status].present? sort_column = sort_column(params[:sort]) sort_direction = sort_direction(params[:direction]) @@ -119,7 +122,7 @@ def filter_and_sort_job_applications end def sort_column(column) - %w[date_applied company_name position_title created_at].include?(column) ? column : "created_at" + %w[date_applied company_name position_title created_at claimed_for_unemployment status].include?(column) ? column : "created_at" end def sort_direction(direction) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 27a777f..fd7da3a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -32,14 +32,31 @@ def progress_bar_class(type) end def sort_link_to(name, column) - direction = (column.to_s == params[:sort] && params[:direction] == "asc") ? "desc" : "asc" - link_to name, - request.params.merge(sort: column, direction: direction), - class: "text-gray-600 hover:text-gray-900", + current_column = params[:sort] + current_direction = params[:direction] + + is_current_column = column.to_s == current_column + next_direction = (is_current_column && current_direction == "asc") ? "desc" : "asc" + + icon = if is_current_column + (current_direction == "asc") ? "↑" : "↓" + else + "" + end + + link_to request.params.merge(sort: column, direction: next_direction), + class: "text-gray-600 hover:text-gray-900 inline-flex items-center", data: { turbo_frame: "job_applications_table", turbo_action: "replace" - } + } do + content_tag(:span, class: "flex items-center") do + safe_join([ + content_tag(:span, name, class: "mr-1"), + content_tag(:span, icon, class: "sort-icon") + ]) + end + end end def safe_url(url) diff --git a/app/helpers/job_applications_helper.rb b/app/helpers/job_applications_helper.rb index 1e267ed..0a90069 100644 --- a/app/helpers/job_applications_helper.rb +++ b/app/helpers/job_applications_helper.rb @@ -1,2 +1,11 @@ module JobApplicationsHelper + def display_position_type(position_type) + case position_type + when "full_time" then "Full-time" + when "part_time" then "Part-time" + when "internship" then "Internship" + else + position_type + end + end end diff --git a/app/models/job_application.rb b/app/models/job_application.rb index 989af3b..3cefb91 100644 --- a/app/models/job_application.rb +++ b/app/models/job_application.rb @@ -1,18 +1,50 @@ class JobApplication < ApplicationRecord + attribute :status, :string + validates :date_applied, :company_name, :method_of_contact, :position_type, :position_title, presence: true validates :email_address, presence: true, if: -> { method_of_contact == "email" } validates :point_of_contact, presence: true, if: -> { ["email", "phone", "recruiter", "other"].include?(method_of_contact) } - validates :website_link, presence: true, if: -> { method_of_contact == "internet job application" } + validates :website_link, presence: true, if: -> { method_of_contact == "internet_job_application" } validates :website_link, url: { allow_blank: true, schemes: ["http", "https"], no_local: true, public_suffix: true } - enum method_of_contact: {email: "email", phone: "phone", internet_job_application: "internet job application", recruiter: "recruiter", other: "other"} - enum position_type: {full_time: "full-time", part_time: "part-time", internship: "internship"} + validates :claimed_for_unemployment, inclusion: {in: [true, false]} + validates :status, inclusion: {in: %w[hired interviewing job_offer no_response not_hired]} + + enum method_of_contact: { + email: "email", + internet_job_application: "internet job application", + other: "other", + phone: "phone", + recruiter: "recruiter" + } + enum position_type: { + full_time: "full_time", + internship: "internship", + part_time: "part_time" + } + enum status: { + hired: "hired", + interviewing: "interviewing", + job_offer: "job offer", + no_response: "no response", + not_hired: "not hired" + } - scope :search, ->(query) { where("company_name ILIKE ? OR position_title ILIKE ?", "%#{query}%", "%#{query}%") } scope :by_method_of_contact, ->(method) { where(method_of_contact: method) } scope :by_position_type, ->(type) { where(position_type: type) } + scope :by_status, ->(status) { where(status: status) } + scope :claimed_for_unemployment, -> { where(claimed_for_unemployment: true) } + scope :not_claimed_for_unemployment, -> { where(claimed_for_unemployment: false) } + scope :search, ->(query) { + where("company_name ILIKE :query OR + position_title ILIKE :query OR + email_address ILIKE :query OR + point_of_contact ILIKE :query OR + website_link ILIKE :query OR + status ILIKE :query", query: "%#{query}%") + } end diff --git a/app/views/job_applications/_form.html.erb b/app/views/job_applications/_form.html.erb index 251ec95..6ad9f4f 100644 --- a/app/views/job_applications/_form.html.erb +++ b/app/views/job_applications/_form.html.erb @@ -44,12 +44,22 @@
<%= form.label :position_type, class: 'block text-sm font-medium text-gray-700 mb-2' %> - <%= form.select :position_type, JobApplication.position_types.keys.map { |k| [k.humanize, k] }, { include_blank: 'Select a type' }, class: 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline' %> + <%= form.select :position_type, JobApplication.position_types.keys.map { |k| [display_position_type(k), k] }, { include_blank: 'Select a type' }, class: 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline' %>
<%= form.label :position_title, class: 'block text-sm font-medium text-gray-700 mb-2' %> <%= form.text_field :position_title, class: 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline' %>
+
+ <%= form.label :status, class: 'block text-sm font-medium text-gray-700 mb-2' %> + <%= form.select :status, JobApplication.statuses.keys.map { |k| [k.humanize, k] }, { include_blank: 'Select a status' }, class: 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline' %> +
+ <%= form.label :claimed_for_unemployment, class: 'flex items-center' do %> + <%= form.check_box :claimed_for_unemployment, class: 'h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2' %> + Claimed for Unemployment + <% end %> +
+
<%= link_to 'Cancel', root_path, class: 'bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline mr-2', data: { turbo_frame: "_top" } %> diff --git a/app/views/job_applications/_job_application.html.erb b/app/views/job_applications/_job_application.html.erb index b6c5de1..5d868c6 100644 --- a/app/views/job_applications/_job_application.html.erb +++ b/app/views/job_applications/_job_application.html.erb @@ -1,4 +1,4 @@ - + <%= job_application.date_applied.strftime('%m/%d/%Y') %> @@ -9,17 +9,23 @@ <%= job_application.position_title %> - <%= job_application.position_type.humanize %> + <%= display_position_type(job_application.position_type) %> <%= job_application.method_of_contact.humanize %> - <%= job_application.point_of_contact || '-' %> + <%= job_application.point_of_contact.present? ? job_application.point_of_contact : '-' %> <%= job_application.email_address.present? ? job_application.email_address : '-' %> + + <%= job_application.claimed_for_unemployment ? '✔︎' : '-' %> + + + <%= job_application.status.humanize %> + <% if job_application.website_link.present? %> <%= link_to 'Visit', diff --git a/app/views/job_applications/_job_applications_table.html.erb b/app/views/job_applications/_job_applications_table.html.erb index 3ce9c0c..8369af1 100644 --- a/app/views/job_applications/_job_applications_table.html.erb +++ b/app/views/job_applications/_job_applications_table.html.erb @@ -1,52 +1,57 @@ <%= turbo_frame_tag "job_applications_table" do %>
<% if @job_applications.any? %> - - +
+ - - - - - - - - - - - - - - <%= render partial: 'job_application', collection: job_applications %> - -
- <%= sort_link_to 'Date Applied', 'date_applied' %> - - <%= sort_link_to 'Company Name', 'company_name' %> - - <%= sort_link_to 'Position', 'position_title' %> - - Type - - Contact Method - - P.o.C. - - Email - - Website - - Actions -
- <% else %> -
- -

- <%= "No results match your search criteria." if params[:search].present? || params[:method_of_contact].present? || params[:position_type].present? %> -

-
- <% end %> -
- <% end %> + + <%= sort_link_to 'Applied', 'date_applied' %> + + + <%= sort_link_to 'Company', 'company_name' %> + + + <%= sort_link_to 'Position', 'position_title' %> + + + Type + + + Contact Method + + + P.o.C. + + + Email + + + <%= sort_link_to 'Claimed', 'claimed_for_unemployment' %> + + + <%= sort_link_to 'Status', 'status' %> + + + Website + + + Actions + + + + + <%= render partial: 'job_application', collection: job_applications %> + + + <% else %> +
+ +

+ <%= "No results match your search criteria." if params[:search].present? || params[:method_of_contact].present? || params[:position_type].present? %> +

+
+ <% end %> +
+<% end %> diff --git a/app/views/job_applications/_pagination.html.erb b/app/views/job_applications/_pagination.html.erb index bc65968..2c58ded 100644 --- a/app/views/job_applications/_pagination.html.erb +++ b/app/views/job_applications/_pagination.html.erb @@ -1,19 +1,17 @@ -
- <% if @pagination_info[:total_pages] > 1 %> -
-
-
-
- <%= will_paginate @job_applications, - renderer: CustomPaginationRenderer, - previous_label: 'Previous', - next_label: 'Next', - inner_window: 0, - outer_window: 0, - class: 'pagination', - previous_page_class: 'px-3 py-1 rounded-md text-sm font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50', - next_page_class: 'px-3 py-1 rounded-md text-sm font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50' %> -
+<% if @pagination_info[:total_pages] > 1 %> +
+
- <% end %> -
+
+ <%= will_paginate @job_applications, + renderer: CustomPaginationRenderer, + previous_label: 'Previous', + next_label: 'Next', + inner_window: 0, + outer_window: 0, + class: 'pagination', + previous_page_class: 'px-3 py-1 rounded-md text-sm font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50', + next_page_class: 'px-3 py-1 rounded-md text-sm font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50' %> +
+
+<% end %> diff --git a/app/views/job_applications/index.html.erb b/app/views/job_applications/index.html.erb index b558b65..2f65bd3 100644 --- a/app/views/job_applications/index.html.erb +++ b/app/views/job_applications/index.html.erb @@ -19,11 +19,11 @@
<%= form_with url: job_applications_path, method: :get, data: { controller: "job-filter", turbo_frame: "job_applications_table" } do |form| %>
-
+
<%= form.label :search, "Search", class: "sr-only" %> - <%= form.text_field :search, value: params[:search], class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline", placeholder: "Search by company or position", data: { action: "input->job-filter#submit" } %> + <%= form.text_field :search, value: params[:search], class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline", placeholder: "Search", data: { action: "input->job-filter#submit" } %>
-
+
<%= form.label :method_of_contact, "Contact Method", class: "sr-only" %> <%= form.select :method_of_contact, options_for_select([["All Contact Methods", ""]] + JobApplication.method_of_contacts.map { |k, v| [k.humanize, v] }, params[:method_of_contact]), @@ -32,16 +32,34 @@ data: { action: "change->job-filter#submit" } %>
-
+
<%= form.label :position_type, "Position Type", class: "sr-only" %> <%= form.select :position_type, - options_for_select([["All Position Types", ""]] + JobApplication.position_types.map { |k, v| [k.humanize, v] }, params[:position_type]), + options_for_select([["All Position Types", ""]] + JobApplication.position_types.map { |k, v| [display_position_type(k), v] }, params[:position_type]), {}, class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline", data: { action: "change->job-filter#submit" } %>
-
+
+ <%= form.label :claimed_for_unemployment, "Unemployment Claim", class: "sr-only" %> + <%= form.select :claimed_for_unemployment, + options_for_select([["All Claims", ""], ["Claimed", "true"], ["Not Claimed", "false"]], params[:claimed_for_unemployment]), + {}, + class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline", + data: { action: "change->job-filter#submit" } + %> +
+
+ <%= form.label :status, "Application Status", class: "sr-only" %> + <%= form.select :status, + options_for_select([["All Statuses", ""]] + JobApplication.statuses.map { |k, v| [k.humanize, v] }, params[:status]), + {}, + class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline", + data: { action: "change->job-filter#submit" } + %> +
+
<%= form.button "Reset Filters", type: "button", class: "w-full bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded cursor-pointer", data: { action: "click->job-filter#reset" } %>
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 77ba693..ff66e37 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -310,4 +310,5 @@ # When set to false, does not sign a user in automatically after their password is # changed. Defaults to true, so a user is signed in automatically after changing a password. # config.sign_in_after_change_password = true + config.secret_key = Rails.application.secret_key_base end diff --git a/db/migrate/20240717162540_add_unemployment_claim_and_cover_letter_to_job_applications.rb b/db/migrate/20240717162540_add_unemployment_claim_and_cover_letter_to_job_applications.rb new file mode 100644 index 0000000..83370a6 --- /dev/null +++ b/db/migrate/20240717162540_add_unemployment_claim_and_cover_letter_to_job_applications.rb @@ -0,0 +1,5 @@ +class AddUnemploymentClaimAndCoverLetterToJobApplications < ActiveRecord::Migration[7.1] + def change + add_column :job_applications, :claimed_for_unemployment, :boolean, null: false, default: false + end +end diff --git a/db/migrate/20240717175130_add_status_to_job_applications.rb b/db/migrate/20240717175130_add_status_to_job_applications.rb new file mode 100644 index 0000000..5da5a88 --- /dev/null +++ b/db/migrate/20240717175130_add_status_to_job_applications.rb @@ -0,0 +1,6 @@ +class AddStatusToJobApplications < ActiveRecord::Migration[7.1] + def change + add_column :job_applications, :status, :string, null: false, default: "no response" + add_check_constraint :job_applications, "status IN ('interviewing', 'no response', 'hired', 'not hired', 'job offer')", name: "check_valid_status" + end +end diff --git a/db/schema.rb b/db/schema.rb index fe88d4d..8d1df02 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_07_15_142927) do +ActiveRecord::Schema[7.1].define(version: 2024_07_17_175130) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -26,6 +26,9 @@ t.string "position_title", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "claimed_for_unemployment", default: false, null: false + t.string "status", default: "no response", null: false + t.check_constraint "status::text = ANY (ARRAY['interviewing'::character varying, 'no response'::character varying, 'hired'::character varying, 'not hired'::character varying, 'job offer'::character varying]::text[])", name: "check_valid_status" end create_table "posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| diff --git a/db/seeds.rb b/db/seeds.rb index 0be1a16..728f7af 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -14,6 +14,8 @@ point_of_contact: Faker::Name.name, position_type: %w[full_time part_time internship].sample, position_title: Faker::Job.title, - website_link: website_link + website_link: website_link, + claimed_for_unemployment: [true, false].sample, + status: %w[interviewing no_response not_hired job_offer].sample ) end diff --git a/test/controllers/job_applications_controller_test.rb b/test/controllers/job_applications_controller_test.rb new file mode 100644 index 0000000..2906166 --- /dev/null +++ b/test/controllers/job_applications_controller_test.rb @@ -0,0 +1,30 @@ +require "test_helper" + +class JobApplicationsControllerTest < ActionDispatch::IntegrationTest + setup do + @job_application = create(:job_application) + end + + test "should get index" do + get job_applications_url + assert_response :success + end + + test "should get new" do + get new_job_application_url + assert_response :success + end + + test "should get edit" do + get edit_job_application_url(@job_application) + assert_response :success + end + + test "should destroy job_application" do + assert_difference("JobApplication.count", -1) do + delete job_application_url(@job_application) + end + + assert_redirected_to root_url + end +end diff --git a/test/factories/job_applications_factory.rb b/test/factories/job_applications_factory.rb new file mode 100644 index 0000000..c08ad97 --- /dev/null +++ b/test/factories/job_applications_factory.rb @@ -0,0 +1,26 @@ +FactoryBot.define do + factory :job_application do + date_applied { Date.new(2023, 1, 1) } + company_name { "Example Company" } + method_of_contact { "email" } + position_type { "full_time" } + position_title { "Software Engineer" } + claimed_for_unemployment { false } + status { "interviewing" } + email_address { "example@example.com" } + point_of_contact { "John Doe" } + + trait :with_website do + method_of_contact { "internet_job_application" } + website_link { "https://example.com/jobs" } + end + + trait :claimed do + claimed_for_unemployment { true } + end + + trait :hired do + status { "hired" } + end + end +end diff --git a/test/models/job_application_test.rb b/test/models/job_application_test.rb new file mode 100644 index 0000000..ab59240 --- /dev/null +++ b/test/models/job_application_test.rb @@ -0,0 +1,120 @@ +require "test_helper" + +class JobApplicationTest < ActiveSupport::TestCase + def setup + @job_application = build(:job_application) + end + + test "job application is valid with valid attributes" do + assert @job_application.valid? + end + + test "job application is invalid without date_applied" do + @job_application.date_applied = nil + assert_not @job_application.valid? + assert_not_nil @job_application.errors[:date_applied] + end + + test "job application is invalid without company_name" do + @job_application.company_name = nil + assert_not @job_application.valid? + assert_not_nil @job_application.errors[:company_name] + end + + test "job application is invalid without method_of_contact" do + @job_application.method_of_contact = nil + assert_not @job_application.valid? + assert_not_nil @job_application.errors[:method_of_contact] + end + + test "job application is invalid without position_type" do + @job_application.position_type = nil + assert_not @job_application.valid? + assert_not_nil @job_application.errors[:position_type] + end + + test "job application is invalid without position_title" do + @job_application.position_title = nil + assert_not @job_application.valid? + assert_not_nil @job_application.errors[:position_title] + end + + test "job application requires email_address when method_of_contact is email" do + @job_application.method_of_contact = "email" + @job_application.email_address = nil + assert_not @job_application.valid? + assert_not_nil @job_application.errors[:email_address] + end + + test "job application requires website_link when method_of_contact is internet job application" do + @job_application.method_of_contact = "internet_job_application" + @job_application.website_link = nil + assert_not @job_application.valid? + assert_includes @job_application.errors[:website_link], "can't be blank" + end + + test "job application validates website_link format" do + @job_application.method_of_contact = "internet_job_application" + @job_application.website_link = "invalid-url" + assert_not @job_application.valid? + assert_not_nil @job_application.errors[:website_link] + end + + test "job application validates status inclusion" do + assert_raises(ArgumentError) do + build(:job_application, status: "invalid_status") + end + end + + test "search scope finds job applications by company_name or position_title" do + create(:job_application, company_name: "Unique Company") + create(:job_application, position_title: "Unique Position") + + assert_equal 1, JobApplication.search("Unique Company").count + assert_equal 1, JobApplication.search("Unique Position").count + assert_equal 2, JobApplication.search("Unique").count + end + + test "by_method_of_contact scope finds job applications by method_of_contact" do + JobApplication.destroy_all + create(:job_application, method_of_contact: "email") + create(:job_application, method_of_contact: "internet_job_application", website_link: "http://example.com") + + assert_equal 1, JobApplication.by_method_of_contact("email").count + assert_equal 1, JobApplication.by_method_of_contact("internet_job_application").count + end + + test "by_position_type scope finds job applications by position_type" do + JobApplication.destroy_all + create(:job_application, position_type: "full_time") + create(:job_application, position_type: "part_time") + + assert_equal 1, JobApplication.by_position_type("full_time").count + assert_equal 1, JobApplication.by_position_type("part_time").count + end + + test "by_status scope finds job applications by status" do + JobApplication.destroy_all + create(:job_application, status: "interviewing") + create(:job_application, status: "hired") + + assert_equal 1, JobApplication.by_status("interviewing").count + assert_equal 1, JobApplication.by_status("hired").count + end + + test "claimed_for_unemployment scope finds job applications claimed for unemployment" do + JobApplication.destroy_all + create(:job_application, claimed_for_unemployment: true) + create(:job_application, claimed_for_unemployment: false) + + assert_equal 1, JobApplication.claimed_for_unemployment.count + end + + test "not_claimed_for_unemployment scope finds job applications not claimed for unemployment" do + JobApplication.destroy_all + create(:job_application, claimed_for_unemployment: true) + create(:job_application, claimed_for_unemployment: false) + + assert_equal 1, JobApplication.not_claimed_for_unemployment.count + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 2b7dace..2b1502a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,7 +1,11 @@ +ENV["BT_TEST_FORMAT"] ||= "dots" ENV["RAILS_ENV"] ||= "test" require_relative "../config/environment" require "rails/test_help" require "minitest/reporters" +require "factory_bot_rails" +require "rails-controller-testing" +require "database_cleaner/active_record" reporters = [] @@ -17,6 +21,29 @@ end Minitest::Reporters.use! reporters +class ActionDispatch::IntegrationTest + setup do + # Mock Vite helpers + ActionView::Base.class_eval do + def vite_javascript_tag(*args) + "" + end + + def vite_client_tag + "" + end + + def vite_stylesheet_tag(*args) + "" + end + + def vite_asset_path(*args) + "" + end + end + end +end + module ActiveSupport class TestCase # Run tests in parallel with specified workers @@ -26,6 +53,17 @@ class TestCase fixtures :all # Add more helper methods to be used by all tests here... + include FactoryBot::Syntax::Methods + + DatabaseCleaner.strategy = :transaction + + setup do + DatabaseCleaner.start + end + + teardown do + DatabaseCleaner.clean + end end end