DEV: Introduce core features system specs for plugins

This patch adds a new shared example to be used as a smoke test in
plugins and themes.

A `skip_examples` argument is available to easily opt-out from a
category of tests.

Example:
```rb
RSpec.describe "Testing core features", type: :system do
  it_behaves_like "having working core features", skip_examples: %i[search login]
end
```
This commit is contained in:
Loïc Guitaut
2025-03-27 12:12:01 +01:00
committed by Loïc Guitaut
parent 7b9976795e
commit 4f82ceaf39
7 changed files with 269 additions and 67 deletions
-62
View File
@@ -10,66 +10,4 @@ module OneboxHelpers
preview = Nokogiri::HTML::DocumentFragment.parse(raw_fragment)
preview.css(tag_name).first[attribute]
end
RSpec.shared_context "with engines" do
let(:onebox) { described_class.new(link) }
let(:html) { onebox.to_html }
let(:data) { onebox.send(:data).deep_symbolize_keys }
let(:link) { @link }
let(:uri) { defined?(@uri) ? @uri : link }
before do
fixture = defined?(@onebox_fixture) ? @onebox_fixture : described_class.onebox_name
stub_request(:get, uri).to_return(status: 200, body: onebox_response(fixture))
end
end
RSpec.shared_examples_for "an engine" do
it "responds to data" do
expect(described_class.private_instance_methods).to include(:data)
end
it "correctly matches the url" do
onebox = Onebox::Matcher.new(link, { allowed_iframe_regexes: [/.*/] }).oneboxed
expect(onebox).to be(described_class)
end
describe "#data" do
it "includes title" do
expect(data[:title]).not_to be_nil
end
it "includes link" do
expect(data[:link]).not_to be_nil
end
it "is serializable" do
expect { Marshal.dump(data) }.to_not raise_error
end
end
end
RSpec.shared_examples_for "a layout engine" do
describe "#to_html" do
it "includes subname" do
expect(html).to include(%|<aside class="onebox #{described_class.onebox_name}">|)
end
it "includes title" do
expect(html).to include(data[:title])
end
it "includes link" do
expect(html).to include(%|class="link" href="#{data[:link]}|)
end
it "includes badge" do
expect(html).to include(%|<strong class="name">#{data[:badge]}</strong>|)
end
it "includes domain" do
expect(html).to include(%|class="domain" href="#{data[:domain]}|)
end
end
end
end
@@ -1,6 +1,6 @@
# frozen_string_literal: true
shared_examples "basic reviewable attributes" do
RSpec.shared_examples "basic reviewable attributes" do
describe "#id" do
it "equals the reviewable's id" do
expect(subject[:id]).to eq(reviewable.id)
@@ -0,0 +1,201 @@
# frozen_string_literal: true
RSpec.shared_examples_for "having working core features" do |skip_examples: []|
fab!(:category) { Fabricate(:category, name: "General") }
fab!(:topics) { Fabricate.times(3, :topic_with_op, category:) }
fab!(:topic)
fab!(:active_user) { Fabricate(:active_user, password: "secure_password") }
let(:composer) { PageObjects::Components::Composer.new }
if skip_examples.exclude?(:login)
describe "Login" do
let(:login_form) { PageObjects::Modals::Login.new }
before { EmailToken.confirm(Fabricate(:email_token, user: active_user).token) }
it "logs in" do
visit("/")
login_form
.open
.fill(username: active_user.username, password: "secure_password")
.click_login
expect(page).to have_css(".current-user", visible: true)
end
it "displays a login button in the header" do
visit("/")
expect(page).to have_css("header .login-button", visible: true)
end
end
end
if skip_examples.exclude?(:topics)
describe "Topics" do
context "with an anonymous user" do
before { visit "/" }
it "lists latest topics" do
expect(page).to have_css(".topic-list-item", count: 4)
end
it "lists topics for a category" do
within("#sidebar-section-content-categories") { click_on("General") }
expect(page).to have_css(".topic-list-item", count: 3)
end
it "displays a specific topic" do
click_on(topics.first.title)
expect(page).to have_content(topics.first.title)
expect(page).to have_content(topics.first.first_post.raw)
end
end
context "with a logged in user" do
before do
sign_in(active_user)
visit "/"
end
it "lists latest topics" do
expect(page).to have_css(".topic-list-item", count: 4)
end
it "lists topics for a category" do
within("#sidebar-section-content-categories") { click_on("General") }
expect(page).to have_css(".topic-list-item", count: 3)
end
it "displays a specific topic" do
click_on(topics.first.title)
expect(page).to have_content(topics.first.title)
expect(page).to have_content(topics.first.first_post.raw)
end
it "replies in a topic" do
click_on(topics.first.title)
expect(page).to have_content(topics.first.first_post.raw)
within(".actions") { click_button("Reply") }
composer.focus
send_keys("This is a long enough reply.")
expect(page).to have_css(".d-editor-preview p", visible: true)
within(".save-or-cancel") { click_button("Reply") }
expect(page).to have_content("This is a long enough reply.")
end
it "creates a new topic" do
find("#create-topic", visible: true).click
composer.fill_title("This is a new topic")
composer.fill_content("This is a long enough sentence.")
expect(page).to have_css(".d-editor-preview p", visible: true)
within(".save-or-cancel") { click_button("Create Topic") }
expect(page).to have_content("This is a new topic")
expect(page).to have_content("This is a long enough sentence.")
end
end
end
end
if skip_examples.exclude?(:likes)
describe "Likes" do
before do
sign_in(active_user)
visit "/"
end
it "likes a post" do
click_on(topics.first.title)
within(".double-button") do
find(".toggle-like").click
expect(page).to have_content("1")
end
end
end
end
if skip_examples.exclude?(:profile)
describe "User profile" do
fab!(:user)
before { UserStat.update_all(post_count: 1) }
context "with an anonymous user" do
it "displays a users profile" do
visit("/u/#{user.username}/summary")
expect(page).to have_content(user.name)
expect(page).to have_content("Activity")
end
end
context "with a logged in user" do
before { sign_in(active_user) }
it "displays a users profile" do
visit("/u/#{user.username}/summary")
expect(page).to have_content(user.name)
expect(page).to have_content("Message")
end
it "displays the users own profile" do
visit("/u/#{active_user.username}/summary")
expect(page).to have_content(active_user.name)
expect(page).to have_content("Preferences")
end
end
end
end
if skip_examples.exclude?(:search)
describe "Search" do
let(:search_page) { PageObjects::Pages::Search.new }
before do
SearchIndexer.enable
topics.each { SearchIndexer.index(_1, force: true) }
SiteSetting.enable_welcome_banner = false
end
after { SearchIndexer.disable }
context "with an anonymous user" do
it "searches using the quick search" do
visit("/")
search_page.click_search_icon
search_page.type_in_search_menu(topics.first.title)
search_page.click_search_menu_link
expect(search_page).to have_topic_title_for_first_search_result(topics.first.title)
end
it "searches using the full page search" do
visit("/search")
search_page.type_in_search(topics.first.title)
search_page.click_search_button
expect(search_page).to have_search_result
end
end
context "with a logged in user" do
before { sign_in(active_user) }
it "searches using the quick search" do
visit("/")
search_page.click_search_icon
search_page.type_in_search_menu(topics.first.title)
search_page.click_search_menu_link
expect(search_page).to have_topic_title_for_first_search_result(topics.first.title)
end
it "searches using the full page search" do
visit("/search")
search_page.type_in_search(topics.first.title)
search_page.click_search_button
expect(search_page).to have_search_result
end
end
end
end
end
+63
View File
@@ -0,0 +1,63 @@
# frozen_string_literal: true
RSpec.shared_context "with engines" do
let(:onebox) { described_class.new(link) }
let(:html) { onebox.to_html }
let(:data) { onebox.send(:data).deep_symbolize_keys }
let(:link) { @link }
let(:uri) { defined?(@uri) ? @uri : link }
before do
fixture = defined?(@onebox_fixture) ? @onebox_fixture : described_class.onebox_name
stub_request(:get, uri).to_return(status: 200, body: onebox_response(fixture))
end
end
RSpec.shared_examples_for "an engine" do
it "responds to data" do
expect(described_class.private_instance_methods).to include(:data)
end
it "correctly matches the url" do
onebox = Onebox::Matcher.new(link, { allowed_iframe_regexes: [/.*/] }).oneboxed
expect(onebox).to be(described_class)
end
describe "#data" do
it "includes title" do
expect(data[:title]).not_to be_nil
end
it "includes link" do
expect(data[:link]).not_to be_nil
end
it "is serializable" do
expect { Marshal.dump(data) }.to_not raise_error
end
end
end
RSpec.shared_examples_for "a layout engine" do
describe "#to_html" do
it "includes subname" do
expect(html).to include(%|<aside class="onebox #{described_class.onebox_name}">|)
end
it "includes title" do
expect(html).to include(data[:title])
end
it "includes link" do
expect(html).to include(%|class="link" href="#{data[:link]}|)
end
it "includes badge" do
expect(html).to include(%|<strong class="name">#{data[:badge]}</strong>|)
end
it "includes domain" do
expect(html).to include(%|class="domain" href="#{data[:domain]}|)
end
end
end
@@ -100,13 +100,13 @@ RSpec.shared_examples "User Sidebar Serializer Attributes" do |serializer_klass|
describe "#display_sidebar_tags" do
fab!(:tag)
it "should not be included in serialised object when tagging has been disabled" do
it "is not included in serialised object when tagging has been disabled" do
SiteSetting.tagging_enabled = false
expect(serializer.as_json[:display_sidebar_tags]).to eq(nil)
end
it "should be true when user has visible tags" do
it "returns true when user has visible tags" do
SiteSetting.tagging_enabled = true
Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [tag.name])
@@ -115,7 +115,7 @@ RSpec.shared_examples "User Sidebar Serializer Attributes" do |serializer_klass|
expect(serializer.as_json[:display_sidebar_tags]).to eq(true)
end
it "should be false when user has no visible tags" do
it "returns false when user has no visible tags" do
SiteSetting.tagging_enabled = true
Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [tag.name])
@@ -3,7 +3,7 @@
RSpec.shared_examples_for "a versioned model" do
let(:model) { Fabricate(described_class.to_s.downcase) }
it "should be versioned" do
it "is versioned" do
expect(model).to respond_to(:version)
expect(model.version).to eq(1)
end