mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
[64635] Format enterprise tokens table according to their statuses
https://community.openproject.org/wp/64635 This follows the design on Figma: - 4 columns: subscription, active users, domain, dates - subscription shows plan name, subscriber, and some labels depending on the token validity and attributes - the dates are highlighted if they have an influence on the token status
This commit is contained in:
@@ -31,29 +31,63 @@
|
||||
module Admin::EnterpriseTokens
|
||||
class RowComponent < ::OpPrimer::BorderBoxRowComponent
|
||||
alias :token :model
|
||||
delegate :subscriber, :domain, to: :token
|
||||
|
||||
def email
|
||||
token.mail
|
||||
end
|
||||
|
||||
def plan
|
||||
helpers.enterprise_token_plan_name(token)
|
||||
end
|
||||
|
||||
def max_active_users
|
||||
if token.unlimited_users?
|
||||
I18n.t("js.admin.enterprise.upsell.unlimited")
|
||||
def column_css_class(column)
|
||||
if column == :dates
|
||||
"#{super} -no-ellipsis"
|
||||
else
|
||||
token.max_active_users
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Subscription column
|
||||
def subscription
|
||||
render(Primer::Box.new(classes: "d-flex flex-column gap-1")) do
|
||||
concat plan_name_with_statuses_html
|
||||
concat subscriber_html
|
||||
end
|
||||
end
|
||||
|
||||
def plan_name_with_statuses_html
|
||||
render(Primer::Box.new(classes: "d-flex flex-row gap-1 flex-items-baseline")) do
|
||||
concat primer_text(plan_name, font_weight: :bold)
|
||||
token.statuses.each do |status|
|
||||
concat token_status_label_html(status)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def subscriber_html
|
||||
primer_text(token.subscriber, tag: :div)
|
||||
end
|
||||
|
||||
def token_status_label_html(status)
|
||||
primer_label(I18n.t("admin.enterprise.status.#{status}"), scheme: status_scheme(status))
|
||||
end
|
||||
|
||||
# Active users column
|
||||
def max_active_users
|
||||
count =
|
||||
if token.unlimited_users?
|
||||
I18n.t("js.admin.enterprise.upsell.unlimited")
|
||||
else
|
||||
token.max_active_users.to_s
|
||||
end
|
||||
primer_text(count, color: :subtle)
|
||||
end
|
||||
|
||||
# Domain column
|
||||
def domain
|
||||
primer_text(token.domain, color: :subtle)
|
||||
end
|
||||
|
||||
# Dates column
|
||||
def dates
|
||||
[
|
||||
helpers.format_date(token.starts_at),
|
||||
token.will_expire? ? helpers.format_date(token.expires_at) : I18n.t("js.admin.enterprise.upsell.unlimited")
|
||||
].join(" – ")
|
||||
capture do
|
||||
concat(primer_text(start_date, color: start_date_color))
|
||||
concat(primer_text(" – "))
|
||||
concat(primer_text(expiration_date, color: expiration_date_color))
|
||||
end
|
||||
end
|
||||
|
||||
def button_links
|
||||
@@ -86,5 +120,59 @@ module Admin::EnterpriseTokens
|
||||
item.with_leading_visual_icon(icon: :trash)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def plan_name
|
||||
helpers.enterprise_token_plan_name(token)
|
||||
end
|
||||
|
||||
def status_scheme(status)
|
||||
case status
|
||||
when :trial
|
||||
:accent
|
||||
when :invalid_domain, :expired
|
||||
:danger
|
||||
when :not_active, :expiring_soon, :in_grace_period
|
||||
:attention
|
||||
end
|
||||
end
|
||||
|
||||
def start_date
|
||||
helpers.format_date(token.starts_at)
|
||||
end
|
||||
|
||||
def expiration_date
|
||||
token.will_expire? ? helpers.format_date(token.expires_at) : I18n.t("js.admin.enterprise.upsell.unlimited")
|
||||
end
|
||||
|
||||
def start_date_color
|
||||
if token.started?
|
||||
:subtle
|
||||
else
|
||||
:attention
|
||||
end
|
||||
end
|
||||
|
||||
def expiration_date_color
|
||||
if token.expiring_soon? || token.in_grace_period?
|
||||
:attention
|
||||
elsif token.expired?
|
||||
:danger
|
||||
else
|
||||
:subtle
|
||||
end
|
||||
end
|
||||
|
||||
def primer_text(text, color: :subtle, **options)
|
||||
if color != :subtle
|
||||
options = options.reverse_merge(font_weight: :bold)
|
||||
end
|
||||
render(Primer::Beta::Text.new(color:, **options)) { text }
|
||||
end
|
||||
|
||||
def primer_label(text, scheme:)
|
||||
render(Primer::Beta::Label.new(scheme:)) { text }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
|
||||
module Admin::EnterpriseTokens
|
||||
class TableComponent < ::OpPrimer::BorderBoxTableComponent
|
||||
columns :plan, :subscriber, :max_active_users, :email, :domain, :dates
|
||||
columns :subscription, :max_active_users, :domain, :dates
|
||||
|
||||
mobile_columns :plan, :subscriber, :max_active_users, :dates
|
||||
mobile_columns :subscription, :max_active_users, :dates
|
||||
|
||||
mobile_labels :project_name
|
||||
|
||||
main_column :plan
|
||||
main_column :subscription
|
||||
|
||||
def sortable?
|
||||
false
|
||||
@@ -56,10 +56,8 @@ module Admin::EnterpriseTokens
|
||||
|
||||
def headers
|
||||
@headers ||= [
|
||||
[:plan, { caption: EnterpriseToken.human_attribute_name(:plan) }],
|
||||
[:subscriber, { caption: EnterpriseToken.human_attribute_name(:subscriber) }],
|
||||
[:subscription, { caption: EnterpriseToken.human_attribute_name(:subscription) }],
|
||||
[:max_active_users, { caption: EnterpriseToken.human_attribute_name(:active_user_count_restriction) }],
|
||||
[:email, { caption: EnterpriseToken.human_attribute_name(:email) }],
|
||||
[:domain, { caption: EnterpriseToken.human_attribute_name(:domain) }],
|
||||
[:dates, { caption: I18n.t(:label_dates) }]
|
||||
].compact
|
||||
|
||||
@@ -59,11 +59,7 @@ module EnterpriseHelper
|
||||
|
||||
def enterprise_token_plan_name(enterprise_token)
|
||||
plan = enterprise_token.plan.to_s
|
||||
|
||||
<<~LABEL.squish
|
||||
#{I18n.t(plan, scope: [:enterprise_plans], default: plan.humanize)}
|
||||
(#{I18n.t(:label_token_version)} #{enterprise_token.version})
|
||||
LABEL
|
||||
I18n.t(plan, scope: [:enterprise_plans], default: plan.humanize)
|
||||
end
|
||||
|
||||
def enterprise_plan_additional_features(enterprise_token)
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
class EnterpriseToken < ApplicationRecord
|
||||
EXPIRING_SOON_DAYS = 30
|
||||
|
||||
class << self
|
||||
def all_tokens
|
||||
all.sort_by(&:sort_key)
|
||||
@@ -167,10 +169,41 @@ class EnterpriseToken < ApplicationRecord
|
||||
|
||||
delegate :clear_current_tokens_cache, to: :EnterpriseToken
|
||||
|
||||
def expiring_soon?
|
||||
token_object.will_expire? \
|
||||
&& token_object.active?(reprieve: false) \
|
||||
&& token_object.expires_at <= EXPIRING_SOON_DAYS.days.from_now
|
||||
end
|
||||
|
||||
def in_grace_period?
|
||||
token_object.expired?(reprieve: false) \
|
||||
&& !token_object.expired?(reprieve: true)
|
||||
end
|
||||
|
||||
def expired?(reprieve: true)
|
||||
token_object.expired?(reprieve:)
|
||||
end
|
||||
|
||||
def statuses
|
||||
statuses = []
|
||||
if trial?
|
||||
statuses << :trial
|
||||
end
|
||||
if invalid_domain?
|
||||
statuses << :invalid_domain
|
||||
end
|
||||
if !started?
|
||||
statuses << :not_active
|
||||
elsif expiring_soon?
|
||||
statuses << :expiring_soon
|
||||
elsif in_grace_period?
|
||||
statuses << :in_grace_period
|
||||
elsif expired?
|
||||
statuses << :expired
|
||||
end
|
||||
statuses
|
||||
end
|
||||
|
||||
##
|
||||
# The domain is only validated for tokens from version 2.0 onwards.
|
||||
def invalid_domain?
|
||||
|
||||
@@ -113,6 +113,13 @@ en:
|
||||
contact: "Contact us for a demo"
|
||||
enterprise_info_html: "is an Enterprise <span class='spot-icon spot-icon_inline spot-icon_enterprise-addons'></span> add-on."
|
||||
upgrade_info: "Please upgrade to a paid plan to activate and start using it in your team."
|
||||
status:
|
||||
expired: "Expired"
|
||||
expiring_soon: "Expiring soon"
|
||||
in_grace_period: "In grace period"
|
||||
invalid_domain: "Invalid domain"
|
||||
not_active: "Not active"
|
||||
trial: "Trial"
|
||||
jemalloc_allocator: Jemalloc memory allocator
|
||||
journal_aggregation:
|
||||
explanation:
|
||||
@@ -1003,9 +1010,10 @@ en:
|
||||
enterprise_token:
|
||||
starts_at: "Valid since"
|
||||
subscriber: "Subscriber"
|
||||
subscription: "Subscription"
|
||||
plan: "Plan"
|
||||
encoded_token: "Enterprise support token"
|
||||
active_user_count_restriction: "Maximum active users"
|
||||
active_user_count_restriction: "Active users"
|
||||
enterprise_trial:
|
||||
company: "Company"
|
||||
grids/grid:
|
||||
|
||||
@@ -76,7 +76,7 @@ a
|
||||
.-break-word
|
||||
word-wrap: break-word
|
||||
|
||||
.ellipsis,
|
||||
.ellipsis:not(.-no-ellipsis),
|
||||
.form--field.ellipsis .form--label
|
||||
@include text-shortener
|
||||
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Admin::EnterpriseTokens::TableComponent, type: :component do
|
||||
include EnterpriseHelper
|
||||
include EnterpriseTokenFactory
|
||||
include Redmine::I18n
|
||||
|
||||
let(:token) { create_enterprise_token }
|
||||
let(:tokens) { [token] }
|
||||
|
||||
subject(:component) { described_class.new(rows: tokens) }
|
||||
|
||||
it "renders multiple tokens in a table with plan and subscriber name in the subscription column" do
|
||||
expired_token = create_enterprise_token(subscriber: "My company", expires_at: 1.month.ago)
|
||||
active_token = create_enterprise_token(subscriber: "My company", plan: "Premium")
|
||||
component = described_class.new(rows: [expired_token, active_token])
|
||||
|
||||
render_inline(component)
|
||||
|
||||
expect(page).to have_text(enterprise_token_plan_name(expired_token), count: 1)
|
||||
expect(page).to have_text(enterprise_token_plan_name(active_token), count: 1)
|
||||
expect(page).to have_text("My company", count: 2)
|
||||
end
|
||||
|
||||
def first_subscription_cell
|
||||
page.first(".subscription")
|
||||
end
|
||||
|
||||
def subscription_cells
|
||||
page.all(".subscription")
|
||||
end
|
||||
|
||||
def first_dates_cell
|
||||
page.first(".dates")
|
||||
end
|
||||
|
||||
def dates_cells
|
||||
page.all(".dates")
|
||||
end
|
||||
|
||||
context "when a token is a trial" do
|
||||
let(:tokens) do
|
||||
[
|
||||
create_enterprise_token(trial: true),
|
||||
create_enterprise_token(trial: true, expires_at: Date.current.prev_day(60))
|
||||
]
|
||||
end
|
||||
|
||||
it "displays a 'Trial' accent label in the subscription column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(subscription_cells).to all(have_primer_label("Trial", scheme: "accent"))
|
||||
end
|
||||
end
|
||||
|
||||
context "when a token has invalid domain" do
|
||||
let(:token) { create_enterprise_token(domain: "invalid.com") }
|
||||
|
||||
it "displays a 'Invalid domain' danger label in the subscription column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(first_subscription_cell).to have_primer_label(count: 1)
|
||||
expect(first_subscription_cell).to have_primer_label("Invalid domain", scheme: "danger")
|
||||
end
|
||||
end
|
||||
|
||||
context "when a token is not active yet" do
|
||||
let(:token) { create_enterprise_token(starts_at: Date.current.next_day(30)) }
|
||||
|
||||
it "displays a 'Not active' attention label in the subscription column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(first_subscription_cell).to have_primer_label(count: 1)
|
||||
expect(first_subscription_cell).to have_primer_label("Not active", scheme: "attention")
|
||||
end
|
||||
|
||||
it "has start date in :attention color in the dates column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(first_dates_cell).to have_primer_text(format_date(token.starts_at), color: "attention")
|
||||
.and have_primer_text(format_date(token.expires_at), color: "subtle")
|
||||
end
|
||||
end
|
||||
|
||||
context "when a token is active and nearing expiration date" do
|
||||
let(:tokens) do
|
||||
[
|
||||
create_enterprise_token(expires_at: Date.current),
|
||||
create_enterprise_token(expires_at: Date.current.next_day(15))
|
||||
]
|
||||
end
|
||||
|
||||
it "displays a 'Expiring soon' attention label in the subscription column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(subscription_cells).to all(have_primer_label(count: 1))
|
||||
expect(subscription_cells).to all(have_primer_label("Expiring soon", scheme: "attention"))
|
||||
end
|
||||
|
||||
it "has expiry date in :attention color in the dates column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(dates_cells[0]).to have_primer_text(format_date(tokens.first.starts_at), color: "subtle")
|
||||
.and have_primer_text(format_date(tokens.first.expires_at), color: "attention")
|
||||
expect(dates_cells[1]).to have_primer_text(format_date(tokens.second.starts_at), color: "subtle")
|
||||
.and have_primer_text(format_date(tokens.second.expires_at), color: "attention")
|
||||
end
|
||||
end
|
||||
|
||||
context "when a token reached expiration date but is within the reprieve days" do
|
||||
let(:tokens) do
|
||||
[
|
||||
create_enterprise_token(reprieve_days: 5, expires_at: Date.current.prev_day(1)),
|
||||
create_enterprise_token(reprieve_days: 5, expires_at: Date.current.prev_day(5))
|
||||
]
|
||||
end
|
||||
|
||||
it "displays a 'In grace period' attention label in the subscription column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(subscription_cells).to all(have_primer_label(count: 1))
|
||||
expect(subscription_cells).to all(have_primer_label("In grace period", scheme: "attention"))
|
||||
end
|
||||
|
||||
it "has expiry date in :attention color in the dates column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(dates_cells[0]).to have_primer_text(format_date(tokens.first.starts_at), color: "subtle")
|
||||
.and have_primer_text(format_date(tokens.first.expires_at), color: "attention")
|
||||
expect(dates_cells[1]).to have_primer_text(format_date(tokens.second.starts_at), color: "subtle")
|
||||
.and have_primer_text(format_date(tokens.second.expires_at), color: "attention")
|
||||
end
|
||||
end
|
||||
|
||||
context "when a token is fully expired" do
|
||||
let(:tokens) do
|
||||
[
|
||||
create_enterprise_token(expires_at: Date.current.prev_day(1)),
|
||||
create_enterprise_token(expires_at: Date.current.prev_day(5), reprieve_days: 4),
|
||||
create_enterprise_token(expires_at: Date.current.prev_day(42), reprieve_days: 30)
|
||||
]
|
||||
end
|
||||
|
||||
it "displays a 'Expired' danger label in the subscription column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(subscription_cells).to all(have_primer_label(count: 1))
|
||||
expect(subscription_cells).to all(have_primer_label("Expired", scheme: "danger"))
|
||||
end
|
||||
|
||||
it "has expiry date in :danger color in the dates column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(dates_cells[0]).to have_primer_text(format_date(tokens.first.starts_at), color: "subtle")
|
||||
.and have_primer_text(format_date(tokens.first.expires_at), color: "danger")
|
||||
expect(dates_cells[1]).to have_primer_text(format_date(tokens.second.starts_at), color: "subtle")
|
||||
.and have_primer_text(format_date(tokens.second.expires_at), color: "danger")
|
||||
expect(dates_cells[2]).to have_primer_text(format_date(tokens.third.starts_at), color: "subtle")
|
||||
.and have_primer_text(format_date(tokens.third.expires_at), color: "danger")
|
||||
end
|
||||
end
|
||||
|
||||
context "when token does not expire" do
|
||||
let(:token) do
|
||||
create_enterprise_token(expires_at: nil)
|
||||
end
|
||||
|
||||
it "displays a 'Unlimited' label in the subscription column" do
|
||||
render_inline(component)
|
||||
|
||||
expect(first_dates_cell).to have_primer_text("Unlimited", color: "subtle")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -34,8 +34,8 @@ RSpec.describe EnterpriseHelper do
|
||||
describe "#enterprise_token_plan_name" do
|
||||
let(:token) { instance_double(EnterpriseToken, plan: :legacy_enterprise, version: "4.0.0") }
|
||||
|
||||
it "returns the correct string" do
|
||||
expect(helper.enterprise_token_plan_name(token)).to eq("Enterprise Plan (Token Version 4.0.0)")
|
||||
it "returns the plan name" do
|
||||
expect(helper.enterprise_token_plan_name(token)).to eq("Enterprise Plan")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -558,6 +558,70 @@ RSpec.describe EnterpriseToken do
|
||||
end
|
||||
end
|
||||
|
||||
describe "#expiring_soon?" do
|
||||
context "when token expiration date is within 30 days" do
|
||||
it "returns true" do
|
||||
expect(build_enterprise_token(expires_at: Date.current)).to be_expiring_soon
|
||||
expect(build_enterprise_token(expires_at: Date.current.next_day(10))).to be_expiring_soon
|
||||
expect(build_enterprise_token(expires_at: Date.current.next_day(30))).to be_expiring_soon
|
||||
end
|
||||
end
|
||||
|
||||
context "when token has no expiration date" do
|
||||
it "returns false" do
|
||||
expect(build_enterprise_token(expires_at: nil)).not_to be_expiring_soon
|
||||
end
|
||||
end
|
||||
|
||||
context "when token expiration date is within 30 days but token is not active yet" do
|
||||
it "returns false" do
|
||||
expect(build_enterprise_token(starts_at: Date.tomorrow, expires_at: Date.current.next_day(20))).not_to be_expiring_soon
|
||||
end
|
||||
end
|
||||
|
||||
context "when token is expired but in grace period" do
|
||||
it "returns false" do
|
||||
expect(build_enterprise_token(expires_at: Date.yesterday, reprieve_days: 1)).not_to be_expiring_soon
|
||||
end
|
||||
end
|
||||
|
||||
context "when token is expired" do
|
||||
it "returns false" do
|
||||
expect(build_enterprise_token(expires_at: Date.current.prev_day(10), reprieve_days: 5)).not_to be_expiring_soon
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#in_grace_period?" do
|
||||
context "when token has no expiration date" do
|
||||
it "returns false" do
|
||||
expect(build_enterprise_token(expires_at: nil)).not_to be_in_grace_period
|
||||
end
|
||||
end
|
||||
|
||||
context "when token expiration date is today or in the future" do
|
||||
it "returns false" do
|
||||
expect(build_enterprise_token(expires_at: Date.current, reprieve_days: 100)).not_to be_in_grace_period
|
||||
expect(build_enterprise_token(expires_at: Date.tomorrow, reprieve_days: 100)).not_to be_in_grace_period
|
||||
end
|
||||
end
|
||||
|
||||
context "when token expiration date is in the past within reprieve_days days" do
|
||||
it "returns true" do
|
||||
expect(build_enterprise_token(expires_at: Date.yesterday, reprieve_days: 1)).to be_in_grace_period
|
||||
expect(build_enterprise_token(expires_at: Date.current.prev_day(10), reprieve_days: 10)).to be_in_grace_period
|
||||
expect(build_enterprise_token(expires_at: Date.current.prev_day(10), reprieve_days: 20)).to be_in_grace_period
|
||||
end
|
||||
end
|
||||
|
||||
context "when token expiration date is in the past outside of reprieve_days days" do
|
||||
it "returns false" do
|
||||
expect(build_enterprise_token(expires_at: Date.yesterday, reprieve_days: 0)).not_to be_in_grace_period
|
||||
expect(build_enterprise_token(expires_at: Date.current.prev_day(10), reprieve_days: 9)).not_to be_in_grace_period
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#expired?" do
|
||||
context "when token has no expiration date" do
|
||||
let(:token) { build_enterprise_token(expires_at: nil) }
|
||||
|
||||
@@ -32,7 +32,7 @@ require "spec_helper"
|
||||
|
||||
RSpec.describe Authorization::EnterpriseService do
|
||||
let(:instance) { described_class.new(token) }
|
||||
let(:token) { instance_double(EnterpriseToken, token_object:, expired?: expired?) }
|
||||
let(:token) { instance_double(EnterpriseToken, token_object:, expired?: expired?, invalid_domain?: false) }
|
||||
let(:token_object) { OpenProject::Token.new }
|
||||
let(:feature) { "some_feature" }
|
||||
let(:expired?) { false }
|
||||
|
||||
@@ -46,8 +46,16 @@ Capybara.add_selector :primer_label, locator_type: [String, Symbol] do
|
||||
text.public_send(exact ? :eql? : :include?, locator.to_s)
|
||||
end
|
||||
|
||||
expression_filter :scheme do |expr, scheme|
|
||||
builder(expr).add_attribute_conditions(class: "Label--#{scheme.downcase}")
|
||||
# Use `node_filter` instead of `expression_filter` to have a better failure
|
||||
# message when the selector fails: `expression_filter` modifies the initial
|
||||
# query and elements without the expected scheme are not returned.
|
||||
# `node_filter` applies the filter on the elements returned by the query so
|
||||
# that error message can list them if none matches.
|
||||
node_filter :scheme do |node, scheme|
|
||||
actual = node[:class].scan(/(?<=Label--)[\w-]+/).first
|
||||
(actual&.downcase == scheme.downcase).tap do |res|
|
||||
add_error("Expected scheme to be #{scheme.inspect} but was #{actual.inspect}") unless res
|
||||
end
|
||||
end
|
||||
|
||||
describe_expression_filters do |scheme: nil, **|
|
||||
@@ -55,6 +63,35 @@ Capybara.add_selector :primer_label, locator_type: [String, Symbol] do
|
||||
end
|
||||
end
|
||||
|
||||
Capybara.add_selector :primer_text, locator_type: [String] do
|
||||
label "Primer Text"
|
||||
|
||||
xpath do |locator, **|
|
||||
xpath = XPath.descendant(:span)
|
||||
unless locator.nil?
|
||||
locator = locator.to_s
|
||||
xpath = xpath[XPath.string.n.is(locator)]
|
||||
end
|
||||
xpath
|
||||
end
|
||||
|
||||
# Use `node_filter` instead of `expression_filter` to have a better failure
|
||||
# message when the selector fails: `expression_filter` modifies the initial
|
||||
# query and elements without the expected color are not returned.
|
||||
# `node_filter` applies the filter on the elements returned by the query so
|
||||
# that error message can list them if none matches.
|
||||
node_filter :color do |node, color|
|
||||
actual = node[:class].scan(/(?<=color-fg-)[\w-]+/).first
|
||||
(actual&.downcase == color.downcase).tap do |res|
|
||||
add_error("Expected color to be #{color.inspect} but was #{actual ? actual.inspect : 'not set'}") unless res
|
||||
end
|
||||
end
|
||||
|
||||
describe_expression_filters do |color: nil, **|
|
||||
" with color #{color.inspect}" if color
|
||||
end
|
||||
end
|
||||
|
||||
Capybara.add_selector :octicon, locator_type: [String, Symbol] do
|
||||
label "Octicon"
|
||||
|
||||
@@ -89,6 +126,14 @@ module Capybara
|
||||
Matchers::NegatedMatcher.new(have_primer_label(...))
|
||||
end
|
||||
|
||||
def have_primer_text(locator = nil, **, &)
|
||||
Matchers::HaveSelector.new(:primer_text, locator, **, &)
|
||||
end
|
||||
|
||||
def have_no_primer_text(...)
|
||||
Matchers::NegatedMatcher.new(have_primer_text(...))
|
||||
end
|
||||
|
||||
def have_octicon(locator = nil, **, &)
|
||||
Matchers::HaveSelector.new(:octicon, locator, **, &)
|
||||
end
|
||||
|
||||
@@ -55,7 +55,7 @@ module EnterpriseTokenFactory
|
||||
# @yield [EnterpriseToken] The `EnterpriseToken` instance
|
||||
# @return [EnterpriseToken] The created `EnterpriseToken`
|
||||
def create_enterprise_token(encoded_token_name = nil, **attributes)
|
||||
encoded_token_name ||= "token"
|
||||
encoded_token_name ||= "token_#{SecureRandom.uuid}"
|
||||
enterprise_token = build_enterprise_token(encoded_token_name, **attributes) do |token|
|
||||
token.save!(validate: false)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user