diff --git a/.gitignore b/.gitignore index a24ce3bb3..379d5d1b2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ /coverage/ /doc/ /pkg/ +/spec/dummy/log/ +/spec/dummy/tmp/ /spec/examples.txt /spec/reports/ /tmp/ diff --git a/.rubocop.yml b/.rubocop.yml index 116e90678..5a2b7ff57 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -47,6 +47,7 @@ Sorbet/TrueSigil: - "**/*.rake" Exclude: - "lib/ruby_lsp/tapioca/server_addon.rb" + - "spec/dummy/**/*.rb" Style/CaseEquality: Enabled: false diff --git a/sorbet/rbi/shims/minitest.rbi b/sorbet/rbi/shims/minitest.rbi new file mode 100644 index 000000000..be8fff170 --- /dev/null +++ b/sorbet/rbi/shims/minitest.rbi @@ -0,0 +1,8 @@ +# typed: true + +# Can be removed once https://github.com/Shopify/rbi-central/pull/300 ships + +class Minitest::HooksSpec + sig { params(arg: Symbol, block: T.proc.bind(T.attached_class).void).void } + def self.after(arg, &block); end +end diff --git a/spec/dummy/app/jobs/notify_user_job.rb b/spec/dummy/app/jobs/notify_user_job.rb new file mode 100644 index 000000000..dc9683c3f --- /dev/null +++ b/spec/dummy/app/jobs/notify_user_job.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class NotifyUserJob < ActiveJob::Base + def perform(user) + end +end diff --git a/spec/dummy/bin/rails b/spec/dummy/bin/rails new file mode 100755 index 000000000..f8883e3fe --- /dev/null +++ b/spec/dummy/bin/rails @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# This command will automatically be run when you run "rails" with Rails gems +# installed from the root of your application. + +ENGINE_ROOT = File.expand_path("..", __dir__) +APP_PATH = File.expand_path("../config/application", __dir__) + +# Set up gems listed in the Gemfile. +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) + +require "rails/engine/commands" diff --git a/spec/dummy/config.ru b/spec/dummy/config.ru new file mode 100644 index 000000000..b5a779e16 --- /dev/null +++ b/spec/dummy/config.ru @@ -0,0 +1,7 @@ +# typed: strict +# frozen_string_literal: true + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb new file mode 100644 index 000000000..a37de563d --- /dev/null +++ b/spec/dummy/config/application.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) + +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) +$LOAD_PATH.unshift(File.expand_path("../../../lib", __dir__)) + +require "rails" # minimal, instead of "rails/all" +require "active_job/railtie" + +Bundler.require(*Rails.groups) + +module Dummy + class Application < Rails::Application + end +end diff --git a/spec/dummy/config/environment.rb b/spec/dummy/config/environment.rb new file mode 100644 index 000000000..e8173e052 --- /dev/null +++ b/spec/dummy/config/environment.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative "application" + +Rails.application.initialize! diff --git a/spec/spec_with_project.rb b/spec/spec_with_project.rb index 8d6a2646d..3413914a5 100644 --- a/spec/spec_with_project.rb +++ b/spec/spec_with_project.rb @@ -11,7 +11,7 @@ class SpecWithProject < Minitest::HooksSpec # Spec lifecycle - # TODO: Replace by `before(:all)` once Sorbet understands it + # TODO: Replace with `before(:all)` once Sorbet understands it sig { params(args: T.untyped).void } def initialize(*args) super(*T.unsafe(args)) diff --git a/spec/tapioca/addon_spec.rb b/spec/tapioca/addon_spec.rb new file mode 100644 index 000000000..0a6d212c3 --- /dev/null +++ b/spec/tapioca/addon_spec.rb @@ -0,0 +1,53 @@ +# typed: true +# frozen_string_literal: true + +require "spec_helper" +require "language_server-protocol" +require "ruby_lsp/utils" +require "ruby_lsp/ruby_lsp_rails/runner_client" +require "minitest/hooks" + +module RubyLsp + module Tapioca + class AddonSpec < Minitest::HooksSpec + # The approach here is based on tests within the Ruby LSP Rails gem + + # TODO: Replace with `before(:all)` once Sorbet understands it + def initialize(*args) + super(*T.unsafe(args)) + @outgoing_queue = Thread::Queue.new + @client = T.let(nil, T.nilable(RubyLsp::Rails::RunnerClient)) + FileUtils.chdir("spec/dummy") do + @client = RubyLsp::Rails::RunnerClient.new(@outgoing_queue) + end + end + + after(:all) do + T.must(@client).shutdown + + assert_predicate(@client, :stopped?) + @outgoing_queue.close + end + + it "generates DSL RBIs for a given constant" do + addon_path = File.expand_path("lib/ruby_lsp/tapioca/server_addon.rb") + T.must(@client).register_server_addon(File.expand_path(addon_path)) + T.must(@client).delegate_notification( + server_addon_name: "Tapioca", + request_name: "dsl", + constants: ["NotifyUserJob"], + ) + + begin + Timeout.timeout(10) do + sleep(1) until File.exist?("spec/dummy/sorbet/rbi/dsl/notify_user_job.rbi") + end + rescue Timeout::Error + flunk("RBI file was not generated") + end + ensure + FileUtils.rm_rf("spec/dummy/sorbet/rbi") + end + end + end +end