discourse/lib/turbo_tests/base_formatter.rb
Sérgio Saquetim 726fc21187
DEV: Enforce deprecation-free tests for preinstalled plugins/themes (#36445)
This PR implements stricter deprecation handling that enforces
deprecation-free tests for core and preinstalled plugins, while allowing
custom (non-preinstalled) plugins and themes to have deprecations
without causing test failures.

### Key Changes

#### CI Workflow Improvements
- Split plugin system tests into separate CI targets: `core-plugins`,
`official-plugins`, and `chat`
- Enhance `bin/turbo_rspec` to accept comma-separated exclude patterns
via `--exclude-pattern`
- Simplify workflow configuration with default `shell: bash` and
consolidated environment variables

#### Plugin Classification & Detection
- Centralize official plugins list in `config/official_plugins.json` for
unified backend and frontend access
- Detect preinstalled plugins by checking for absence of `.git`
directory
- Add `isOfficial` and `isPreinstalled` metadata flags to plugin info
- Add `data-preinstalled` and `data-official` attributes to all plugin
and theme script tags for runtime identification

#### Deprecation Source Tracking
- Track deprecation sources (core, plugin, or theme) through template
map and resolver to attribute deprecations correctly
- Improve `source-identifier.js` to detect admin UI plugin files in both
development and production environments
- Add source information to deprecation messages for better debugging

#### Test Infrastructure
- Modify `raise-on-deprecation` test helper to skip errors for custom
(non-preinstalled) plugins and themes
- Add `EMBER_RAISE_ON_DEPRECATION` environment variable to control
deprecation throwing behavior in Rails tests
- Automatically set `EMBER_RAISE_ON_DEPRECATION` for core and
preinstalled plugin/theme specs in `rails_helper.rb`
- Improve deprecation summary output for system specs with test/spec
origin tracking

#### Deprecation Workflow Enhancements
- Add `dont-throw` handler for selective deprecation bypassing in test
fixtures without raising errors
- Add `dont-count` handler for preventing deprecation counting in
specific scenarios (e.g., test fixtures)

#### Deprecation Fixes
- Fix pending deprecations across core plugins (chat, data-explorer,
discourse-subscriptions, gamification, house-ads, reactions,
rss-polling, styleguide)
- Update import paths and remove deprecated patterns
- Migrate deprecated Handlebars templates to JavaScript API

### Testing Strategy

With these changes:
- **Core and preinstalled plugins** must pass all tests without any
deprecations
- **Custom plugins and themes** can have deprecations without failing
tests
- Test fixtures can use `dont-throw` and `dont-count` handlers when
testing deprecation behavior itself
- System specs automatically configure deprecation enforcement based on
test file location

---------

Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2025-12-16 17:48:29 -03:00

125 lines
4.2 KiB
Ruby

# frozen_string_literal: true
RSpec::Support.require_rspec_core "formatters/base_text_formatter"
RSpec::Support.require_rspec_core "formatters/console_codes"
module TurboTests
class BaseFormatter < RSpec::Core::Formatters::BaseTextFormatter
RSpec::Core::Formatters.register(self, :dump_summary)
def dump_summary(notification, timings)
output_slowest_examples(timings) if timings.present?
totals_by_id, totals_by_origin = aggregate_js_deprecations(notification.examples)
output_js_deprecations(totals_by_id, totals_by_origin) if totals_by_id.present?
super(notification)
end
private
def output_slowest_examples(timings)
output.puts "\nTop #{timings.size} Slowest examples:"
timings.each do |(full_description, source_location, duration)|
output.puts " #{full_description}"
output.puts " #{RSpec::Core::Formatters::ConsoleCodes.wrap(duration.to_s + "ms", :bold)} #{source_location}"
end
end
def aggregate_js_deprecations(examples)
totals_by_id = Hash.new(0)
totals_by_origin = Hash.new { |h, k| h[k] = Hash.new(0) }
examples.each do |example|
origin = extract_origin_from_example(example) || "unknown"
example.metadata[:js_deprecations]&.each do |id, count|
totals_by_id[id] += count
totals_by_origin[origin][id] += count
end
end
[totals_by_id, totals_by_origin]
end
def output_js_deprecations(totals_by_id, totals_by_origin)
output.puts "\n[Deprecation Counter] Test run completed with deprecations:\n\n"
deprecations_table = generate_deprecations_table(totals_by_id)
output.puts deprecations_table
origin_table = nil
if totals_by_origin.any?
origin_table = generate_deprecations_by_origin_table(totals_by_origin)
output.puts "\nDeprecations by spec origin:\n\n"
output.puts origin_table
end
write_github_summary(deprecations_table, origin_table)
end
def generate_deprecations_table(totals_by_id)
max_id_length = totals_by_id.keys.map(&:length).max
headers = ["id".ljust(max_id_length), "count".rjust(5)]
rows = totals_by_id.map { |id, count| [id.ljust(max_id_length), count.to_s.rjust(5)] }
build_markdown_table(headers, rows)
end
def generate_deprecations_by_origin_table(totals_by_origin)
all_ids = totals_by_origin.values.flat_map(&:keys).uniq
max_id_length = all_ids.map(&:length).max
origins = totals_by_origin.keys.sort
max_origin_length = [origins.map(&:length).max, 6].max
headers = ["origin".ljust(max_origin_length), "id".ljust(max_id_length), "count".rjust(5)]
rows = []
origins.each do |origin|
origin_deprecations = totals_by_origin[origin]
sorted_ids = origin_deprecations.keys.sort
sorted_ids.each do |id|
count = origin_deprecations[id]
rows += [[origin.ljust(max_origin_length), id.ljust(max_id_length), count.to_s.rjust(5)]]
end
end
build_markdown_table(headers, rows)
end
def build_markdown_table(headers, rows)
table = "| #{headers.join(" | ")} |\n"
table += "| #{headers.map { |h| "-" * h.length }.join(" | ")} |\n"
rows.each { |row| table += "| #{row.join(" | ")} |\n" }
table
end
def write_github_summary(deprecations_table, origin_table)
return unless ENV["GITHUB_ACTIONS"] && ENV["GITHUB_STEP_SUMMARY"]
summary = "### ⚠️ JS Deprecations\n\nTest run completed with deprecations:\n\n"
summary += deprecations_table
summary += "\n\nDeprecations by spec origin:\n\n#{origin_table}" if origin_table
summary += "\n\n"
File.write(ENV["GITHUB_STEP_SUMMARY"], summary)
end
def extract_origin_from_example(example)
example_file_path = example.metadata[:rerun_file_path]
return nil unless example_file_path
expanded_example_file_path = Pathname.new(example_file_path).expand_path
return nil unless expanded_example_file_path.to_s.start_with?(Rails.root.to_s)
extension_match = example_file_path.match(%r{/(plugins|themes)/([^/]+)/})
if extension_match
_type_dir, extension_name = extension_match.captures
extension_name
else
"core"
end
end
end
end