From 5ef478825371a6e83f81de16454a3cf39ecf3770 Mon Sep 17 00:00:00 2001 From: Alan Cornthwaite Date: Mon, 13 May 2024 15:12:32 +0930 Subject: [PATCH 1/7] Remove unusable components --- app/components/koi/tables/body.rb | 228 ------------------ .../koi/tables/body_cell_component.rb | 21 -- .../koi/tables/body_row_component.rb | 142 ----------- app/components/koi/tables/header.rb | 63 ----- .../koi/tables/header_cell_component.rb | 38 --- .../koi/tables/header_row_component.rb | 213 ---------------- app/components/koi/tables/table_component.rb | 2 - spec/components/koi/tables/body_spec.rb | 187 -------------- spec/components/koi/tables/header_spec.rb | 78 ------ .../koi/tables/table_component_spec.rb | 152 ------------ 10 files changed, 1124 deletions(-) delete mode 100644 app/components/koi/tables/body.rb delete mode 100644 app/components/koi/tables/body_cell_component.rb delete mode 100644 app/components/koi/tables/body_row_component.rb delete mode 100644 app/components/koi/tables/header.rb delete mode 100644 app/components/koi/tables/header_cell_component.rb delete mode 100644 app/components/koi/tables/header_row_component.rb delete mode 100644 spec/components/koi/tables/body_spec.rb delete mode 100644 spec/components/koi/tables/header_spec.rb delete mode 100644 spec/components/koi/tables/table_component_spec.rb diff --git a/app/components/koi/tables/body.rb b/app/components/koi/tables/body.rb deleted file mode 100644 index efd3167a3..000000000 --- a/app/components/koi/tables/body.rb +++ /dev/null @@ -1,228 +0,0 @@ -# frozen_string_literal: true - -using Katalyst::HtmlAttributes::HasHtmlAttributes - -module Koi - module Tables - module Body - # Shows Yes/No for boolean values - class BooleanComponent < BodyCellComponent - def rendered_value - value ? "Yes" : "No" - end - end - - # Formats the value as a date - # @param format [String] date format, defaults to :admin - # @param relative [Boolean] if true, the date may be(if within 5 days) shown as a relative date - class DateComponent < BodyCellComponent - include Koi::DateHelper - - def initialize(table, record, attribute, format: :admin, relative: true, **options) - super(table, record, attribute, **options) - - @format = format - @relative = relative - end - - def value - super&.to_date - end - - def rendered_value - @relative ? relative_time : absolute_time - end - - private - - def absolute_time - value.present? ? I18n.l(value, format: @format) : "" - end - - def relative_time - if value.blank? - "" - else - days_ago_in_words(value)&.capitalize || absolute_time - end - end - - def default_html_attributes - @relative && value.present? && days_ago_in_words(value).present? ? { title: absolute_time } : {} - end - end - - # Formats the value as a datetime - # @param format [String] datetime format, defaults to :admin - # @param relative [Boolean] if true, the datetime may be(if today) shown as a relative date/time - class DatetimeComponent < BodyCellComponent - include ActionView::Helpers::DateHelper - - def initialize(table, record, attribute, format: :admin, relative: true, **options) - super(table, record, attribute, **options) - - @format = format - @relative = relative - end - - def value - super&.to_datetime - end - - def rendered_value - @relative ? relative_time : absolute_time - end - - private - - def absolute_time - value.present? ? I18n.l(value, format: @format) : "" - end - - def today? - value.to_date == Date.current - end - - def relative_time - return "" if value.blank? - - if today? - if value > DateTime.current - "#{distance_of_time_in_words(value, DateTime.current)} from now".capitalize - else - "#{distance_of_time_in_words(value, DateTime.current)} ago".capitalize - end - else - absolute_time - end - end - - def default_html_attributes - @relative && today? ? { title: absolute_time } : {} - end - end - - # Formats the value as a money value - # - # The value is expected to be in cents. - # Adds a class to the cell to allow for custom styling - class CurrencyComponent < BodyCellComponent - def initialize(table, record, attribute, options: {}, **html_attributes) - super(table, record, attribute, **html_attributes) - - @options = options - end - - def rendered_value - value.present? ? number_to_currency(value / 100.0, @options) : "" - end - - def default_html_attributes - super.merge_html(class: "type-currency") - end - end - - # Formats the value as a number - # - # Adds a class to the cell to allow for custom styling - class NumberComponent < BodyCellComponent - def rendered_value - value.present? ? number_to_human(value) : "" - end - - def default_html_attributes - super.merge_html(class: "type-number") - end - end - - # Displays the plain text for rich text content - # - # Adds a title attribute to allow for hover over display of the full content - class RichTextComponent < BodyCellComponent - def default_html_attributes - { title: value.to_plain_text } - end - end - - # Displays a link to the record - # The link text is the value of the attribute - # @see Koi::Tables::BodyRowComponent#link - class LinkComponent < BodyCellComponent - def initialize(table, record, attribute, url:, link: {}, **options) - super(table, record, attribute, **options) - - @url = url - @link_options = link - end - - def call - content # ensure content is set before rendering options - - link = content.present? && url.present? ? link_to(content, url, @link_options) : content.to_s - content_tag(@type, link, **html_attributes) - end - - def url - case @url - when Symbol - # helpers are not available until the component is rendered - @url = helpers.public_send(@url, record) - when Proc - @url = @url.call(record) - else - @url - end - end - end - - # Shows an attachment - # - # The value is expected to be an ActiveStorage attachment - # - # If it is representable, shows as a image tag using a default variant named :thumb. - # - # Otherwise shows as a link to download. - class AttachmentComponent < BodyCellComponent - def initialize(table, record, attribute, variant: :thumb, **options) - super(table, record, attribute, **options) - - @variant = variant - end - - def rendered_value - representation - end - - def representation - if value.try(:variable?) && named_variant.present? - image_tag(value.variant(@variant)) - elsif value.try(:attached?) - filename.to_s - else - "" - end - end - - def filename - value.blob.filename - end - - # Utility for accessing the path Rails provides for retrieving the - # attachment for use in cells. Example: - # <% row.attachment :file do |cell| %> - # <%= link_to "Download", cell.internal_path %> - # <% end %> - def internal_path - rails_blob_path(value, disposition: :attachment) - end - - private - - # Find the reflective variant by name (i.e. :thumb by default) - def named_variant - object.attachment_reflections[@attribute.to_s].named_variants[@variant.to_sym] - end - end - end - end -end diff --git a/app/components/koi/tables/body_cell_component.rb b/app/components/koi/tables/body_cell_component.rb deleted file mode 100644 index 638140ea9..000000000 --- a/app/components/koi/tables/body_cell_component.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module Koi - module Tables - # Custom body cell component, in order to override the content - class BodyCellComponent < Katalyst::Tables::BodyCellComponent - def before_render - # fallback if no content block is given - with_content(rendered_value) unless content? - end - - def rendered_value - value.to_s - end - - def to_s - rendered_value - end - end - end -end diff --git a/app/components/koi/tables/body_row_component.rb b/app/components/koi/tables/body_row_component.rb deleted file mode 100644 index 29df0fd4a..000000000 --- a/app/components/koi/tables/body_row_component.rb +++ /dev/null @@ -1,142 +0,0 @@ -# frozen_string_literal: true - -module Koi - module Tables - # Custom body row component, in order to override the default body cell component - class BodyRowComponent < Katalyst::Tables::BodyRowComponent - # Generates a column from boolean values rendered as "Yes" or "No". - # - # @param method [Symbol] the method to call on the record - # @param attributes [Hash] HTML attributes to be added to the cell - # @param block [Proc] optional block to alter the cell content - # @return [void] - # - # @example Render a boolean column indicating whether the record is active - # <% row.boolean :active %> # => Yes - def boolean(method, **attributes, &block) - with_column(Body::BooleanComponent.new(@table, @record, method, **attributes), &block) - end - - # Generates a column from date values rendered using I18n.l. - # The default format is :admin, but it can be overridden. - # - # @param method [Symbol] the method to call on the record - # @param format [Symbol] the I18n date format to use when rendering - # @param attributes [Hash] HTML attributes to be added to the cell tag - # @param block [Proc] optional block to alter the cell content - # @return [void] - # - # @example Render a date column describing when the record was created - # <% row.date :created_at %> # => 29 Feb 2024 - def date(method, format: :admin, **attributes, &block) - with_column(Body::DateComponent.new(@table, @record, method, format:, **attributes), &block) - end - - # Generates a column from datetime values rendered using I18n.l. - # The default format is :admin, but it can be overridden. - # - # @param method [Symbol] the method to call on the record - # @param format [Symbol] the I18n datetime format to use when rendering - # @param attributes [Hash] HTML attributes to be added to the cell tag - # @param block [Proc] optional block to alter the cell content - # @return [void] - # - # @example Render a datetime column describing when the record was created - # <% row.datetime :created_at %> # => 29 Feb 2024, 5:00pm - def datetime(method, format: :admin, **attributes, &block) - with_column(Body::DatetimeComponent.new(@table, @record, method, format:, **attributes), &block) - end - - # Generates a column from numeric values formatted appropriately. - # - # @param method [Symbol] the method to call on the record - # @param attributes [Hash] HTML attributes to be added to the cell tag - # @param block [Proc] optional block to alter the cell content - # @return [void] - # - # @example Render the number of comments on a post - # <% row.number :comment_count %> # => 0 - def number(method, **attributes, &block) - with_column(Body::NumberComponent.new(@table, @record, method, **attributes), &block) - end - - # Generates a column from numeric values rendered using `number_to_currency`. - # - # @param method [Symbol] the method to call on the record - # @param options [Hash] options to be passed to `number_to_currency` - # @param attributes [Hash] HTML attributes to be added to the cell tag - # @param block [Proc] optional block to alter the cell content - # @return [void] - # - # @example Render a currency column for the price of a product - # <% row.currency :price %> # => $3.50 - def currency(method, options: {}, **attributes, &block) - with_column(Body::CurrencyComponent.new(@table, @record, method, options:, **attributes), &block) - end - - # Generates a column containing HTML markup. - # - # @param method [Symbol] the method to call on the record - # @param attributes [Hash] HTML attributes to be added to the cell tag - # @param block [Proc] optional block to alter the cell content - # @return [void] - # - # @note This method assumes that the method returns HTML-safe content. - # If the content is not HTML-safe, it will be escaped. - # - # @example Render a description column containing HTML markup - # <% row.rich_text :description %> # => Emphasis - def rich_text(method, **attributes, &block) - with_column(Body::RichTextComponent.new(@table, @record, method, **attributes), &block) - end - - # Generates a column that links to the record's show page (by default). - # - # @param method [Symbol] the method to call on the record - # @param link [Hash] options to be passed to the link_to helper - # @option opts [Hash, Array, String, Symbol] :url ([:admin, object]) options for url_for, - # or a symbol to be passed to the route helper - # @param attributes [Hash] HTML attributes to be added to the cell tag - # @param block [Proc] optional block to alter the cell content - # @return [void] - # - # @example Render a column containing the record's title, linked to its show page - # <% row.link :title %> # => About us - # @example Render a column containing the record's title, linked to its edit page - # <% row.link :title, url: :edit_admin_post_path do |cell| %> - # Edit <%= cell %> - # <% end %> - # # => Edit About us - def link(method, url: [:admin, @record], link: {}, **attributes, &block) - with_column(Body::LinkComponent.new(@table, @record, method, url:, link:, **attributes), &block) - end - - # Generates a column that renders the contents as text. - # - # @param method [Symbol] the method to call on the record - # @param attributes [Hash] HTML attributes to be added to the cell tag - # @param block [Proc] optional block to alter the cell content - # @return [void] - # - # @example Render a column containing the record's title - # <% row.text :title %> # => About us - def text(method, **attributes, &block) - with_column(BodyCellComponent.new(@table, @record, method, **attributes), &block) - end - - # Generates a column that renders an ActiveStorage attachment as a downloadable link. - # - # @param method [Symbol] the method to call on the record - # @param variant [Symbol] the variant to use when rendering the image (default :thumb) - # @param attributes [Hash] HTML attributes to be added to the cell tag - # @param block [Proc] optional block to alter the cell content - # @return [void] - # - # @example Render a column containing a download link to the record's background image - # <% row.attachment :background %> # => background.png - def attachment(method, variant: :thumb, **attributes, &block) - with_column(Body::AttachmentComponent.new(@table, @record, method, variant:, **attributes), &block) - end - end - end -end diff --git a/app/components/koi/tables/header.rb b/app/components/koi/tables/header.rb deleted file mode 100644 index e095a41c1..000000000 --- a/app/components/koi/tables/header.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -using Katalyst::HtmlAttributes::HasHtmlAttributes - -module Koi - module Tables - module Header - class NumberComponent < HeaderCellComponent - def default_html_attributes - super.merge_html(class: "type-number") - end - end - - class CurrencyComponent < HeaderCellComponent - def default_html_attributes - super.merge_html(class: "type-currency") - end - end - - class BooleanComponent < HeaderCellComponent - def default_html_attributes - super.merge_html(class: "type-boolean") - end - end - - class DateComponent < HeaderCellComponent - def default_html_attributes - super.merge_html(class: "type-date") - end - end - - class DateTimeComponent < HeaderCellComponent - def default_html_attributes - super.merge_html(class: "type-datetime") - end - end - - class LinkComponent < HeaderCellComponent - def default_html_attributes - super.merge_html(class: "type-link") - end - end - - class TextComponent < HeaderCellComponent - def default_html_attributes - super.merge_html(class: "type-text") - end - end - - class ImageComponent < HeaderCellComponent - def default_html_attributes - super.merge_html(class: "type-image") - end - end - - class AttachmentComponent < HeaderCellComponent - def default_html_attributes - super.merge_html(class: "type-attachment") - end - end - end - end -end diff --git a/app/components/koi/tables/header_cell_component.rb b/app/components/koi/tables/header_cell_component.rb deleted file mode 100644 index 3118bced8..000000000 --- a/app/components/koi/tables/header_cell_component.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -using Katalyst::HtmlAttributes::HasHtmlAttributes - -module Koi - module Tables - class HeaderCellComponent < Katalyst::Tables::HeaderCellComponent - attr_reader :width - - def initialize(table, attribute, label: nil, link: {}, width: nil, **html_attributes) - @width = width - - super(table, attribute, label:, link:, **html_attributes) - end - - def default_html_attributes - super.merge_html(class: width_class) - end - - private - - def width_class - case width - when :xs - "width-xs" - when :s - "width-s" - when :m - "width-m" - when :l - "width-l" - else - "" - end - end - end - end -end diff --git a/app/components/koi/tables/header_row_component.rb b/app/components/koi/tables/header_row_component.rb deleted file mode 100644 index cf9a49ccc..000000000 --- a/app/components/koi/tables/header_row_component.rb +++ /dev/null @@ -1,213 +0,0 @@ -# frozen_string_literal: true - -module Koi - module Tables - # Custom header row component, in order to override the default header cell component - # for number columns, we add a class to the header cell to allow for custom styling - class HeaderRowComponent < Katalyst::Tables::HeaderRowComponent - # Renders a boolean column header - # @param method [Symbol] the method to call on the record to get the value - # @param attributes [Hash] additional arguments are applied as html attributes to the th element - # @option attributes [String] :label (nil) The label options to display in the header - # @option attributes [Hash] :link ({}) The link options for the sorting link - # @option attributes [String] :width (:xs) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil - # - # @example Render a boolean column header - # <% row.boolean :active %> # => Active - # - # @example Render a boolean column header with a custom label - # <% row.boolean :active, label: "Published" %> # => Published - # - # @example Render a boolean column header with medium width - # <% row.boolean :active, width: :m %> - # # => Active - # - # @see Koi::Tables::BodyRowComponent#boolean - def boolean(method, **attributes, &block) - header_cell(method, component: Header::BooleanComponent, **attributes, &block) - end - - # Renders a date column header - # @param method [Symbol] the method to call on the record to get the value - # @param attributes [Hash] additional arguments are applied as html attributes to the th element - # @option attributes [String] :label (nil) The label options to display in the header - # @option attributes [Hash] :link ({}) The link options for the sorting link - # @option attributes [String] :width (:s) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil - # - # @example Render a date column header - # <% row.date :published_on %> # => Published on - # - # @example Render a date column header with a custom label - # <% row.date :published_on, label: "Date" %> # => Date - # - # @example Render a date column header with small width - # <% row.date :published_on, width: :s %> - # # => Published on - # - # @see Koi::Tables::BodyRowComponent#date - def date(method, **attributes, &block) - header_cell(method, component: Header::DateComponent, **attributes, &block) - end - - # Renders a datetime column header - # @param method [Symbol] the method to call on the record to get the value - # @param attributes [Hash] additional arguments are applied as html attributes to the th element - # @option attributes [String] :label (nil) The label options to display in the header - # @option attributes [Hash] :link ({}) The link options for the sorting link - # @option attributes [String] :width (:m) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil - # - # @example Render a datetime column header - # <% row.datetime :created_at %> # => Created at - # - # @example Render a datetime column header with a custom label - # <% row.datetime :created_at, label: "Published at" %> # => Published at - # - # @example Render a datetime column header with small width - # <% row.datetime :created_at, width: :s %> - # # => Created at - # - # @see Koi::Tables::BodyRowComponent#datetime - def datetime(method, **attributes, &block) - header_cell(method, component: Header::DateTimeComponent, **attributes, &block) - end - - # Renders a number column header - # @param method [Symbol] the method to call on the record to get the value - # @param attributes [Hash] additional arguments are applied as html attributes to the th element - # @option attributes [String] :label (nil) The label options to display in the header - # @option attributes [Hash] :link ({}) The link options for the sorting link - # @option attributes [String] :width (:xs) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil - # - # @example Render a number column header - # <% row.number :comment_count %> # => Comments - # - # @example Render a number column header with a custom label - # <% row.number :comment_count, label: "Comments" %> # => Comments - # - # @example Render a number column header with medium width - # <% row.number :comment_count, width: :m %> - # # => Comment Count - # - # @see Koi::Tables::BodyRowComponent#number - def number(method, **attributes, &block) - header_cell(method, component: Header::NumberComponent, **attributes, &block) - end - - # Renders a currency column header - # @param method [Symbol] the method to call on the record to get the value - # @param attributes [Hash] additional arguments are applied as html attributes to the th element - # @option attributes [String] :label (nil) The label options to display in the header - # @option attributes [Hash] :link ({}) The link options for the sorting link - # @option attributes [String] :width (:s) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil - # - # @example Render a currency column header - # <% row.currency :price %> # => Price - # - # @example Render a currency column header with a custom label - # <% row.currency :price, label: "Amount($)" %> # => Amount($) - # - # @example Render a currency column header with medium width - # <% row.currency :price, width: :m %> - # # => Price - # - # @see Koi::Tables::BodyRowComponent#currency - def currency(method, **attributes, &block) - header_cell(method, component: Header::CurrencyComponent, **attributes, &block) - end - - # Renders a rich text column header - # @param method [Symbol] the method to call on the record to get the value - # @param attributes [Hash] additional arguments are applied as html attributes to the th element - # @option attributes [String] :label (nil) The label options to display in the header - # @option attributes [Hash] :link ({}) The link options for the sorting link - # @option attributes [String] :width (nil) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil - # - # @example Render a rich text column header - # <% row.rich_text :content %> # => Content - # - # @example Render a rich text column header with a custom label - # <% row.rich_text :content, label: "Post content" %> # => Post content - # - # @example Render a rich text column header with large width - # <% row.rich_text :content, width: :l %> - # # => Content - # - # @see Koi::Tables::BodyRowComponent#rich_text - def rich_text(method, **attributes, &block) - header_cell(method, component: Header::TextComponent, **attributes, &block) - end - - # Renders a link column header - # @param method [Symbol] the method to call on the record to get the value - # @param attributes [Hash] additional arguments are applied as html attributes to the th element - # @option attributes [String] :label (nil) The label options to display in the header - # @option attributes [Hash] :link ({}) The link options for the sorting link - # @option attributes [String] :width (nil) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil - # - # @example Render a link column header - # <% row.link :link %> # => Link - # - # @example Render a link column header with a custom label - # <% row.link :link, label: "Post" %> # => Post - # - # @example Render a link column header with small width - # <% row.link :content, width: :s %> - # # => Content - # - # @see Koi::Tables::BodyRowComponent#link - def link(method, **attributes, &block) - header_cell(method, component: Header::LinkComponent, **attributes, &block) - end - - # Renders a text column header - # @param method [Symbol] the method to call on the record to get the value - # @param attributes [Hash] additional arguments are applied as html attributes to the th element - # @option attributes [String] :label (nil) The label options to display in the header - # @option attributes [Hash] :link ({}) The link options for the sorting link - # @option attributes [String] :width (nil) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil - # - # @example Render a text column header - # <% row.text :content %> # => Content - # - # @example Render a text column header with a custom label - # <% row.text :content, label: "Description" %> # => Description - # - # @example Render a text column header with large width - # <% row.text :content, width: :l %> - # # => Content - # - # @see Koi::Tables::BodyRowComponent#text - def text(method, **attributes, &block) - header_cell(method, component: Header::TextComponent, **attributes, &block) - end - - # Renders a attachment column header - # @param method [Symbol] the method to call on the record to get the value - # @param attributes [Hash] additional arguments are applied as html attributes to the th element - # @option attributes [String] :label (nil) The label options to display in the header - # @option attributes [Hash] :link ({}) The link options for the sorting link - # @option attributes [String] :width (nil) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil - # - # @example Render a attachment column header - # <% row.attachment :attachment %> # => Attachment - # - # @example Render a attachment column header with a custom label - # <% row.attachment :attachment, label: "Document" %> # => Document - # - # @example Render a attachment column header with small width - # <% row.attachment :attachment, width: :s %> - # # => Attachment - # - # @see Koi::Tables::BodyRowComponent#attachment - def attachment(method, **attributes, &block) - header_cell(method, component: Header::AttachmentComponent, **attributes, &block) - end - - private - - def header_cell(method, component: HeaderCellComponent, **attributes, &block) - with_column(component.new(@table, method, link: @link_attributes, **attributes), &block) - end - end - end -end diff --git a/app/components/koi/tables/table_component.rb b/app/components/koi/tables/table_component.rb index a63628621..75627e5bf 100644 --- a/app/components/koi/tables/table_component.rb +++ b/app/components/koi/tables/table_component.rb @@ -5,8 +5,6 @@ module Tables # Custom table component, in order to override the default header and body row components # which enables us to use our own custom header and body cell components class TableComponent < Katalyst::TableComponent - config.header_row = "Koi::Tables::HeaderRowComponent" - config.body_row = "Koi::Tables::BodyRowComponent" end end end diff --git a/spec/components/koi/tables/body_spec.rb b/spec/components/koi/tables/body_spec.rb deleted file mode 100644 index 8af962486..000000000 --- a/spec/components/koi/tables/body_spec.rb +++ /dev/null @@ -1,187 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Koi::Tables::Body do - include Rails.application.routes.url_helpers - - let(:record) { create(:post) } - let(:table) { Koi::Tables::TableComponent.new(collection: Post.all, id: "table") } - - before do - record - end - - describe Koi::Tables::Body::LinkComponent do - it "renders column" do - component = described_class.new(table, record, :name, url: [:admin, record]) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - - #{record.name} - - HTML - end - - it "supports path helpers" do - component = described_class.new(table, record, :name, url: :edit_admin_post_path) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - - #{record.name} - - HTML - end - - it "supports procs" do - record = create(:url_rewrite, to: "www.example.com") - component = described_class.new(table, record, :from, url: ->(object) { object.to }) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - - #{record.from} - - HTML - end - - it "supports content" do - component = described_class.new(table, record, :name, url: [:admin, record]) - .with_content("Redirect from #{record.name}") - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - - Redirect from #{record.name} - - HTML - end - - it "supports attributes for anchor tag" do - component = described_class.new(table, record, :name, url: [:admin, record], link: { class: "custom" }) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - - #{record.name} - - HTML - end - end - - describe Koi::Tables::Body::RichTextComponent do - it "renders column" do - component = described_class.new(table, record, :content) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - #{record.content} - HTML - end - end - - describe Koi::Tables::Body::BooleanComponent do - it "renders column" do - component = described_class.new(table, record, :active) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - Yes - HTML - end - end - - describe Koi::Tables::Body::DateComponent do - let(:record) { create(:post, published_on: 1.month.ago) } - - it "renders column" do - component = described_class.new(table, record, :published_on) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - #{I18n.l(record.published_on, format: :admin)} - HTML - end - - context "when not relative" do - let(:record) { create(:post, published_on: Date.current) } - - it "renders column" do - component = described_class.new(table, record, :published_on, relative: false) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - #{I18n.l(record.published_on, format: :admin)} - HTML - end - end - - context "when date is within 5 days" do - let(:record) { create(:post, published_on: 2.days.ago) } - - it "renders column" do - component = described_class.new(table, record, :published_on) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - 2 days ago - HTML - end - end - - context "when future date" do - let(:record) { create(:post, published_on: 3.days.from_now) } - - it "renders column" do - component = described_class.new(table, record, :published_on) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - 3 days from now - HTML - end - end - end - - describe Koi::Tables::Body::DatetimeComponent do - it "renders column" do - component = described_class.new(table, record, :created_at) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - Less than a minute ago - HTML - end - - context "when not relative" do - it "renders column" do - component = described_class.new(table, record, :created_at, relative: false) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - #{I18n.l(record.created_at, format: :admin)} - HTML - end - end - end - - describe Koi::Tables::Body::NumberComponent do - it "renders column" do - record = create(:banner) - component = described_class.new(table, record, :ordinal) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - #{record.ordinal} - HTML - end - end - - describe Koi::Tables::Body::CurrencyComponent do - it "renders column" do - record = create(:banner) - component = described_class.new(table, record, :ordinal) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - $#{record.ordinal / 100.0} - HTML - end - end - - describe Koi::Tables::Body::AttachmentComponent do - it "renders column with a preview of the image" do - record = create(:banner, :with_image) - component = described_class.new(table, record, :image) - rendered = render_inline(component) - expect(rendered).to have_css("td > img[src*='dummy.png']") - end - end -end diff --git a/spec/components/koi/tables/header_spec.rb b/spec/components/koi/tables/header_spec.rb deleted file mode 100644 index 8ed69fad8..000000000 --- a/spec/components/koi/tables/header_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Koi::Tables::Header do - let(:table) { Koi::Tables::TableComponent.new(collection:, id: "table") } - let(:collection) { Post.all } - - describe Koi::Tables::Header::BooleanComponent do - it "renders column header" do - component = described_class.new(table, :active) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - Active - HTML - end - - context "when width is specified" do - it "renders column header" do - component = described_class.new(table, :active, width: :m) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - Active - HTML - end - end - - context "when additional css class is specified" do - it "renders column header" do - component = described_class.new(table, :active, class: "custom-class") - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - Active - HTML - end - end - end - - describe Koi::Tables::Header::NumberComponent do - let(:collection) { Banner.all } - - it "renders column header" do - component = described_class.new(table, :ordinal) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - Ordinal - HTML - end - - context "with sorting" do - let(:collection) { Katalyst::Tables::Collection::Base.new(sorting: "ordinal desc").apply(Banner.all) } - - it "renders column header" do - component = described_class.new(table, :ordinal) - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - - Ordinal - - HTML - end - end - - context "with custom class and sorting" do - let(:collection) { Katalyst::Tables::Collection::Base.new(sorting: "ordinal desc").apply(Banner.all) } - - it "renders column header" do - component = described_class.new(table, :ordinal, class: "custom-class") - rendered = render_inline(component) - expect(rendered).to match_html(<<~HTML) - - Ordinal - - HTML - end - end - end -end diff --git a/spec/components/koi/tables/table_component_spec.rb b/spec/components/koi/tables/table_component_spec.rb deleted file mode 100644 index 76b416ab0..000000000 --- a/spec/components/koi/tables/table_component_spec.rb +++ /dev/null @@ -1,152 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Koi::Tables::TableComponent do - include ActionView::Helpers::NumberHelper - include Rails.application.routes.url_helpers - - subject(:table) do - with_request_url("/homepage") do - vc_test_request.headers["Accept"] = "text/html" - render_inline(described_class.new(collection:, id: "table"), &content) - end - end - - let(:collection) { Katalyst::Tables::Collection::Base.new.apply(Post.all) } - let(:content) { Proc.new { |row| row.cell :name } } - - before do - create_list(:post, 1) - create_list(:banner, 1) - vc_test_controller.response = instance_double(ActionDispatch::Response, media_type: "text/html") - end - - it "renders column" do - expect(table).to match_html(<<~HTML) - - - -
Name
#{collection.items.first&.name}
- HTML - end - - context "with boolean" do - let(:content) { Proc.new { |row| row.boolean :active } } - - it "renders boolean column" do - expect(table).to match_html(<<~HTML) - - - -
Active
Yes
- HTML - end - - context "with content" do - let(:content) do - Proc.new do |row| - row.boolean :active do |v| - v ? "Green" : "Red" - end - end - end - - it "renders boolean column with custom content" do - expect(table).to match_html(<<~HTML) - - - -
Active
Green
- HTML - end - end - end - - context "with date" do - let(:value) { I18n.l(collection.items.first&.published_on, format: :admin) } - let(:content) { Proc.new { |row| row.date :published_on } } - - it "renders date column" do - expect(table).to match_html(<<~HTML) - - - -
Published on
#{value}
- HTML - end - end - - context "with datetime" do - let(:value) { I18n.l(collection.items.first&.created_at, format: :admin) } - let(:content) { Proc.new { |row| row.datetime :created_at } } - - it "renders datetime column" do - expect(table).to match_html(<<~HTML) - - - -
Created at
Less than a minute ago
- HTML - end - end - - context "with number" do - let(:collection) { Katalyst::Tables::Collection::Base.new.apply(Banner.all) } - let(:content) { Proc.new { |row| row.number :ordinal } } - - it "renders number column" do - expect(table).to match_html(<<~HTML) - - - -
Ordinal
#{collection.items.first&.ordinal}
- HTML - end - end - - context "with rich text" do - let(:content) { Proc.new { |row| row.rich_text :content } } - - it "renders rich text column" do - expect(table).to match_html(<<~HTML) - - - - - -
Content
- #{collection.items.first&.content} -
- HTML - end - end - - context "with link" do - let(:content) { Proc.new { |row| row.link :name } } - - it "renders link column" do - expect(table).to match_html(<<~HTML) - - - - - -
Name
#{collection.items.first&.name}
- HTML - end - end - - context "with text" do - let(:content) { Proc.new { |row| row.text :title } } - - it "renders text column" do - expect(table).to match_html(<<~HTML) - - - -
Title
#{collection.items.first&.title}
- HTML - end - end -end From 0db6f57802afdf372df1e8a7a03e5704c1ab3a19 Mon Sep 17 00:00:00 2001 From: Alan Cornthwaite Date: Mon, 13 May 2024 15:13:37 +0930 Subject: [PATCH 2/7] Remove table features that are superseded by new tables gem --- app/components/koi/index_table_component.rb | 26 ------------- .../koi/navigation/editor/errors_component.rb | 1 - app/components/koi/ordinal_table_component.rb | 27 -------------- app/components/koi/tables/table_component.rb | 3 ++ .../admin/admin_users_controller.rb | 6 +-- .../admin/url_rewrites_controller.rb | 6 +-- .../koi/controller/is_admin_controller.rb | 1 + .../admin/admin_users/_admin.html+row.erb | 2 - app/views/admin/admin_users/index.html.erb | 7 +++- .../admin/credentials/_credentials.html.erb | 6 +-- .../url_rewrites/_url_rewrite.html+row.erb | 3 -- app/views/admin/url_rewrites/index.html.erb | 8 +++- .../koi/index_table_component_spec.rb | 37 ------------------- .../app/views/admin/banners/index.html.erb | 8 +++- 14 files changed, 29 insertions(+), 112 deletions(-) delete mode 100644 app/components/koi/index_table_component.rb delete mode 100644 app/components/koi/ordinal_table_component.rb delete mode 100644 app/views/admin/admin_users/_admin.html+row.erb delete mode 100644 app/views/admin/url_rewrites/_url_rewrite.html+row.erb delete mode 100644 spec/components/koi/index_table_component_spec.rb diff --git a/app/components/koi/index_table_component.rb b/app/components/koi/index_table_component.rb deleted file mode 100644 index 0f2c3f7d5..000000000 --- a/app/components/koi/index_table_component.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module Koi - class IndexTableComponent < ViewComponent::Base - attr_reader :id, :pagy_id - - # Use the Koi::Tables::TableComponent to support custom header and body row components - renders_one :table, Koi::Tables::TableComponent - renders_one :pagination, Katalyst::Tables::PagyNavComponent - - def initialize(collection:, id: "index-table", **options) - super - - @id = id - @pagy_id = "#{id}-pagination" - - with_table(collection:, id:, class: "index-table", caption: true, **options) - with_pagination(collection:, id: pagy_id) if collection.paginate? - end - - def call - concat(table) - concat(pagination) - end - end -end diff --git a/app/components/koi/navigation/editor/errors_component.rb b/app/components/koi/navigation/editor/errors_component.rb index 089892c0c..dd65fc168 100644 --- a/app/components/koi/navigation/editor/errors_component.rb +++ b/app/components/koi/navigation/editor/errors_component.rb @@ -5,7 +5,6 @@ module Navigation module Editor class ErrorsComponent < ViewComponent::Base include Katalyst::HtmlAttributes - include Katalyst::Tables::TurboReplaceable include Turbo::FramesHelper attr_reader :menu diff --git a/app/components/koi/ordinal_table_component.rb b/app/components/koi/ordinal_table_component.rb deleted file mode 100644 index 887dd911b..000000000 --- a/app/components/koi/ordinal_table_component.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Koi - class OrdinalTableComponent < Tables::TableComponent - include Katalyst::Tables::Orderable - - def initialize(collection:, - id: "index-table", - url: [:order, :admin, collection], - scope: "order[#{collection.model_name.plural}]", - **options) - super(collection:, id:, class: "index-table", caption: true, **options) - - @url = url - @scope = scope - end - - def before_render - with_orderable(url: @url, scope: @scope) unless orderable? - end - - def call - concat(render_parent) - concat(orderable) - end - end -end diff --git a/app/components/koi/tables/table_component.rb b/app/components/koi/tables/table_component.rb index 75627e5bf..62a865d59 100644 --- a/app/components/koi/tables/table_component.rb +++ b/app/components/koi/tables/table_component.rb @@ -5,6 +5,9 @@ module Tables # Custom table component, in order to override the default header and body row components # which enables us to use our own custom header and body cell components class TableComponent < Katalyst::TableComponent + def default_html_attributes + { class: "index-table" } + end end end end diff --git a/app/controllers/admin/admin_users_controller.rb b/app/controllers/admin/admin_users_controller.rb index bb472575b..ab577e658 100644 --- a/app/controllers/admin/admin_users_controller.rb +++ b/app/controllers/admin/admin_users_controller.rb @@ -8,12 +8,8 @@ class AdminUsersController < ApplicationController def index collection = Collection.new.with_params(params).apply(Admin::User.strict_loading) - table = Koi::IndexTableComponent.new(collection:, partial: "admin") - respond_to do |format| - format.turbo_stream { render(table) } if self_referred? - format.html { render :index, locals: { table:, collection: } } - end + render locals: { collection: } end def show diff --git a/app/controllers/admin/url_rewrites_controller.rb b/app/controllers/admin/url_rewrites_controller.rb index 5c5ff9938..5badf8376 100644 --- a/app/controllers/admin/url_rewrites_controller.rb +++ b/app/controllers/admin/url_rewrites_controller.rb @@ -6,12 +6,8 @@ class UrlRewritesController < ApplicationController def index collection = Collection.new.with_params(params).apply(UrlRewrite.strict_loading) - table = Koi::IndexTableComponent.new(collection:) - respond_to do |format| - format.turbo_stream { render(table) } if self_referred? - format.html { render :index, locals: { table:, collection: } } - end + render locals: { collection: } end def show diff --git a/app/controllers/concerns/koi/controller/is_admin_controller.rb b/app/controllers/concerns/koi/controller/is_admin_controller.rb index 31dbec231..b8f2e8d33 100644 --- a/app/controllers/concerns/koi/controller/is_admin_controller.rb +++ b/app/controllers/concerns/koi/controller/is_admin_controller.rb @@ -18,6 +18,7 @@ def authenticate_local_admins(value) include Pagy::Backend default_form_builder "Koi::FormBuilder" + default_table_component Koi::Tables::TableComponent helper Katalyst::GOVUK::Formbuilder::Frontend helper Katalyst::Navigation::FrontendHelper diff --git a/app/views/admin/admin_users/_admin.html+row.erb b/app/views/admin/admin_users/_admin.html+row.erb deleted file mode 100644 index 54df8c46b..000000000 --- a/app/views/admin/admin_users/_admin.html+row.erb +++ /dev/null @@ -1,2 +0,0 @@ -<%= row.link :name, url: :admin_admin_user_path %> -<%= row.text :email %> diff --git a/app/views/admin/admin_users/index.html.erb b/app/views/admin/admin_users/index.html.erb index 987b96eb3..a4fa60d2a 100644 --- a/app/views/admin/admin_users/index.html.erb +++ b/app/views/admin/admin_users/index.html.erb @@ -6,4 +6,9 @@ <%= select_tag(:scope, options_for_select([["Active", :active], ["All", :all], ["Archived", :archived]], params[:scope])) %> <% end %> -<%= render table %> +<%= table_with(collection:) do |row, admin| %> + <%= row.link :name, url: :admin_admin_user_path %> + <%= row.text :email %> +<% end %> + +<%== pagy_nav(collection.pagination) %> diff --git a/app/views/admin/credentials/_credentials.html.erb b/app/views/admin/credentials/_credentials.html.erb index 83e62dba2..57807fe11 100644 --- a/app/views/admin/credentials/_credentials.html.erb +++ b/app/views/admin/credentials/_credentials.html.erb @@ -1,6 +1,6 @@ -<%= render Katalyst::TableComponent.new(id: dom_id(admin, :credentials), collection: admin.credentials, class: "index-table") do |t, c| %> - <% t.cell :nickname %> - <% t.cell :sign_count %> +<%= table_with(id: dom_id(admin, :credentials), collection: admin.credentials, class: "index-table") do |t, c| %> + <% t.text :nickname %> + <% t.number :sign_count %> <% t.cell :actions, label: "" do %> <%= button_to "Remove device", admin_admin_user_credential_path(admin, c), method: :delete, class: "button button--text" %> <% end if admin == current_admin %> diff --git a/app/views/admin/url_rewrites/_url_rewrite.html+row.erb b/app/views/admin/url_rewrites/_url_rewrite.html+row.erb deleted file mode 100644 index da1490e75..000000000 --- a/app/views/admin/url_rewrites/_url_rewrite.html+row.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= row.link :from %> -<%= row.text :to %> -<%= row.boolean :active %> diff --git a/app/views/admin/url_rewrites/index.html.erb b/app/views/admin/url_rewrites/index.html.erb index 54c85ee9f..3c931cc02 100644 --- a/app/views/admin/url_rewrites/index.html.erb +++ b/app/views/admin/url_rewrites/index.html.erb @@ -7,4 +7,10 @@ <%= select_tag(:scope, options_for_select([["Active", :active], ["All", :all], ["Inactive", :inactive]], params[:scope])) %> <% end %> -<%= render(table) %> +<%= table_with(collection:) do |row, url_rewrite| %> + <%= row.link :from, url: [:admin, url_rewrite] %> + <%= row.text :to %> + <%= row.boolean :active %> +<% end %> + +<%== pagy_nav(collection.pagination) %> diff --git a/spec/components/koi/index_table_component_spec.rb b/spec/components/koi/index_table_component_spec.rb deleted file mode 100644 index de31bd4d2..000000000 --- a/spec/components/koi/index_table_component_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Koi::IndexTableComponent do - subject(:component) { described_class.new(collection:) } - - let(:collection) { Katalyst::Tables::Collection::Base.new.apply(Post.strict_loading) } - - let(:post) { create(:post) } - let(:format) { "text/html" } - - before do - post - with_controller_class Admin::PostsController do - vc_test_request.headers["Accept"] = "text/html" - vc_test_controller.response = instance_double(ActionDispatch::Response, media_type: "text/html") - render_inline(component) - end - end - - it "renders collection" do - expect(page).to have_link(post.name, href: vc_test_controller.polymorphic_path([:admin, post])) - end - - it "doesn't render pagination" do - expect(page).not_to have_css("#index-table-pagination") - end - - context "with paginated collection" do - let(:collection) { Admin::PostsController::Collection.new.apply(Post.strict_loading) } - - it "renders pagination" do - expect(page).to have_css("#index-table-pagination") - end - end -end diff --git a/spec/templates/app/views/admin/banners/index.html.erb b/spec/templates/app/views/admin/banners/index.html.erb index 413596f87..8779a91aa 100644 --- a/spec/templates/app/views/admin/banners/index.html.erb +++ b/spec/templates/app/views/admin/banners/index.html.erb @@ -9,4 +9,10 @@ <%= koi_index_actions create: true, search: true %> -<%= render Koi::OrdinalTableComponent.new(collection:) %> +<%= table_with(collection:) do |row, banner| %> + <% row.ordinal %> + <% row.link :name, url: [:admin, banner] %> + <% row.attachment :image %> +<% end %> + +<%= table_orderable_with(collection:, url: order_admin_banners_path) %> From aa2595c8c68402fbdcb42620e3c462e94f55a2d3 Mon Sep 17 00:00:00 2001 From: Alan Cornthwaite Date: Mon, 13 May 2024 15:36:40 +0930 Subject: [PATCH 3/7] Update generators to match new tables syntax --- .../admin_controller/templates/controller.rb.tt | 6 +----- .../koi/admin_views/admin_views_generator.rb | 2 +- .../templates/_record.html+row.erb.tt | 7 ------- .../koi/admin_views/templates/index.html.erb.tt | 17 ++++++++++++++++- .../koi/admin_views_generator_spec.rb | 2 -- spec/support/capybara.rb | 13 ------------- spec/system/index/filtering_spec.rb | 4 ++-- spec/system/index/ordinal_spec.rb | 2 -- spec/system/index/sorting_spec.rb | 2 +- .../views/admin/banners/_banner.html+row.erb | 3 --- 10 files changed, 21 insertions(+), 37 deletions(-) delete mode 100644 lib/generators/koi/admin_views/templates/_record.html+row.erb.tt delete mode 100644 spec/templates/app/views/admin/banners/_banner.html+row.erb diff --git a/lib/generators/koi/admin_controller/templates/controller.rb.tt b/lib/generators/koi/admin_controller/templates/controller.rb.tt index e01ecc68c..7c2a68f8a 100644 --- a/lib/generators/koi/admin_controller/templates/controller.rb.tt +++ b/lib/generators/koi/admin_controller/templates/controller.rb.tt @@ -6,12 +6,8 @@ module Admin def index collection = Collection.new.with_params(params).apply(::<%= class_name %>.strict_loading.all) - table = Koi::IndexTableComponent.new(collection:) - respond_to do |format| - format.turbo_stream { render table } if self_referred? - format.html { render :index, locals: { table:, collection: } } - end + render locals: { collection: } end def show diff --git a/lib/generators/koi/admin_views/admin_views_generator.rb b/lib/generators/koi/admin_views/admin_views_generator.rb index dbf1e1f66..fb6839af1 100644 --- a/lib/generators/koi/admin_views/admin_views_generator.rb +++ b/lib/generators/koi/admin_views/admin_views_generator.rb @@ -25,7 +25,7 @@ def copy_view_files private def available_views - %w(index.html.erb edit.html.erb show.html.erb new.html.erb _fields.html.erb _record.html+row.erb) + %w(index.html.erb edit.html.erb show.html.erb new.html.erb _fields.html.erb) end def govuk_input_for(attribute) diff --git a/lib/generators/koi/admin_views/templates/_record.html+row.erb.tt b/lib/generators/koi/admin_views/templates/_record.html+row.erb.tt deleted file mode 100644 index 94fa6e34a..000000000 --- a/lib/generators/koi/admin_views/templates/_record.html+row.erb.tt +++ /dev/null @@ -1,7 +0,0 @@ -<%- index_attributes.each_with_index do |attribute, index| -%> -<%- if index.zero? -%> -<%% row.link :<%= attribute.name %> %> -<%- else -%> -<%= index_attribute_for attribute %> -<%- end -%> -<%- end -%> diff --git a/lib/generators/koi/admin_views/templates/index.html.erb.tt b/lib/generators/koi/admin_views/templates/index.html.erb.tt index cd1139f7c..87da6a320 100644 --- a/lib/generators/koi/admin_views/templates/index.html.erb.tt +++ b/lib/generators/koi/admin_views/templates/index.html.erb.tt @@ -1,7 +1,22 @@ +<%% content_for :head do %> + + +<%% end %> + <%% content_for :header do %> <%%= render(Koi::Header::IndexComponent.new(model: <%= class_name %>)) %> <%% end %> <%%= koi_index_actions create: true, search: true %> -<%%= render table %> +<%%= table_with(collection:) do |row, <%= singular_name %>| %> + <% index_attributes.each_with_index do |attribute, index| %> + <% if index.zero? %> + <%% row.link :<%= attribute.name %> %> + <% else %> + <%= index_attribute_for attribute %> + <% end %> + <% end %> +<%% end %> + +<%%== pagy_nav(collection.pagination) %> diff --git a/spec/generators/koi/admin_views_generator_spec.rb b/spec/generators/koi/admin_views_generator_spec.rb index 37ddc0a59..662e82334 100644 --- a/spec/generators/koi/admin_views_generator_spec.rb +++ b/spec/generators/koi/admin_views_generator_spec.rb @@ -18,7 +18,6 @@ "app/views/admin/tests/show.html.erb", "app/views/admin/tests/new.html.erb", "app/views/admin/tests/_fields.html.erb", - "app/views/admin/tests/_test.html+row.erb", ) end @@ -29,7 +28,6 @@ expect(Pathname.new(file("app/views/admin/tests/show.html.erb"))).to exist expect(Pathname.new(file("app/views/admin/tests/new.html.erb"))).to exist expect(Pathname.new(file("app/views/admin/tests/_fields.html.erb"))).to exist - expect(Pathname.new(file("app/views/admin/tests/_test.html+row.erb"))).to exist end describe "views/admin/tests/show.html.erb" do diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 6fbd1ea94..de9d64252 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -2,19 +2,6 @@ require "capybara/rspec" -module WaitForTurbo - def wait_for_form_submission - Timeout.timeout(Capybara.default_max_wait_time) do - loop until form_submission_complete? - end - end - - def form_submission_complete? - page.evaluate_script("Turbo.navigator.formSubmission.state === 'stopped'") - end -end - RSpec.configure do |config| config.include Capybara::RSpecMatchers, type: :request - config.include WaitForTurbo, type: :system end diff --git a/spec/system/index/filtering_spec.rb b/spec/system/index/filtering_spec.rb index c25c85bd0..3ff100465 100644 --- a/spec/system/index/filtering_spec.rb +++ b/spec/system/index/filtering_spec.rb @@ -70,7 +70,7 @@ click_on "Next" - expect(page).to have_current_path("/admin/posts?page=2&search=#{query}") + expect(page).to have_current_path("/admin/posts?search=#{query}&page=2") expect(page).to have_css("input[type=search][value=#{query}") end end @@ -81,7 +81,7 @@ click_on "Title" - expect(page).to have_current_path("/admin/posts?sort=title+asc&search=#{query}") + expect(page).to have_current_path("/admin/posts?search=#{query}&sort=title+asc") expect(page).to have_css("input[type=search][value=#{query}]") end end diff --git a/spec/system/index/ordinal_spec.rb b/spec/system/index/ordinal_spec.rb index a3ee4485a..d0344f708 100644 --- a/spec/system/index/ordinal_spec.rb +++ b/spec/system/index/ordinal_spec.rb @@ -28,8 +28,6 @@ expect(page).to have_css("tr:last-child td", text: "first") - wait_for_form_submission - expect(Banner.all).to contain_exactly( have_attributes(name: "second", ordinal: 0), have_attributes(name: "third", ordinal: 1), diff --git a/spec/system/index/sorting_spec.rb b/spec/system/index/sorting_spec.rb index efdc78f2c..113b590ea 100644 --- a/spec/system/index/sorting_spec.rb +++ b/spec/system/index/sorting_spec.rb @@ -65,7 +65,7 @@ click_on "Next" - expect(page).to have_current_path("/admin/posts?page=2&sort=title+asc") + expect(page).to have_current_path("/admin/posts?sort=title+asc&page=2") end end end diff --git a/spec/templates/app/views/admin/banners/_banner.html+row.erb b/spec/templates/app/views/admin/banners/_banner.html+row.erb deleted file mode 100644 index 9ab9c8abd..000000000 --- a/spec/templates/app/views/admin/banners/_banner.html+row.erb +++ /dev/null @@ -1,3 +0,0 @@ -<% row.ordinal %> -<% row.link :name %> -<% row.attachment :image %> From 2cd9d62768f394e316c4bfcc3cd850341780c087 Mon Sep 17 00:00:00 2001 From: Alan Cornthwaite Date: Mon, 13 May 2024 16:00:20 +0930 Subject: [PATCH 4/7] Local instances of katalyst gems --- Gemfile | 2 ++ Gemfile.lock | 23 ++++++++++++++++------- katalyst-koi.gemspec | 1 - 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index ccaa94932..035e1e1f9 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,8 @@ group :development, :test do gem "faker" gem "image_processing" # only required for projects using active storage gem "katalyst-basic-auth" + gem "katalyst-html-attributes", path: "../html-attributes" + gem "katalyst-tables", path: "../katalyst-tables" gem "propshaft" gem "puma" gem "rails" diff --git a/Gemfile.lock b/Gemfile.lock index 15e4dcefc..5a8bbaed0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,17 @@ +PATH + remote: ../html-attributes + specs: + katalyst-html-attributes (1.0.2) + activesupport + html-attributes-utils + +PATH + remote: ../katalyst-tables + specs: + katalyst-tables (3.0.0.beta1) + katalyst-html-attributes + view_component + PATH remote: . specs: @@ -8,7 +22,6 @@ PATH katalyst-govuk-formbuilder (>= 1.9) katalyst-kpop (>= 3.1) katalyst-navigation - katalyst-tables pagy (>= 8.0) rails (>= 7.1) stimulus-rails @@ -213,9 +226,6 @@ GEM view_component katalyst-govuk-formbuilder (1.9.0) govuk_design_system_formbuilder - katalyst-html-attributes (1.0.1) - activesupport - html-attributes-utils katalyst-kpop (3.1.2) katalyst-html-attributes turbo-rails @@ -225,9 +235,6 @@ GEM katalyst-kpop katalyst-tables view_component - katalyst-tables (2.6.0) - katalyst-html-attributes - view_component language_server-protocol (3.17.0.3) loofah (2.22.0) crass (~> 1.0.2) @@ -471,7 +478,9 @@ DEPENDENCIES faker image_processing katalyst-basic-auth + katalyst-html-attributes! katalyst-koi! + katalyst-tables! propshaft puma rack_session_access diff --git a/katalyst-koi.gemspec b/katalyst-koi.gemspec index 3d811cd25..0c71b4c4d 100644 --- a/katalyst-koi.gemspec +++ b/katalyst-koi.gemspec @@ -39,5 +39,4 @@ Gem::Specification.new do |s| s.add_dependency "katalyst-content" s.add_dependency "katalyst-kpop", ">= 3.1" s.add_dependency "katalyst-navigation" - s.add_dependency "katalyst-tables" end From fbd73b5d6f571712b3e90d187c32a441250a3320 Mon Sep 17 00:00:00 2001 From: Alan Cornthwaite Date: Mon, 13 May 2024 16:46:14 +0930 Subject: [PATCH 5/7] Add koi specific custom typed cells --- .../koi/tables/cells/attachment_component.rb | 60 ++++++++++++ .../koi/tables/cells/link_component.rb | 43 +++++++++ app/components/koi/tables/table_component.rb | 52 ++++++++++- .../tables/cells/attachment_component_spec.rb | 83 +++++++++++++++++ .../koi/tables/cells/link_component_spec.rb | 92 +++++++++++++++++++ spec/support/frontend_examples.rb | 14 +++ spec/support/match_html.rb | 6 +- spec/views/admin/posts/index.html.erb_spec.rb | 5 +- 8 files changed, 348 insertions(+), 7 deletions(-) create mode 100644 app/components/koi/tables/cells/attachment_component.rb create mode 100644 app/components/koi/tables/cells/link_component.rb create mode 100644 spec/components/koi/tables/cells/attachment_component_spec.rb create mode 100644 spec/components/koi/tables/cells/link_component_spec.rb create mode 100644 spec/support/frontend_examples.rb diff --git a/app/components/koi/tables/cells/attachment_component.rb b/app/components/koi/tables/cells/attachment_component.rb new file mode 100644 index 000000000..c9e6fa007 --- /dev/null +++ b/app/components/koi/tables/cells/attachment_component.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Koi + module Tables + module Cells + # Shows an attachment + # + # The value is expected to be an ActiveStorage attachment + # + # If it is representable, shows as a image tag using the specified variant. + # + # Otherwise shows as a link to download. + class AttachmentComponent < Katalyst::Tables::CellComponent + def initialize(variant:, **) + super(**) + + @variant = variant + end + + def rendered_value + representation + end + + def representation + if value.try(:variable?) && named_variant.present? + image_tag(value.variant(@variant)) + elsif value.try(:attached?) + filename.to_s + else + "" + end + end + + def filename + value.blob.filename + end + + # Utility for accessing the path Rails provides for retrieving the + # attachment for use in cells. Example: + # <% row.attachment :file do |cell| %> + # <%= link_to "Download", cell.internal_path %> + # <% end %> + def internal_path + rails_blob_path(value, disposition: :attachment) + end + + private + + def default_html_attributes + { class: "type-attachment" } + end + + # Find the reflective variant by name (i.e. :thumb by default) + def named_variant + record.attachment_reflections[@column.to_s].named_variants[@variant.to_sym] + end + end + end + end +end diff --git a/app/components/koi/tables/cells/link_component.rb b/app/components/koi/tables/cells/link_component.rb new file mode 100644 index 000000000..b1d2eda45 --- /dev/null +++ b/app/components/koi/tables/cells/link_component.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Koi + module Tables + module Cells + # Displays a link to the record + # The link text is the value of the attribute + class LinkComponent < Katalyst::Tables::CellComponent + define_html_attribute_methods :link_attributes + + def initialize(url:, link:, **) + super(**) + + @url = url + + self.link_attributes = link + end + + def rendered_value + link_to(value, url, **link_attributes) + end + + def url + case @url + when Symbol + # helpers are not available until the component is rendered + @url = helpers.public_send(@url, record) + when Proc + @url = @url.call(record) + else + @url + end + end + + private + + def default_html_attributes + { class: "type-link" } + end + end + end + end +end diff --git a/app/components/koi/tables/table_component.rb b/app/components/koi/tables/table_component.rb index 62a865d59..b742560c7 100644 --- a/app/components/koi/tables/table_component.rb +++ b/app/components/koi/tables/table_component.rb @@ -2,9 +2,57 @@ module Koi module Tables - # Custom table component, in order to override the default header and body row components - # which enables us to use our own custom header and body cell components class TableComponent < Katalyst::TableComponent + # Generates a column that links to the record's show page (by default). + # + # @param column [Symbol] the column's name, called as a method on the record + # @param label [String|nil] the label to use for the column header + # @param heading [boolean] if true, data cells will use `th` tags + # @param url [Symbol|String|Proc] arguments for url_For, defaults to the record + # @param link [Hash] options to be passed to the link_to helper + # @param ** [Hash] HTML attributes to be added to column cells + # @param & [Proc] optional block to alter the cell content + # + # If a block is provided, it will be called with the link cell component as an argument. + # @yieldparam cell [Katalyst::Tables::Cells::LinkComponent] the cell component + # + # @return [void] + # + # @example Render a column containing the record's title, linked to its show page + # <% row.link :title %> # => About us + # @example Render a column containing the record's title, linked to its edit page + # <% row.link :title, url: :edit_admin_post_path do |cell| %> + # Edit <%= cell %> + # <% end %> + # # => Edit About us + def link(column, label: nil, heading: false, url: [:admin, record], link: {}, **, &) + with_cell(Tables::Cells::LinkComponent.new( + collection:, row:, column:, record:, label:, heading:, url:, link:, **, + ), &) + end + + # Generates a column that renders an ActiveStorage attachment as a downloadable link. + # + # @param column [Symbol] the column's name, called as a method on the record + # @param label [String|nil] the label to use for the column header + # @param heading [boolean] if true, data cells will use `th` tags + # @param variant [Symbol] the variant to use when rendering the image (default :thumb) + # @param ** [Hash] HTML attributes to be added to column cells + # @param & [Proc] optional block to alter the cell content + # + # If a block is provided, it will be called with the attachment cell component as an argument. + # @yieldparam cell [Katalyst::Tables::Cells::AttachmentComponent] the cell component + # + # @return [void] + # + # @example Render a column containing a download link to the record's background image + # <% row.attachment :background %> # => background.png + def attachment(column, label: nil, heading: false, variant: :thumb, **, &) + with_cell(Tables::Cells::AttachmentComponent.new( + collection:, row:, column:, record:, label:, heading:, variant:, **, + ), &) + end + def default_html_attributes { class: "index-table" } end diff --git a/spec/components/koi/tables/cells/attachment_component_spec.rb b/spec/components/koi/tables/cells/attachment_component_spec.rb new file mode 100644 index 000000000..850b1694b --- /dev/null +++ b/spec/components/koi/tables/cells/attachment_component_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Koi::Tables::Cells::AttachmentComponent do + let(:table) { Koi::Tables::TableComponent.new(collection:) } + let(:collection) { create_list(:banner, 1, :with_image) } + let(:rendered) { render_inline(table) { |row| row.attachment(:image) } } + let(:label) { rendered.at_css("thead th") } + let(:data) { rendered.at_css("tbody td") } + + it "renders column header" do + expect(label).to match_html(<<~HTML) + Image + HTML + end + + it "renders column data" do + expect(data).to have_css("td.type-attachment img[src*='dummy.png']") + end + + context "with html_options" do + let(:rendered) { render_inline(table) { |row| row.attachment(:image, **Test::HTML_ATTRIBUTES) } } + + it "renders header with html_options" do + expect(label).to match_html(<<~HTML) + Image + HTML + end + + it "renders data with html_options" do + expect(data).to have_css("td.type-attachment.CLASS[data-foo=bar] img[src*='dummy.png']") + end + end + + context "when given a label" do + let(:rendered) { render_inline(table) { |row| row.attachment(:image, label: "LABEL") } } + + it "renders header with label" do + expect(label).to match_html(<<~HTML) + LABEL + HTML + end + + it "renders data without label" do + expect(data).to have_css("td.type-attachment img[src*='dummy.png']") + end + end + + context "when given an empty label" do + let(:rendered) { render_inline(table) { |row| row.attachment(:image, label: "") } } + + it "renders header with an empty label" do + expect(label).to match_html(<<~HTML) + + HTML + end + end + + context "with nil data value" do + let(:collection) { build_list(:banner, 1) } + + it "renders data as falsey" do + expect(data).to match_html(<<~HTML) + + HTML + end + end + + context "when given a block" do + let(:rendered) { render_inline(table) { |row| row.attachment(:image) { |cell| cell.tag.span(cell) } } } + + it "renders the default header" do + expect(label).to match_html(<<~HTML) + Image + HTML + end + + it "renders the custom data" do + expect(data).to have_css("td.type-attachment > span > img[src*='dummy.png']") + end + end +end diff --git a/spec/components/koi/tables/cells/link_component_spec.rb b/spec/components/koi/tables/cells/link_component_spec.rb new file mode 100644 index 000000000..8730f6bb6 --- /dev/null +++ b/spec/components/koi/tables/cells/link_component_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Koi::Tables::Cells::LinkComponent do + let(:table) { Koi::Tables::TableComponent.new(collection:) } + let(:collection) { create_list(:post, 1) } + let(:rendered) { render_inline(table) { |row, _post| row.link(:name) } } + let(:label) { rendered.at_css("thead th") } + let(:data) { rendered.at_css("tbody td") } + + it "renders column header" do + expect(label).to match_html(<<~HTML) + Name + HTML + end + + it "renders column data" do + expect(data).to match_html(<<~HTML) + #{collection.first.name} + HTML + end + + context "with html_options" do + let(:rendered) { render_inline(table) { |row| row.link(:name, **Test::HTML_ATTRIBUTES) } } + + it "renders header with html_options" do + expect(label).to match_html(<<~HTML) + Name + HTML + end + + it "renders data with html_options" do + expect(data).to match_html(<<~HTML) + #{collection.first.name} + HTML + end + end + + context "when given a label" do + let(:rendered) { render_inline(table) { |row| row.link(:name, label: "LABEL") } } + + it "renders header with label" do + expect(label).to match_html(<<~HTML) + LABEL + HTML + end + + it "renders data without label" do + expect(data).to match_html(<<~HTML) + #{collection.first.name} + HTML + end + end + + context "when given an empty label" do + let(:rendered) { render_inline(table) { |row| row.link(:name, label: "") } } + + it "renders header with an empty label" do + expect(label).to match_html(<<~HTML) + + HTML + end + end + + context "with nil data value" do + let(:rendered) { render_inline(table) { |row| row.link(:name) } } + let(:collection) { create_list(:post, 1, name: nil) } + + it "renders data as url" do + expect(data).to match_html(<<~HTML) + /admin/posts/#{collection.first.id} + HTML + end + end + + context "when given a block" do + let(:rendered) { render_inline(table) { |row| row.link(:name) { |cell| cell.tag.span(cell) } } } + + it "renders the default header" do + expect(label).to match_html(<<~HTML) + Name + HTML + end + + it "renders the custom data" do + expect(data).to match_html(<<~HTML) + #{collection.first.name} + HTML + end + end +end diff --git a/spec/support/frontend_examples.rb b/spec/support/frontend_examples.rb new file mode 100644 index 000000000..518ad61d0 --- /dev/null +++ b/spec/support/frontend_examples.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "rack/request" +require "rails_helper" + +module Test + HTML_ATTRIBUTES = { + id: "ID", + class: "CLASS", + html: { style: "style" }, + data: { foo: "bar" }, + aria: { label: "LABEL" }, + }.freeze +end diff --git a/spec/support/match_html.rb b/spec/support/match_html.rb index aad1e6ad8..b50d2b939 100644 --- a/spec/support/match_html.rb +++ b/spec/support/match_html.rb @@ -27,8 +27,8 @@ def initialize(expected_html, **options) def matches?(response) case response when Nokogiri::XML::Node - @actual_doc = response @actual_html = response.to_html + @actual_doc = Nokogiri::HTML.fragment(@actual_html) else @actual_html = response @actual_doc = Nokogiri::HTML.fragment(response) @@ -57,8 +57,8 @@ def diff module RSpec module Matchers - def match_html(expected_html, **options) - HTMLMatcher.new(expected_html, **options) + def match_html(expected_html, **) + HTMLMatcher.new(expected_html, **) end end end diff --git a/spec/views/admin/posts/index.html.erb_spec.rb b/spec/views/admin/posts/index.html.erb_spec.rb index b6724f8b8..7f733623c 100644 --- a/spec/views/admin/posts/index.html.erb_spec.rb +++ b/spec/views/admin/posts/index.html.erb_spec.rb @@ -12,12 +12,13 @@ view.extend(Pagy::Frontend) collection = Admin::PostsController::Collection.new.apply(Post.all) - table = Koi::Tables::TableComponent.new(collection:, id: "index-table") # Workaround for https://github.com/rspec/rspec-rails/issues/2729 view.lookup_context.prefixes.prepend "admin/posts" - render locals: { collection:, table: } + allow(view).to receive(:default_table_component_class).and_return(Koi::Tables::TableComponent) + + render locals: { collection: } end it { expect(rendered).to have_css("th", text: "Name") } From e5819adc56831691420437b6d400cb12d03ab4a0 Mon Sep 17 00:00:00 2001 From: Alan Cornthwaite Date: Mon, 13 May 2024 16:49:41 +0930 Subject: [PATCH 6/7] Fix: naming conflict when running "test" --- .../koi/active_record_generator_spec.rb | 16 ++++++++-------- spec/generators/koi/admin_generator_spec.rb | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/generators/koi/active_record_generator_spec.rb b/spec/generators/koi/active_record_generator_spec.rb index 3a1364392..78e0b92fc 100644 --- a/spec/generators/koi/active_record_generator_spec.rb +++ b/spec/generators/koi/active_record_generator_spec.rb @@ -6,23 +6,23 @@ RSpec.describe Koi::ActiveRecordGenerator do subject(:output) do - gen = generator(%w(test title:string slug:string content:rich_text)) + gen = generator(%w(resource title:string slug:string content:rich_text)) Ammeter::OutputCapturer.capture(:stdout) { gen.invoke_all } end it "runs the expected creation steps" do expect(output.lines.grep(/create/).map { |l| l.split.last }).to contain_exactly( - "app/models/test.rb", - %r{db/migrate/\d+_create_tests.rb}, - "spec/factories/tests.rb", - "spec/models/test_spec.rb", + "app/models/resource.rb", + %r{db/migrate/\d+_create_resources.rb}, + "spec/factories/resources.rb", + "spec/models/resource_spec.rb", ) end it "creates the expected files" do output - expect(Pathname.new(file("app/models/test.rb"))).to exist - expect(Pathname.new(migration_file("db/migrate/create_tests.rb"))).to exist - expect(Pathname.new(file("spec/models/test_spec.rb"))).to exist + expect(Pathname.new(file("app/models/resource.rb"))).to exist + expect(Pathname.new(migration_file("db/migrate/create_resources.rb"))).to exist + expect(Pathname.new(file("spec/models/resource_spec.rb"))).to exist end end diff --git a/spec/generators/koi/admin_generator_spec.rb b/spec/generators/koi/admin_generator_spec.rb index 11991d222..0c638f80d 100644 --- a/spec/generators/koi/admin_generator_spec.rb +++ b/spec/generators/koi/admin_generator_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Koi::AdminGenerator do subject(:output) do - gen = generator(%w(test title:text content:rich_text)) + gen = generator(%w(resource title:text content:rich_text)) Ammeter::OutputCapturer.capture(:stdout) { gen.invoke_all } end From ad21fe642ec658deeaee929075d0693bdc932513 Mon Sep 17 00:00:00 2001 From: Alan Cornthwaite Date: Tue, 14 May 2024 11:03:23 +0930 Subject: [PATCH 7/7] Churn: dependency updates --- Gemfile.lock | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5a8bbaed0..b7ff96584 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,14 @@ PATH remote: ../html-attributes specs: - katalyst-html-attributes (1.0.2) + katalyst-html-attributes (1.1.0) activesupport html-attributes-utils PATH remote: ../katalyst-tables specs: - katalyst-tables (3.0.0.beta1) + katalyst-tables (3.0.0) katalyst-html-attributes view_component @@ -129,7 +129,7 @@ GEM erubi (~> 1.4) parser (>= 2.4) smart_properties - bigdecimal (3.1.7) + bigdecimal (3.1.8) bindata (2.5.0) brakeman (6.1.2) racc @@ -201,7 +201,7 @@ GEM hashdiff (1.1.0) html-attributes-utils (1.0.2) activesupport (>= 6.1.4.4) - i18n (1.14.4) + i18n (1.14.5) concurrent-ruby (~> 1.0) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) @@ -211,8 +211,8 @@ GEM activesupport (>= 6.0.0) railties (>= 6.0.0) io-console (0.7.2) - irb (1.12.0) - rdoc + irb (1.13.1) + rdoc (>= 4.0.0) reline (>= 0.4.2) json (2.7.2) jwt (2.8.1) @@ -224,13 +224,13 @@ GEM katalyst-html-attributes katalyst-kpop view_component - katalyst-govuk-formbuilder (1.9.0) + katalyst-govuk-formbuilder (1.9.1) govuk_design_system_formbuilder katalyst-kpop (3.1.2) katalyst-html-attributes turbo-rails view_component - katalyst-navigation (1.6.0) + katalyst-navigation (1.7.0) katalyst-html-attributes katalyst-kpop katalyst-tables @@ -251,7 +251,7 @@ GEM mini_mime (1.1.5) minitest (5.22.3) mutex_m (0.2.0) - net-imap (0.4.10) + net-imap (0.4.11) date net-protocol net-pop (0.1.2) @@ -260,17 +260,17 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.7.1) - nokogiri (1.16.4-aarch64-linux) + nio4r (2.7.3) + nokogiri (1.16.5-aarch64-linux) racc (~> 1.4) - nokogiri (1.16.4-arm64-darwin) + nokogiri (1.16.5-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.4-x86_64-linux) + nokogiri (1.16.5-x86_64-linux) racc (~> 1.4) openssl (3.2.0) openssl-signature_algorithm (1.3.0) openssl (> 2.0) - pagy (8.3.0) + pagy (8.4.0) parallel (1.24.0) parser (3.3.1.0) ast (~> 2.4.1) @@ -286,7 +286,7 @@ GEM puma (6.4.2) nio4r (~> 2.0) racc (1.7.3) - rack (3.0.10) + rack (3.0.11) rack-session (2.0.0) rack (>= 3.0.0) rack-test (2.1.0) @@ -334,8 +334,8 @@ GEM rake (13.2.1) rdoc (6.6.3.1) psych (>= 4.0.0) - regexp_parser (2.9.0) - reline (0.5.3) + regexp_parser (2.9.1) + reline (0.5.6) io-console (~> 0.5) rexml (3.2.6) rspec-core (3.13.0) @@ -343,7 +343,7 @@ GEM rspec-expectations (3.13.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.0) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-rails (6.1.2) @@ -355,7 +355,7 @@ GEM rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-support (3.13.1) - rubocop (1.63.4) + rubocop (1.63.5) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -366,8 +366,8 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.2) - parser (>= 3.3.0.4) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) rubocop-capybara (2.20.0) rubocop (~> 1.41) rubocop-factory_bot (2.25.1) @@ -388,7 +388,7 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) rubocop-rake (0.6.0) rubocop (~> 1.0) - rubocop-rspec (2.29.1) + rubocop-rspec (2.29.2) rubocop (~> 1.40) rubocop-capybara (~> 2.17) rubocop-factory_bot (~> 2.22) @@ -400,11 +400,11 @@ GEM ffi (~> 1.12) safety_net_attestation (0.4.0) jwt (~> 2.0) - sass-embedded (1.75.0-aarch64-linux-gnu) + sass-embedded (1.77.1-aarch64-linux-gnu) google-protobuf (>= 3.25, < 5.0) - sass-embedded (1.75.0-arm64-darwin) + sass-embedded (1.77.1-arm64-darwin) google-protobuf (>= 3.25, < 5.0) - sass-embedded (1.75.0-x86_64-linux-gnu) + sass-embedded (1.77.1-x86_64-linux-gnu) google-protobuf (>= 3.25, < 5.0) sentry-rails (5.17.3) railties (>= 5.0)