rspec-tracer 1.2.2 → 2.0.0.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +197 -45
- data/README.md +439 -429
- data/bin/rspec-tracer +15 -0
- data/lib/rspec_tracer/cache/Rakefile +43 -0
- data/lib/rspec_tracer/cli/cache_clear.rb +98 -0
- data/lib/rspec_tracer/cli/cache_info.rb +103 -0
- data/lib/rspec_tracer/cli/doctor.rb +275 -0
- data/lib/rspec_tracer/cli/explain.rb +148 -0
- data/lib/rspec_tracer/cli/report_open.rb +82 -0
- data/lib/rspec_tracer/cli.rb +116 -0
- data/lib/rspec_tracer/configuration.rb +1100 -3
- data/lib/rspec_tracer/engine.rb +1076 -0
- data/lib/rspec_tracer/example.rb +21 -6
- data/lib/rspec_tracer/filter.rb +35 -0
- data/lib/rspec_tracer/line_stub.rb +61 -0
- data/lib/rspec_tracer/load_config.rb +2 -2
- data/lib/rspec_tracer/logger.rb +15 -0
- data/lib/rspec_tracer/rails/README.md +78 -0
- data/lib/rspec_tracer/rails/i18n_tracking.rb +137 -0
- data/lib/rspec_tracer/rails/notifications.rb +263 -0
- data/lib/rspec_tracer/rails/preset.rb +94 -0
- data/lib/rspec_tracer/rails/railtie.rb +22 -0
- data/lib/rspec_tracer/rails.rb +15 -0
- data/lib/rspec_tracer/remote_cache/README.md +140 -0
- data/lib/rspec_tracer/remote_cache/Rakefile +35 -11
- data/lib/rspec_tracer/remote_cache/archive.rb +137 -0
- data/lib/rspec_tracer/remote_cache/backend.rb +73 -0
- data/lib/rspec_tracer/remote_cache/git_ancestry.rb +241 -0
- data/lib/rspec_tracer/remote_cache/local_fs_backend.rb +439 -0
- data/lib/rspec_tracer/remote_cache/redis_backend.rb +554 -0
- data/lib/rspec_tracer/remote_cache/s3_backend.rb +712 -0
- data/lib/rspec_tracer/remote_cache/user_tasks.rb +397 -0
- data/lib/rspec_tracer/remote_cache/validator.rb +40 -62
- data/lib/rspec_tracer/remote_cache.rb +22 -0
- data/lib/rspec_tracer/reporters/README.md +103 -0
- data/lib/rspec_tracer/reporters/base.rb +87 -0
- data/lib/rspec_tracer/reporters/coverage_json_reporter.rb +338 -0
- data/lib/rspec_tracer/reporters/html/.gitignore +19 -0
- data/lib/rspec_tracer/reporters/html/.prettierignore +4 -0
- data/lib/rspec_tracer/reporters/html/.prettierrc.json +9 -0
- data/lib/rspec_tracer/reporters/html/README.md +80 -0
- data/lib/rspec_tracer/reporters/html/dist/assets/index.css +2 -0
- data/lib/rspec_tracer/reporters/html/dist/assets/index.js +1 -0
- data/lib/rspec_tracer/reporters/html/dist/index.html +24 -0
- data/lib/rspec_tracer/reporters/html/eslint.config.js +62 -0
- data/lib/rspec_tracer/reporters/html/package-lock.json +4941 -0
- data/lib/rspec_tracer/reporters/html/package.json +29 -0
- data/lib/rspec_tracer/reporters/html/src/app.jsx +130 -0
- data/lib/rspec_tracer/reporters/html/src/components/AllExamples.jsx +86 -0
- data/lib/rspec_tracer/reporters/html/src/components/DuplicateExamples.jsx +68 -0
- data/lib/rspec_tracer/reporters/html/src/components/ExamplesDependency.jsx +78 -0
- data/lib/rspec_tracer/reporters/html/src/components/FilesDependency.jsx +72 -0
- data/lib/rspec_tracer/reporters/html/src/components/FlakyExamples.jsx +42 -0
- data/lib/rspec_tracer/reporters/html/src/components/ReportTable.jsx +131 -0
- data/lib/rspec_tracer/reporters/html/src/components/SearchBar.jsx +19 -0
- data/lib/rspec_tracer/reporters/html/src/index.html +23 -0
- data/lib/rspec_tracer/reporters/html/src/main.jsx +37 -0
- data/lib/rspec_tracer/reporters/html/src/styles.css +434 -0
- data/lib/rspec_tracer/reporters/html/vite.config.js +42 -0
- data/lib/rspec_tracer/reporters/html_reporter.rb +266 -0
- data/lib/rspec_tracer/reporters/json_reporter.rb +88 -0
- data/lib/rspec_tracer/reporters/payload_builder.rb +235 -0
- data/lib/rspec_tracer/reporters/registry.rb +120 -0
- data/lib/rspec_tracer/reporters/terminal_reporter.rb +264 -0
- data/lib/rspec_tracer/rspec/README.md +73 -0
- data/lib/rspec_tracer/rspec/installation.rb +97 -0
- data/lib/rspec_tracer/rspec/metadata.rb +96 -0
- data/lib/rspec_tracer/rspec/parallel_tests.rb +459 -0
- data/lib/rspec_tracer/rspec/reporter_hook.rb +84 -0
- data/lib/rspec_tracer/rspec/runner_hook.rb +178 -0
- data/lib/rspec_tracer/source_file.rb +24 -7
- data/lib/rspec_tracer/storage/README.md +35 -0
- data/lib/rspec_tracer/storage/backend.rb +68 -0
- data/lib/rspec_tracer/storage/json_backend.rb +866 -0
- data/lib/rspec_tracer/storage/lazy_snapshot.rb +65 -0
- data/lib/rspec_tracer/storage/schema.rb +43 -0
- data/lib/rspec_tracer/storage/serializer/json.rb +41 -0
- data/lib/rspec_tracer/storage/serializer/msgpack.rb +90 -0
- data/lib/rspec_tracer/storage/snapshot.rb +127 -0
- data/lib/rspec_tracer/storage/sqlite_backend.rb +686 -0
- data/lib/rspec_tracer/time_formatter.rb +37 -18
- data/lib/rspec_tracer/tracker/README.md +36 -0
- data/lib/rspec_tracer/tracker/coverage_adapter.rb +174 -0
- data/lib/rspec_tracer/tracker/declared_globs.rb +100 -0
- data/lib/rspec_tracer/tracker/dependency_graph.rb +134 -0
- data/lib/rspec_tracer/tracker/env_matcher.rb +127 -0
- data/lib/rspec_tracer/tracker/env_snapshot.rb +77 -0
- data/lib/rspec_tracer/tracker/example_registry.rb +153 -0
- data/lib/rspec_tracer/tracker/file_digest.rb +61 -0
- data/lib/rspec_tracer/tracker/filter.rb +127 -0
- data/lib/rspec_tracer/tracker/input.rb +99 -0
- data/lib/rspec_tracer/tracker/io_hooks/file.rb +55 -0
- data/lib/rspec_tracer/tracker/io_hooks/io.rb +24 -0
- data/lib/rspec_tracer/tracker/io_hooks/json.rb +23 -0
- data/lib/rspec_tracer/tracker/io_hooks/kernel.rb +26 -0
- data/lib/rspec_tracer/tracker/io_hooks/yaml.rb +38 -0
- data/lib/rspec_tracer/tracker/io_hooks.rb +195 -0
- data/lib/rspec_tracer/tracker/loaded_files_tracker.rb +295 -0
- data/lib/rspec_tracer/tracker/new_file_detector.rb +62 -0
- data/lib/rspec_tracer/tracker/whole_suite_invalidators.rb +96 -0
- data/lib/rspec_tracer/version.rb +4 -1
- data/lib/rspec_tracer.rb +232 -381
- metadata +93 -43
- data/lib/rspec_tracer/cache.rb +0 -207
- data/lib/rspec_tracer/coverage_merger.rb +0 -42
- data/lib/rspec_tracer/coverage_reporter.rb +0 -187
- data/lib/rspec_tracer/coverage_writer.rb +0 -58
- data/lib/rspec_tracer/html_reporter/Rakefile +0 -18
- data/lib/rspec_tracer/html_reporter/assets/javascripts/application.js +0 -56
- data/lib/rspec_tracer/html_reporter/assets/javascripts/libraries/jquery.js +0 -10881
- data/lib/rspec_tracer/html_reporter/assets/javascripts/plugins/datatables.js +0 -15381
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/application.css +0 -196
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/datatables.css +0 -459
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/jquery-ui.css +0 -436
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/print.css +0 -92
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/reset.css +0 -265
- data/lib/rspec_tracer/html_reporter/public/application.css +0 -5
- data/lib/rspec_tracer/html_reporter/public/application.js +0 -6
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc_disabled.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_both.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc_disabled.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/favicon.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/loading.gif +0 -0
- data/lib/rspec_tracer/html_reporter/reporter.rb +0 -242
- data/lib/rspec_tracer/html_reporter/views/duplicate_examples.erb +0 -34
- data/lib/rspec_tracer/html_reporter/views/examples.erb +0 -58
- data/lib/rspec_tracer/html_reporter/views/examples_dependency.erb +0 -36
- data/lib/rspec_tracer/html_reporter/views/files_dependency.erb +0 -36
- data/lib/rspec_tracer/html_reporter/views/flaky_examples.erb +0 -38
- data/lib/rspec_tracer/html_reporter/views/layout.erb +0 -38
- data/lib/rspec_tracer/remote_cache/aws.rb +0 -176
- data/lib/rspec_tracer/remote_cache/cache.rb +0 -75
- data/lib/rspec_tracer/remote_cache/repo.rb +0 -210
- data/lib/rspec_tracer/report_generator.rb +0 -158
- data/lib/rspec_tracer/report_merger.rb +0 -68
- data/lib/rspec_tracer/report_writer.rb +0 -141
- data/lib/rspec_tracer/reporter.rb +0 -204
- data/lib/rspec_tracer/rspec_reporter.rb +0 -41
- data/lib/rspec_tracer/rspec_runner.rb +0 -56
- data/lib/rspec_tracer/ruby_coverage.rb +0 -9
- data/lib/rspec_tracer/runner.rb +0 -278
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSpecTracer
|
|
4
|
+
# Reporters subsystem. Houses the default reporters (terminal,
|
|
5
|
+
# JSON, HTML, coverage.json) plus the protocol class
|
|
6
|
+
# ({Base}) that users subclass when shipping custom reporters
|
|
7
|
+
# via `add_reporter MyReporter`.
|
|
8
|
+
module Reporters
|
|
9
|
+
# Abstract base for every reporter rspec-tracer ships. Takes the
|
|
10
|
+
# finalized Storage::Snapshot + report_dir + run_metadata triplet
|
|
11
|
+
# (architectural decision (b), Option X - raw Snapshot, no
|
|
12
|
+
# projection struct) and exposes two lifecycle hooks:
|
|
13
|
+
#
|
|
14
|
+
# - `generate` - subclass must implement; emits the reporter's
|
|
15
|
+
# output format (JSON file, terminal lines, HTML etc.)
|
|
16
|
+
# - `no_op?` - true when the run had zero tracked examples; the
|
|
17
|
+
# Registry checks this before calling `generate` so empty runs
|
|
18
|
+
# don't litter the report_dir with empty artifacts.
|
|
19
|
+
#
|
|
20
|
+
# `initialize` accepts `**opts` so custom reporters can take
|
|
21
|
+
# constructor args via `config.add_reporter MyReporter, color: false`.
|
|
22
|
+
# The base class itself ignores opts; subclasses read what they need.
|
|
23
|
+
#
|
|
24
|
+
# Errors during `generate` are NOT rescued here - the Registry
|
|
25
|
+
# wraps each reporter call in a per-reporter rescue + warn, so a
|
|
26
|
+
# buggy custom reporter never propagates a non-zero exit into the
|
|
27
|
+
# user's test suite (graceful degradation, same contract as
|
|
28
|
+
# Storage backends per ARCHITECTURE.md).
|
|
29
|
+
#
|
|
30
|
+
# @example Registering a custom reporter
|
|
31
|
+
# class MyReporter < RSpecTracer::Reporters::Base
|
|
32
|
+
# def generate
|
|
33
|
+
# File.write(File.join(report_dir, 'my.json'),
|
|
34
|
+
# JSON.pretty_generate(snapshot.to_h))
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# RSpecTracer.configure do
|
|
39
|
+
# add_reporter MyReporter, my_option: true
|
|
40
|
+
# end
|
|
41
|
+
class Base
|
|
42
|
+
# @return [RSpecTracer::Storage::Snapshot, nil] finalized run snapshot.
|
|
43
|
+
# @return [String] absolute report directory.
|
|
44
|
+
# @return [Hash] run_metadata hash (pid / run_time / etc).
|
|
45
|
+
# @return [#info, #warn, nil] tracer logger.
|
|
46
|
+
# @return [Hash] reporter-specific options.
|
|
47
|
+
attr_reader :snapshot, :report_dir, :run_metadata, :logger, :options
|
|
48
|
+
|
|
49
|
+
# @param snapshot [RSpecTracer::Storage::Snapshot] the finalized
|
|
50
|
+
# per-run snapshot (`nil` only on early-abort paths)
|
|
51
|
+
# @param report_dir [String] absolute path the reporter should
|
|
52
|
+
# write into (created by Engine.finalize before this fires)
|
|
53
|
+
# @param run_metadata [Hash] pid / run_time / started_at /
|
|
54
|
+
# cache_path / parallel_tests / rails flags
|
|
55
|
+
# @param logger [#info, #warn, nil] tracer logger
|
|
56
|
+
# @param options [Hash] reporter-specific keyword args from
|
|
57
|
+
# `add_reporter MyReporter, **opts`
|
|
58
|
+
def initialize(snapshot:, report_dir:, run_metadata:, logger: nil, **options)
|
|
59
|
+
@snapshot = snapshot
|
|
60
|
+
@report_dir = report_dir
|
|
61
|
+
@run_metadata = run_metadata || {}
|
|
62
|
+
@logger = logger
|
|
63
|
+
@options = options
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Subclass must implement. Called once per reporter per run by
|
|
67
|
+
# the Registry. Errors propagate to the Registry's per-reporter
|
|
68
|
+
# rescue (graceful degradation: a buggy reporter never breaks
|
|
69
|
+
# the suite).
|
|
70
|
+
#
|
|
71
|
+
# @return [void]
|
|
72
|
+
def generate
|
|
73
|
+
raise NotImplementedError, "#{self.class}#generate must be implemented"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Registry skips `generate` when this returns true so empty runs
|
|
77
|
+
# do not produce empty artifacts.
|
|
78
|
+
#
|
|
79
|
+
# @return [Boolean]
|
|
80
|
+
def no_op?
|
|
81
|
+
snapshot.nil? ||
|
|
82
|
+
snapshot.all_examples.nil? ||
|
|
83
|
+
snapshot.all_examples.empty?
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'set'
|
|
6
|
+
|
|
7
|
+
require_relative 'base'
|
|
8
|
+
require_relative '../line_stub'
|
|
9
|
+
|
|
10
|
+
module RSpecTracer
|
|
11
|
+
# Internal Reporters — see {RSpecTracer} for the user-facing surface.
|
|
12
|
+
# @api private
|
|
13
|
+
module Reporters
|
|
14
|
+
# Single owner of `coverage.json` emission in 2.0. Replaces the
|
|
15
|
+
# legacy CoverageReporter + CoverageWriter + CoverageMerger +
|
|
16
|
+
# RubyCoverage quartet that 1.x carried. Output shape preserved byte-for-byte
|
|
17
|
+
# for downstream consumers (user CI dashboards parse this):
|
|
18
|
+
#
|
|
19
|
+
# { "RSpecTracer": {
|
|
20
|
+
# "coverage": { "<absolute_path>": [Integer|nil, ...], ... },
|
|
21
|
+
# "timestamp": <Integer>
|
|
22
|
+
# } }
|
|
23
|
+
#
|
|
24
|
+
# Cumulative coverage source: `::Coverage.peek_result` at finalize
|
|
25
|
+
# time, routed through `Tracker::CoverageAdapter#peek_unfiltered`
|
|
26
|
+
# so the lib/-wide peek_result call-site count stays at 3 (one
|
|
27
|
+
# each in coverage_adapter.rb, rspec/installation.rb, and
|
|
28
|
+
# tracker/loaded_files_tracker.rb).
|
|
29
|
+
#
|
|
30
|
+
# Skipped-example contribution: `Engine#merge_skipped_coverage`,
|
|
31
|
+
# which replays 1.x's CoverageReporter algorithm verbatim - per-
|
|
32
|
+
# skipped-example per-line strengths add into the cumulative map.
|
|
33
|
+
#
|
|
34
|
+
# SimpleCov interop: when SimpleCov is loaded, this reporter does
|
|
35
|
+
# NOT write coverage.json. Instead it installs a small inner module
|
|
36
|
+
# via `::Coverage.singleton_class.prepend` so SimpleCov's at_exit
|
|
37
|
+
# result-merge sees rspec-tracer's filtered coverage. Matches 1.x
|
|
38
|
+
# behavior + the integration matrix convention.
|
|
39
|
+
#
|
|
40
|
+
# Parallel_tests: per-worker emit happens through the Registry
|
|
41
|
+
# like every other reporter (each worker writes its own
|
|
42
|
+
# coverage.json under the per-worker coverage_path). The elected
|
|
43
|
+
# worker calls `merge_parallel` from `RSpec::ParallelTests` to
|
|
44
|
+
# union the per-worker files into the top-level coverage.json.
|
|
45
|
+
class CoverageJsonReporter < Base
|
|
46
|
+
# Internal constant.
|
|
47
|
+
# @api private
|
|
48
|
+
FILENAME = 'coverage.json'
|
|
49
|
+
|
|
50
|
+
# Concrete implementation of {RSpecTracer::Reporters::Base#generate}.
|
|
51
|
+
# Builds the cumulative coverage payload and writes `coverage.json`
|
|
52
|
+
# under {#report_dir}, except when SimpleCov owns the run (in which
|
|
53
|
+
# case the SimpleCov interop shim takes over).
|
|
54
|
+
#
|
|
55
|
+
# @return [String, nil] absolute path of the written file, or nil
|
|
56
|
+
# when the run wrote nothing (engine missing / SimpleCov interop).
|
|
57
|
+
def generate
|
|
58
|
+
return nil unless RSpecTracer.engine
|
|
59
|
+
return install_simplecov_interop if RSpecTracer.simplecov?
|
|
60
|
+
|
|
61
|
+
coverage = build_coverage
|
|
62
|
+
write_coverage_json(coverage)
|
|
63
|
+
log_stats(coverage)
|
|
64
|
+
target_path
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Coverage emission fires unconditionally - even when no examples
|
|
68
|
+
# ran, 1.x writes coverage.json with whatever boot-time peek_result
|
|
69
|
+
# returned (plus filter + stub). Override Base#no_op? so the
|
|
70
|
+
# parent's "snapshot has zero examples" early-return doesn't gate
|
|
71
|
+
# this emitter.
|
|
72
|
+
def no_op?
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Class-level rollup for parallel_tests. Unions per-line
|
|
77
|
+
# strengths across the per-worker coverage.json files written by
|
|
78
|
+
# `generate` on each worker, then writes the merged top-level
|
|
79
|
+
# coverage.json. Replaces 1.x's
|
|
80
|
+
# `CoverageMerger.new + CoverageWriter.new(top_path, merger)`.
|
|
81
|
+
def self.merge_parallel(peer_paths:, output_path:, logger: nil)
|
|
82
|
+
merged = {}
|
|
83
|
+
peer_paths.each do |peer|
|
|
84
|
+
path = File.join(peer, FILENAME)
|
|
85
|
+
next unless File.file?(path)
|
|
86
|
+
|
|
87
|
+
data = JSON.parse(File.read(path, encoding: 'UTF-8'))
|
|
88
|
+
peer_coverage = data.fetch('RSpecTracer').fetch('coverage')
|
|
89
|
+
peer_coverage.each do |file_path, lines|
|
|
90
|
+
if merged.key?(file_path)
|
|
91
|
+
merge_line_arrays(merged[file_path], lines)
|
|
92
|
+
else
|
|
93
|
+
merged[file_path] = lines.dup
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
99
|
+
payload = { RSpecTracer: { coverage: merged, timestamp: Time.now.utc.to_i } }
|
|
100
|
+
File.write(output_path, JSON.pretty_generate(payload), encoding: 'UTF-8')
|
|
101
|
+
logger&.debug("rspec-tracer: wrote merged coverage.json to #{output_path}")
|
|
102
|
+
output_path
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Internal helper for the tracer pipeline.
|
|
106
|
+
# @api private
|
|
107
|
+
def self.merge_line_arrays(into, from)
|
|
108
|
+
from.each_with_index do |strength, idx|
|
|
109
|
+
next if strength.nil? || into[idx].nil?
|
|
110
|
+
|
|
111
|
+
into[idx] += strength
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
private_class_method :merge_line_arrays
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
# Internal method on the tracer pipeline.
|
|
119
|
+
# @api private
|
|
120
|
+
def build_coverage
|
|
121
|
+
coverage = engine.coverage_adapter.peek_unfiltered
|
|
122
|
+
accumulate_skipped(coverage)
|
|
123
|
+
finalize_coverage_files(coverage)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Shape-agnostic skipped-coverage accumulator. The `coverage`
|
|
127
|
+
# hash carries one of two per-file shapes interchangeably:
|
|
128
|
+
#
|
|
129
|
+
# - `Array<Integer|nil>` (lines-only — peek_unfiltered;
|
|
130
|
+
# the user-facing `coverage.json`
|
|
131
|
+
# contract)
|
|
132
|
+
# - `{ lines: Array, branches: Hash }`
|
|
133
|
+
# (hash-mode — peek_unfiltered_full;
|
|
134
|
+
# what SimpleCov needs when branch
|
|
135
|
+
# coverage is enabled)
|
|
136
|
+
#
|
|
137
|
+
# Both callers (`build_coverage` writing coverage.json,
|
|
138
|
+
# `install_simplecov_interop` feeding SimpleCov.result) share
|
|
139
|
+
# this one accumulator; the per-shape mechanics are isolated in
|
|
140
|
+
# `ensure_mutable_lines`. Hash-mode entries keep their
|
|
141
|
+
# `:branches` sub-hash unchanged (this method only touches
|
|
142
|
+
# lines); array-mode entries are mutated in place. New entries
|
|
143
|
+
# synthesised for files Coverage didn't observe go in as Array
|
|
144
|
+
# (line stubs have no branches to preserve).
|
|
145
|
+
def accumulate_skipped(coverage)
|
|
146
|
+
skipped_ids = engine.registry.ids_with_status(:skipped)
|
|
147
|
+
return coverage if skipped_ids.empty?
|
|
148
|
+
|
|
149
|
+
engine.merge_skipped_coverage(skipped_ids).each do |file_path, line_map|
|
|
150
|
+
line_arr = ensure_mutable_lines(coverage, file_path)
|
|
151
|
+
line_map.each do |line_number, strength|
|
|
152
|
+
idx = line_number.to_i
|
|
153
|
+
line_arr[idx] = (line_arr[idx] || 0) + strength
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
coverage
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Returns the mutable `Array<Integer|nil>` view for
|
|
160
|
+
# `coverage[file_path]` we can write per-line strengths into,
|
|
161
|
+
# rewriting `coverage[file_path]` in-place when a dup or stub
|
|
162
|
+
# was needed. `Coverage.peek_result` returns frozen line arrays
|
|
163
|
+
# on Ruby 3.2+; dup-on-write so subsequent writes share the
|
|
164
|
+
# mutable copy.
|
|
165
|
+
#
|
|
166
|
+
# nil → install line_stub (Array shape; new entry
|
|
167
|
+
# has no branches to preserve).
|
|
168
|
+
# Hash{lines:,} → if `:lines` is frozen / nil, replace the
|
|
169
|
+
# Hash entry with one carrying a fresh /
|
|
170
|
+
# dup'd lines array + the original branches.
|
|
171
|
+
# Array → if frozen, dup + replace; else mutate in
|
|
172
|
+
# place.
|
|
173
|
+
def ensure_mutable_lines(coverage, file_path)
|
|
174
|
+
existing = coverage[file_path]
|
|
175
|
+
|
|
176
|
+
case existing
|
|
177
|
+
when nil
|
|
178
|
+
stub = line_stub(file_path)
|
|
179
|
+
coverage[file_path] = stub
|
|
180
|
+
stub
|
|
181
|
+
when ::Hash
|
|
182
|
+
line_arr = existing[:lines]
|
|
183
|
+
return line_arr if line_arr && !line_arr.frozen?
|
|
184
|
+
|
|
185
|
+
line_arr = line_arr ? line_arr.dup : line_stub(file_path)
|
|
186
|
+
coverage[file_path] = { lines: line_arr, branches: existing[:branches] || {} }
|
|
187
|
+
line_arr
|
|
188
|
+
else
|
|
189
|
+
return existing unless existing.frozen?
|
|
190
|
+
|
|
191
|
+
dup = existing.dup
|
|
192
|
+
coverage[file_path] = dup
|
|
193
|
+
dup
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Apply `coverage_filters` + `coverage_tracked_files` glob; sort
|
|
198
|
+
# the resulting file list (matches legacy CoverageReporter#
|
|
199
|
+
# final_coverage_files at coverage_reporter.rb:113-131) and slice
|
|
200
|
+
# + line-stub missing entries.
|
|
201
|
+
def finalize_coverage_files(coverage)
|
|
202
|
+
all = coverage.keys.to_set
|
|
203
|
+
|
|
204
|
+
if RSpecTracer.coverage_tracked_files
|
|
205
|
+
Dir[RSpecTracer.coverage_tracked_files].each do |name|
|
|
206
|
+
all << File.expand_path(name, RSpecTracer.root)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
all.select! do |file_path|
|
|
211
|
+
name = file_name_for(file_path)
|
|
212
|
+
RSpecTracer.coverage_filters.none? { |filter| filter.match?(file_name: name) }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
all.sort.to_h { |file_path| [file_path, coverage[file_path] || line_stub(file_path).freeze] }
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Internal method on the tracer pipeline.
|
|
219
|
+
# @api private
|
|
220
|
+
def file_name_for(file_path)
|
|
221
|
+
prefix = "#{RSpecTracer.root}/"
|
|
222
|
+
return file_path unless file_path.start_with?(prefix)
|
|
223
|
+
|
|
224
|
+
file_path.sub(/\A#{Regexp.escape(RSpecTracer.root)}/, '')
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Internal method on the tracer pipeline.
|
|
228
|
+
# @api private
|
|
229
|
+
def write_coverage_json(coverage)
|
|
230
|
+
path = target_path
|
|
231
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
232
|
+
payload = { RSpecTracer: { coverage: coverage, timestamp: Time.now.utc.to_i } }
|
|
233
|
+
File.write(path, JSON.pretty_generate(payload), encoding: 'UTF-8')
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Internal method on the tracer pipeline.
|
|
237
|
+
# @api private
|
|
238
|
+
def target_path
|
|
239
|
+
File.join(RSpecTracer.coverage_path, FILENAME)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Internal method on the tracer pipeline.
|
|
243
|
+
# @api private
|
|
244
|
+
def log_stats(coverage)
|
|
245
|
+
total = 0
|
|
246
|
+
covered = 0
|
|
247
|
+
coverage.each_value do |lines|
|
|
248
|
+
lines.each do |strength|
|
|
249
|
+
next if strength.nil?
|
|
250
|
+
|
|
251
|
+
total += 1
|
|
252
|
+
covered += 1 if strength.positive?
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
return if total.zero?
|
|
256
|
+
|
|
257
|
+
percent = (100.0 * covered / total).round(2)
|
|
258
|
+
logger&.info(
|
|
259
|
+
"rspec-tracer: coverage #{covered}/#{total} LOC (#{percent}%) -> #{target_path}"
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Delegates to the top-level RSpecTracer::LineStub which holds
|
|
264
|
+
# the per-engine MRI/JRuby implementations. Kept off the gated
|
|
265
|
+
# path so the JRuby branch (uncoverable on MRI) doesn't fail
|
|
266
|
+
# the 100%-line+branch contract this gated subdir carries.
|
|
267
|
+
def line_stub(file_path)
|
|
268
|
+
RSpecTracer::LineStub.for(file_path)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def engine
|
|
272
|
+
RSpecTracer.engine
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# SimpleCov-branch coverage payload skips coverage_filters +
|
|
276
|
+
# line_stub. 1.x parity: when SimpleCov is loaded, the legacy
|
|
277
|
+
# CoverageReporter.coverage exposed via the RubyCoverage shim
|
|
278
|
+
# is the raw peek + skipped-merge accumulator, NOT the
|
|
279
|
+
# generate_final_coverage post-filter view (the legacy
|
|
280
|
+
# `simplecov? ? run_simplecov_exit_task : run_coverage_exit_task`
|
|
281
|
+
# branch only ran generate_final_coverage on the non-SimpleCov
|
|
282
|
+
# side). Preserving this shape keeps SimpleCov's at_exit
|
|
283
|
+
# result-merge seeing whatever it was tracking minus rspec-
|
|
284
|
+
# tracer's narrow filters.
|
|
285
|
+
#
|
|
286
|
+
# `peek_unfiltered_full` preserves Coverage's full
|
|
287
|
+
# `{lines:, branches:}` hash shape verbatim (vs `peek_unfiltered`
|
|
288
|
+
# which reduces hash-mode entries to `:lines` only for the
|
|
289
|
+
# user-facing `coverage.json` Array<Integer|nil> contract).
|
|
290
|
+
# SimpleCov's branch report needs the branches sub-hash;
|
|
291
|
+
# silently stripping them here was the pre-PR-#165 bug that
|
|
292
|
+
# made every dogfooded suite report `Branch Coverage: 0/0`
|
|
293
|
+
# under `enable_coverage :branch`.
|
|
294
|
+
#
|
|
295
|
+
# `accumulate_skipped` is shape-agnostic (handles both
|
|
296
|
+
# hash-mode and array-mode entries; preserves `:branches`
|
|
297
|
+
# untouched on hash-mode dup-on-write), so we hand it the full
|
|
298
|
+
# peek directly without any view-extraction round-trip. Empty
|
|
299
|
+
# skipped registry → it early-returns + nothing reallocates;
|
|
300
|
+
# non-empty → only the per-skipped-file line arrays get dup'd.
|
|
301
|
+
# Either way, no per-call O(N files) allocation.
|
|
302
|
+
def install_simplecov_interop
|
|
303
|
+
coverage = engine.coverage_adapter.peek_unfiltered_full
|
|
304
|
+
accumulate_skipped(coverage)
|
|
305
|
+
SimpleCovInterop.install(coverage)
|
|
306
|
+
nil
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Inner module preserving 1.x's RubyCoverage shim contract. When
|
|
310
|
+
# SimpleCov calls `::Coverage.result` at its at_exit time, this
|
|
311
|
+
# prepended `result` method returns rspec-tracer's filtered
|
|
312
|
+
# cumulative coverage so SimpleCov's result-merge sees exactly
|
|
313
|
+
# what rspec-tracer saw. Matches the 1.x behavior the
|
|
314
|
+
# integration matrix relies on.
|
|
315
|
+
module SimpleCovInterop
|
|
316
|
+
class << self
|
|
317
|
+
# Internal attribute.
|
|
318
|
+
# @api private
|
|
319
|
+
attr_accessor :coverage
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Internal helper for the tracer pipeline.
|
|
323
|
+
# @api private
|
|
324
|
+
def self.install(coverage)
|
|
325
|
+
self.coverage = coverage
|
|
326
|
+
klass = ::Coverage.singleton_class
|
|
327
|
+
klass.prepend(self) unless klass.ancestors.include?(self)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Internal method on the tracer pipeline.
|
|
331
|
+
# @api private
|
|
332
|
+
def result
|
|
333
|
+
SimpleCovInterop.coverage
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Per-subdirectory gitignore for the HTML reporter frontend toolchain.
|
|
2
|
+
#
|
|
3
|
+
# Committed: src/, dist/, package.json, package-lock.json,
|
|
4
|
+
# vite.config.js, eslint.config.js, .prettierrc.json,
|
|
5
|
+
# .prettierignore, README.md.
|
|
6
|
+
# Not committed: node_modules/ (reproduced from package-lock.json),
|
|
7
|
+
# .vite/ (local Vite cache), build/test artifacts.
|
|
8
|
+
|
|
9
|
+
node_modules/
|
|
10
|
+
.vite/
|
|
11
|
+
.vite-cache/
|
|
12
|
+
coverage/
|
|
13
|
+
*.log
|
|
14
|
+
npm-debug.log*
|
|
15
|
+
yarn-debug.log*
|
|
16
|
+
yarn-error.log*
|
|
17
|
+
pnpm-debug.log*
|
|
18
|
+
.eslintcache
|
|
19
|
+
.DS_Store
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# HTML reporter frontend
|
|
2
|
+
|
|
3
|
+
Source for the single-page HTML report rendered by
|
|
4
|
+
`RSpecTracer::Reporters::HtmlReporter` into
|
|
5
|
+
`<report_dir>/index.html` at finalize-time.
|
|
6
|
+
|
|
7
|
+
## Stack
|
|
8
|
+
|
|
9
|
+
- **Preact 10** — React-identical API at ~3 KB runtime. Five report
|
|
10
|
+
types share a `ReportTable` / `SearchBar` pair, so a component tree
|
|
11
|
+
pays for itself. Users who fork to customize find a universal API;
|
|
12
|
+
end users who open the report get a tiny bundle.
|
|
13
|
+
- **Vite** — zero-config JSX + CSS pipeline, deterministic output.
|
|
14
|
+
- **No TypeScript** — plain JSX keeps the toolchain surface small. Add
|
|
15
|
+
`.ts` if a future widget earns it.
|
|
16
|
+
|
|
17
|
+
No test framework on the frontend side. The single source of truth for
|
|
18
|
+
rendered output is the Ruby-side golden spec
|
|
19
|
+
(`spec/fixtures/golden/html_reporter/index.html`) which compares the
|
|
20
|
+
byte-identical output of `HtmlReporter#generate` against a committed
|
|
21
|
+
baseline. Drift in components shows up there.
|
|
22
|
+
|
|
23
|
+
## Layout
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
lib/rspec_tracer/reporters/html/
|
|
27
|
+
├── package.json # dependencies pinned, lock committed
|
|
28
|
+
├── package-lock.json
|
|
29
|
+
├── vite.config.js # stable filenames, no hashing, deterministic
|
|
30
|
+
├── README.md # this file
|
|
31
|
+
├── src/
|
|
32
|
+
│ ├── index.html # template; Ruby injects payload + fallback
|
|
33
|
+
│ ├── main.jsx # boot + hydrate
|
|
34
|
+
│ ├── app.jsx # tab shell + summary
|
|
35
|
+
│ ├── styles.css # all styles (single bundle)
|
|
36
|
+
│ └── components/
|
|
37
|
+
│ ├── ReportTable.jsx # shared primitive: filter + sort
|
|
38
|
+
│ ├── SearchBar.jsx
|
|
39
|
+
│ ├── AllExamples.jsx
|
|
40
|
+
│ ├── DuplicateExamples.jsx
|
|
41
|
+
│ ├── FlakyExamples.jsx
|
|
42
|
+
│ ├── ExamplesDependency.jsx
|
|
43
|
+
│ └── FilesDependency.jsx
|
|
44
|
+
└── dist/ # COMMITTED build output; users never rebuild
|
|
45
|
+
├── index.html
|
|
46
|
+
└── assets/
|
|
47
|
+
├── index.js
|
|
48
|
+
└── index.css
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Rebuilding
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
task reporters:html:build # npm ci + vite build
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The Taskfile wires this into `task gem:build` so local gem builds
|
|
58
|
+
always pick up a fresh dist. On CI, the drift check
|
|
59
|
+
(`task reporters:html:check`) runs a fresh build and fails if
|
|
60
|
+
`git diff --exit-code dist/` is non-empty — committing a src/ change
|
|
61
|
+
without rebuilding fails fast.
|
|
62
|
+
|
|
63
|
+
## Integration with the Ruby reporter
|
|
64
|
+
|
|
65
|
+
At finalize-time, `HtmlReporter#generate`:
|
|
66
|
+
|
|
67
|
+
1. Reads `dist/index.html` (skeleton with marker comments).
|
|
68
|
+
2. Replaces `<!-- RSPEC_TRACER_FALLBACK -->` with server-rendered
|
|
69
|
+
`<table>` HTML for each of the 5 report types. These tables
|
|
70
|
+
satisfy the "works without JavaScript" AC and are removed from
|
|
71
|
+
the DOM by `main.jsx` after Preact hydrates.
|
|
72
|
+
3. Replaces the `<script id="report-data">` body with the reporter
|
|
73
|
+
payload (built by `Reporters::PayloadBuilder`, the same module
|
|
74
|
+
`JsonReporter` uses).
|
|
75
|
+
4. Writes the finished HTML to `<report_dir>/index.html` and copies
|
|
76
|
+
`dist/assets/` to `<report_dir>/assets/`.
|
|
77
|
+
|
|
78
|
+
The reporter emits identically whether JavaScript is available in the
|
|
79
|
+
end user's browser or not. The interactive version is strictly
|
|
80
|
+
additive.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--bg:#f7f8fa;--surface:#fff;--surface-alt:#f0f3f7;--border:#d8dde4;--border-strong:#b0b8c2;--text:#1b1f26;--text-muted:#515b66;--accent:#1a5fb4;--accent-contrast:#fff;--danger:#a6160f;--warning:#8a5a00;--success:#146c2e;--neutral:#4a5460;--skipped:#5f6774;--flaky:#8a3fa6;--focus-ring:#1a5fb4}*{box-sizing:border-box}html,body{background:var(--bg);color:var(--text);margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica Neue,Arial,sans-serif;font-size:14px;line-height:1.45}code{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,monospace;font-size:.92em}a{color:var(--accent)}.noscript-banner{color:#6a4a00;background:#fff4d6;border-bottom:1px solid #e0c98a;padding:12px 20px;font-size:13px}.report-root{max-width:1400px;margin:0 auto;padding:24px}.report-header h1{margin:0 0 12px;font-size:22px}.summary{flex-wrap:wrap;gap:8px;margin:0 0 12px;padding:0;display:flex}.summary-item{background:var(--surface);border:1px solid var(--border);border-radius:6px;flex-direction:column;gap:2px;min-width:90px;padding:10px 14px;display:flex}.summary-item dt{text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted);margin:0;font-size:11px}.summary-item dd{margin:0;font-size:20px;font-weight:600}.summary-passed dd{color:var(--success)}.summary-failed dd{color:var(--danger)}.summary-pending dd{color:var(--warning)}.summary-skipped dd{color:var(--skipped)}.report-meta{color:var(--text-muted);flex-wrap:wrap;gap:16px;margin:0 0 20px;font-size:12px;display:flex}.tab-bar{border-bottom:1px solid var(--border);flex-wrap:wrap;gap:4px;margin-bottom:16px;display:flex}.tab-button{appearance:none;color:var(--text);cursor:pointer;font:inherit;background:0 0;border:1px solid #0000;border-bottom:none;border-top-left-radius:6px;border-top-right-radius:6px;align-items:center;gap:8px;padding:8px 14px;display:inline-flex}.tab-button:hover{background:var(--surface);border-color:var(--border)}.tab-button.is-active{background:var(--surface);border-color:var(--border);border-bottom-color:var(--surface);margin-bottom:-1px;font-weight:600}.tab-button:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.tab-count{background:var(--surface-alt);color:var(--text-muted);border-radius:10px;padding:1px 8px;font-size:11px}.tab-button.is-active .tab-count{background:var(--border);color:var(--text)}.report-panel{background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:20px}.report-table{flex-direction:column;gap:12px;display:flex}.search-bar{flex-direction:column;gap:4px;max-width:360px;display:flex}.search-label{text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted);font-size:11px}.search-input{appearance:none;border:1px solid var(--border);font:inherit;background:var(--surface);color:var(--text);border-radius:4px;padding:6px 10px}.search-input:focus-visible{outline:2px solid var(--focus-ring);outline-offset:1px;border-color:var(--focus-ring)}.report-table__counts{color:var(--text-muted);margin:0;font-size:12px}.data-table{border-collapse:collapse;border:1px solid var(--border);width:100%}.data-table th,.data-table td{border-bottom:1px solid var(--border);text-align:left;vertical-align:top;padding:8px 12px}.data-table thead th{background:var(--surface-alt);color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border-strong);font-size:11px;font-weight:600;position:sticky;top:0}.data-table tbody tr:nth-child(2n){background:var(--surface-alt)}.data-table .is-numeric{text-align:right;font-variant-numeric:tabular-nums}.sort-button{appearance:none;color:inherit;font:inherit;cursor:pointer;text-transform:inherit;letter-spacing:inherit;background:0 0;border:none;align-items:center;gap:4px;padding:0;display:inline-flex}.sort-button:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.sort-indicator{color:var(--text-muted);font-size:10px}.sort-button[data-sort=asc] .sort-indicator,.sort-button[data-sort=desc] .sort-indicator{color:var(--text)}.report-table__empty{text-align:center;color:var(--text-muted);padding:20px;font-style:italic}.cell-description{max-width:520px}.cell-location,.cell-file,.cell-id{color:var(--text-muted)}.cell-deps{max-width:520px}.badge{text-transform:uppercase;letter-spacing:.04em;color:var(--accent-contrast);border-radius:10px;padding:2px 8px;font-size:11px;font-weight:600;display:inline-block}.status-passed{background:var(--success)}.status-failed{background:var(--danger)}.status-pending{background:var(--warning)}.status-skipped{background:var(--skipped)}.status-flaky{background:var(--flaky)}.status-unknown{background:var(--neutral)}.deps-details{margin:0}.deps-details+.deps-details{margin-top:6px}.deps-details summary{cursor:pointer;color:var(--accent)}.deps-details summary::marker{color:var(--text-muted)}.deps-list{margin:6px 0 0;padding-left:18px}.deps-list li{align-items:baseline;gap:8px;padding:2px 0;display:flex}.dep-count{color:var(--text-muted);font-size:12px}.visually-hidden{clip:rect(0, 0, 0, 0)!important;white-space:nowrap!important;border:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}.fallback-root{flex-direction:column;gap:24px;padding:0 0 24px;display:flex}.fallback-section h2{margin:0 0 8px;font-size:16px}.fallback-table{border-collapse:collapse;border:1px solid var(--border);width:100%}.fallback-table caption{text-align:left;color:var(--text-muted);padding:6px 0;font-size:12px}.fallback-table th,.fallback-table td{border-bottom:1px solid var(--border);text-align:left;vertical-align:top;padding:6px 10px}.fallback-table thead th{background:var(--surface-alt);text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted);font-size:11px}@media (prefers-reduced-motion:reduce){*{transition-duration:.01ms!important;animation-duration:.01ms!important}}
|
|
2
|
+
/*$vite$:1*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e,t,n,r,i,a,o,s,c,l,u,d,f,p,m={},h=[],g=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,_=Array.isArray;function v(e,t){for(var n in t)e[n]=t[n];return e}function y(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function b(t,n,r){var i,a,o,s={};for(o in n)o==`key`?i=n[o]:o==`ref`?a=n[o]:s[o]=n[o];if(arguments.length>2&&(s.children=arguments.length>3?e.call(arguments,2):r),typeof t==`function`&&t.defaultProps!=null)for(o in t.defaultProps)s[o]===void 0&&(s[o]=t.defaultProps[o]);return x(t,s,i,a,null)}function x(e,r,i,a,o){var s={type:e,props:r,key:i,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++n,__i:-1,__u:0};return o==null&&t.vnode!=null&&t.vnode(s),s}function S(e){return e.children}function C(e,t){this.props=e,this.context=t}function w(e,t){if(t==null)return e.__?w(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type==`function`?w(e):null}function T(e){if(e.__P&&e.__d){var n=e.__v,r=n.__e,i=[],a=[],o=v({},n);o.__v=n.__v+1,t.vnode&&t.vnode(o),P(e.__P,o,n,e.__n,e.__P.namespaceURI,32&n.__u?[r]:null,i,r??w(n),!!(32&n.__u),a),o.__v=n.__v,o.__.__k[o.__i]=o,ne(i,o,a),n.__e=n.__=null,o.__e!=r&&E(o)}}function E(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),E(e)}function D(e){(!e.__d&&(e.__d=!0)&&r.push(e)&&!O.__r++||i!=t.debounceRendering)&&((i=t.debounceRendering)||a)(O)}function O(){try{for(var e,t=1;r.length;)r.length>t&&r.sort(o),e=r.shift(),t=r.length,T(e)}finally{r.length=O.__r=0}}function ee(e,t,n,r,i,a,o,s,c,l,u){var d,f,p,g,_,v,y,b=r&&r.__k||h,x=t.length;for(c=k(n,t,b,c,x),d=0;d<x;d++)(p=n.__k[d])!=null&&(f=p.__i!=-1&&b[p.__i]||m,p.__i=d,v=P(e,p,f,i,a,o,s,c,l,u),g=p.__e,p.ref&&f.ref!=p.ref&&(f.ref&&I(f.ref,null,p),u.push(p.ref,p.__c||g,p)),_==null&&g!=null&&(_=g),(y=!!(4&p.__u))||f.__k===p.__k?(c=A(p,c,e,y),y&&f.__e&&(f.__e=null)):typeof p.type==`function`&&v!==void 0?c=v:g&&(c=g.nextSibling),p.__u&=-7);return n.__e=_,c}function k(e,t,n,r,i){var a,o,s,c,l,u=n.length,d=u,f=0;for(e.__k=Array(i),a=0;a<i;a++)(o=t[a])!=null&&typeof o!=`boolean`&&typeof o!=`function`?(typeof o==`string`||typeof o==`number`||typeof o==`bigint`||o.constructor==String?o=e.__k[a]=x(null,o,null,null,null):_(o)?o=e.__k[a]=x(S,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[a]=x(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[a]=o,c=a+f,o.__=e,o.__b=e.__b+1,s=null,(l=o.__i=j(o,n,c,d))!=-1&&(d--,(s=n[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(i>u?f--:i<u&&f++),typeof o.type!=`function`&&(o.__u|=4)):l!=c&&(l==c-1?f--:l==c+1?f++:(l>c?f--:f++,o.__u|=4))):e.__k[a]=null;if(d)for(a=0;a<u;a++)(s=n[a])!=null&&!(2&s.__u)&&(s.__e==r&&(r=w(s)),L(s,s));return r}function A(e,t,n,r){var i,a;if(typeof e.type==`function`){for(i=e.__k,a=0;i&&a<i.length;a++)i[a]&&(i[a].__=e,t=A(i[a],t,n,r));return t}e.__e!=t&&(r&&(t&&e.type&&!t.parentNode&&(t=w(e)),n.insertBefore(e.__e,t||null)),t=e.__e);do t&&=t.nextSibling;while(t!=null&&t.nodeType==8);return t}function j(e,t,n,r){var i,a,o,s=e.key,c=e.type,l=t[n],u=l!=null&&(2&l.__u)==0;if(l===null&&s==null||u&&s==l.key&&c==l.type)return n;if(r>+!!u){for(i=n-1,a=n+1;i>=0||a<t.length;)if((l=t[o=i>=0?i--:a++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return o}return-1}function M(e,t,n){t[0]==`-`?e.setProperty(t,n??``):e[t]=n==null?``:typeof n!=`number`||g.test(t)?n:n+`px`}function N(e,t,n,r,i){var a,o;n:if(t==`style`)if(typeof n==`string`)e.style.cssText=n;else{if(typeof r==`string`&&(e.style.cssText=r=``),r)for(t in r)n&&t in n||M(e.style,t,``);if(n)for(t in n)r&&n[t]==r[t]||M(e.style,t,n[t])}else if(t[0]==`o`&&t[1]==`n`)a=t!=(t=t.replace(u,`$1`)),o=t.toLowerCase(),t=o in e||t==`onFocusOut`||t==`onFocusIn`?o.slice(2):t.slice(2),e.l||={},e.l[t+a]=n,n?r?n[l]=r[l]:(n[l]=d,e.addEventListener(t,a?p:f,a)):e.removeEventListener(t,a?p:f,a);else{if(i==`http://www.w3.org/2000/svg`)t=t.replace(/xlink(H|:h)/,`h`).replace(/sName$/,`s`);else if(t!=`width`&&t!=`height`&&t!=`href`&&t!=`list`&&t!=`form`&&t!=`tabIndex`&&t!=`download`&&t!=`rowSpan`&&t!=`colSpan`&&t!=`role`&&t!=`popover`&&t in e)try{e[t]=n??``;break n}catch{}typeof n==`function`||(n==null||!1===n&&t[4]!=`-`?e.removeAttribute(t):e.setAttribute(t,t==`popover`&&n==1?``:n))}}function te(e){return function(n){if(this.l){var r=this.l[n.type+e];if(n[c]==null)n[c]=d++;else if(n[c]<r[l])return;return r(t.event?t.event(n):n)}}}function P(e,n,r,i,a,o,s,c,l,u){var d,f,p,m,g,b,x,w,T,E,D,O,k,A,j,M=n.type;if(n.constructor!==void 0)return null;128&r.__u&&(l=!!(32&r.__u),o=[c=n.__e=r.__e]),(d=t.__b)&&d(n);n:if(typeof M==`function`)try{if(w=n.props,T=M.prototype&&M.prototype.render,E=(d=M.contextType)&&i[d.__c],D=d?E?E.props.value:d.__:i,r.__c?x=(f=n.__c=r.__c).__=f.__E:(T?n.__c=f=new M(w,D):(n.__c=f=new C(w,D),f.constructor=M,f.render=ae),E&&E.sub(f),f.state||={},f.__n=i,p=f.__d=!0,f.__h=[],f._sb=[]),T&&f.__s==null&&(f.__s=f.state),T&&M.getDerivedStateFromProps!=null&&(f.__s==f.state&&(f.__s=v({},f.__s)),v(f.__s,M.getDerivedStateFromProps(w,f.__s))),m=f.props,g=f.state,f.__v=n,p)T&&M.getDerivedStateFromProps==null&&f.componentWillMount!=null&&f.componentWillMount(),T&&f.componentDidMount!=null&&f.__h.push(f.componentDidMount);else{if(T&&M.getDerivedStateFromProps==null&&w!==m&&f.componentWillReceiveProps!=null&&f.componentWillReceiveProps(w,D),n.__v==r.__v||!f.__e&&f.shouldComponentUpdate!=null&&!1===f.shouldComponentUpdate(w,f.__s,D)){n.__v!=r.__v&&(f.props=w,f.state=f.__s,f.__d=!1),n.__e=r.__e,n.__k=r.__k,n.__k.some(function(e){e&&(e.__=n)}),h.push.apply(f.__h,f._sb),f._sb=[],f.__h.length&&s.push(f);break n}f.componentWillUpdate!=null&&f.componentWillUpdate(w,f.__s,D),T&&f.componentDidUpdate!=null&&f.__h.push(function(){f.componentDidUpdate(m,g,b)})}if(f.context=D,f.props=w,f.__P=e,f.__e=!1,O=t.__r,k=0,T)f.state=f.__s,f.__d=!1,O&&O(n),d=f.render(f.props,f.state,f.context),h.push.apply(f.__h,f._sb),f._sb=[];else do f.__d=!1,O&&O(n),d=f.render(f.props,f.state,f.context),f.state=f.__s;while(f.__d&&++k<25);f.state=f.__s,f.getChildContext!=null&&(i=v(v({},i),f.getChildContext())),T&&!p&&f.getSnapshotBeforeUpdate!=null&&(b=f.getSnapshotBeforeUpdate(m,g)),A=d!=null&&d.type===S&&d.key==null?re(d.props.children):d,c=ee(e,_(A)?A:[A],n,r,i,a,o,s,c,l,u),f.base=n.__e,n.__u&=-161,f.__h.length&&s.push(f),x&&(f.__E=f.__=null)}catch(e){if(n.__v=null,l||o!=null)if(e.then){for(n.__u|=l?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;o[o.indexOf(c)]=null,n.__e=c}else{for(j=o.length;j--;)y(o[j]);F(n)}else n.__e=r.__e,n.__k=r.__k,e.then||F(n);t.__e(e,n,r)}else o==null&&n.__v==r.__v?(n.__k=r.__k,n.__e=r.__e):c=n.__e=ie(r.__e,n,r,i,a,o,s,l,u);return(d=t.diffed)&&d(n),128&n.__u?void 0:c}function F(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(F))}function ne(e,n,r){for(var i=0;i<r.length;i++)I(r[i],r[++i],r[++i]);t.__c&&t.__c(n,e),e.some(function(n){try{e=n.__h,n.__h=[],e.some(function(e){e.call(n)})}catch(e){t.__e(e,n.__v)}})}function re(e){return typeof e!=`object`||!e||e.__b>0?e:_(e)?e.map(re):v({},e)}function ie(n,r,i,a,o,s,c,l,u){var d,f,p,h,g,v,b,x=i.props||m,S=r.props,C=r.type;if(C==`svg`?o=`http://www.w3.org/2000/svg`:C==`math`?o=`http://www.w3.org/1998/Math/MathML`:o||=`http://www.w3.org/1999/xhtml`,s!=null){for(d=0;d<s.length;d++)if((g=s[d])&&`setAttribute`in g==!!C&&(C?g.localName==C:g.nodeType==3)){n=g,s[d]=null;break}}if(n==null){if(C==null)return document.createTextNode(S);n=document.createElementNS(o,C,S.is&&S),l&&=(t.__m&&t.__m(r,s),!1),s=null}if(C==null)x===S||l&&n.data==S||(n.data=S);else{if(s&&=e.call(n.childNodes),!l&&s!=null)for(x={},d=0;d<n.attributes.length;d++)x[(g=n.attributes[d]).name]=g.value;for(d in x)g=x[d],d==`dangerouslySetInnerHTML`?p=g:d==`children`||d in S||d==`value`&&`defaultValue`in S||d==`checked`&&`defaultChecked`in S||N(n,d,null,g,o);for(d in S)g=S[d],d==`children`?h=g:d==`dangerouslySetInnerHTML`?f=g:d==`value`?v=g:d==`checked`?b=g:l&&typeof g!=`function`||x[d]===g||N(n,d,g,x[d],o);if(f)l||p&&(f.__html==p.__html||f.__html==n.innerHTML)||(n.innerHTML=f.__html),r.__k=[];else if(p&&(n.innerHTML=``),ee(r.type==`template`?n.content:n,_(h)?h:[h],r,i,a,C==`foreignObject`?`http://www.w3.org/1999/xhtml`:o,s,c,s?s[0]:i.__k&&w(i,0),l,u),s!=null)for(d=s.length;d--;)y(s[d]);l||(d=`value`,C==`progress`&&v==null?n.removeAttribute(`value`):v!=null&&(v!==n[d]||C==`progress`&&!v||C==`option`&&v!=x[d])&&N(n,d,v,x[d],o),d=`checked`,b!=null&&b!=n[d]&&N(n,d,b,x[d],o))}return n}function I(e,n,r){try{if(typeof e==`function`){var i=typeof e.__u==`function`;i&&e.__u(),i&&n==null||(e.__u=e(n))}else e.current=n}catch(e){t.__e(e,r)}}function L(e,n,r){var i,a;if(t.unmount&&t.unmount(e),(i=e.ref)&&(i.current&&i.current!=e.__e||I(i,null,n)),(i=e.__c)!=null){if(i.componentWillUnmount)try{i.componentWillUnmount()}catch(e){t.__e(e,n)}i.base=i.__P=null}if(i=e.__k)for(a=0;a<i.length;a++)i[a]&&L(i[a],n,r||typeof e.type!=`function`);r||y(e.__e),e.__c=e.__=e.__e=void 0}function ae(e,t,n){return this.constructor(e,n)}function oe(n,r,i){var a,o,s,c;r==document&&(r=document.documentElement),t.__&&t.__(n,r),o=(a=typeof i==`function`)?null:i&&i.__k||r.__k,s=[],c=[],P(r,n=(!a&&i||r).__k=b(S,null,[n]),o||m,m,r.namespaceURI,!a&&i?[i]:o?null:r.firstChild?e.call(r.childNodes):null,s,!a&&i?i:o?o.__e:r.firstChild,a,c),ne(s,n,c)}e=h.slice,t={__e:function(e,t,n,r){for(var i,a,o;t=t.__;)if((i=t.__c)&&!i.__)try{if((a=i.constructor)&&a.getDerivedStateFromError!=null&&(i.setState(a.getDerivedStateFromError(e)),o=i.__d),i.componentDidCatch!=null&&(i.componentDidCatch(e,r||{}),o=i.__d),o)return i.__E=i}catch(t){e=t}throw e}},n=0,C.prototype.setState=function(e,t){var n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=v({},this.state);typeof e==`function`&&(e=e(v({},n),this.props)),e&&v(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),D(this))},C.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),D(this))},C.prototype.render=S,r=[],a=typeof Promise==`function`?Promise.prototype.then.bind(Promise.resolve()):setTimeout,o=function(e,t){return e.__v.__b-t.__v.__b},O.__r=0,s=Math.random().toString(8),c=`__d`+s,l=`__a`+s,u=/(PointerCapture)$|Capture$/i,d=0,f=te(!1),p=te(!0);var R,z,B,V,H=0,U=[],W=t,G=W.__b,K=W.__r,q=W.diffed,se=W.__c,ce=W.unmount,le=W.__;function ue(e,t){W.__h&&W.__h(z,e,H||t),H=0;var n=z.__H||={__:[],__h:[]};return e>=n.__.length&&n.__.push({}),n.__[e]}function J(e){return H=1,de(ge,e)}function de(e,t,n){var r=ue(R++,2);if(r.t=e,!r.__c&&(r.__=[n?n(t):ge(void 0,t),function(e){var t=r.__N?r.__N[0]:r.__[0],n=r.t(t,e);t!==n&&(r.__N=[n,r.__[1]],r.__c.setState({}))}],r.__c=z,!z.__f)){var i=function(e,t,n){if(!r.__c.__H)return!0;var i=r.__c.__H.__.filter(function(e){return e.__c});if(i.every(function(e){return!e.__N}))return!a||a.call(this,e,t,n);var o=r.__c.props!==e;return i.some(function(e){if(e.__N){var t=e.__[0];e.__=e.__N,e.__N=void 0,t!==e.__[0]&&(o=!0)}}),a&&a.call(this,e,t,n)||o};z.__f=!0;var a=z.shouldComponentUpdate,o=z.componentWillUpdate;z.componentWillUpdate=function(e,t,n){if(this.__e){var r=a;a=void 0,i(e,t,n),a=r}o&&o.call(this,e,t,n)},z.shouldComponentUpdate=i}return r.__N||r.__}function Y(e,t){var n=ue(R++,7);return he(n.__H,t)&&(n.__=e(),n.__H=t,n.__h=e),n.__}function fe(){for(var e;e=U.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(X),t.__h.some(Z),t.__h=[]}catch(n){t.__h=[],W.__e(n,e.__v)}}}W.__b=function(e){z=null,G&&G(e)},W.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),le&&le(e,t)},W.__r=function(e){K&&K(e),R=0;var t=(z=e.__c).__H;t&&(B===z?(t.__h=[],z.__h=[],t.__.some(function(e){e.__N&&(e.__=e.__N),e.u=e.__N=void 0})):(t.__h.some(X),t.__h.some(Z),t.__h=[],R=0)),B=z},W.diffed=function(e){q&&q(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(U.push(t)!==1&&V===W.requestAnimationFrame||((V=W.requestAnimationFrame)||me)(fe)),t.__H.__.some(function(e){e.u&&(e.__H=e.u),e.u=void 0})),B=z=null},W.__c=function(e,t){t.some(function(e){try{e.__h.some(X),e.__h=e.__h.filter(function(e){return!e.__||Z(e)})}catch(n){t.some(function(e){e.__h&&=[]}),t=[],W.__e(n,e.__v)}}),se&&se(e,t)},W.unmount=function(e){ce&&ce(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.some(function(e){try{X(e)}catch(e){t=e}}),n.__H=void 0,t&&W.__e(t,n.__v))};var pe=typeof requestAnimationFrame==`function`;function me(e){var t,n=function(){clearTimeout(r),pe&&cancelAnimationFrame(t),setTimeout(e)},r=setTimeout(n,35);pe&&(t=requestAnimationFrame(n))}function X(e){var t=z,n=e.__c;typeof n==`function`&&(e.__c=void 0,n()),z=t}function Z(e){var t=z;e.__c=e.__(),z=t}function he(e,t){return!e||e.length!==t.length||t.some(function(t,n){return t!==e[n]})}function ge(e,t){return typeof t==`function`?t(e):t}var _e=0;Array.isArray;function Q(e,n,r,i,a,o){n||={};var s,c,l=n;if(`ref`in l)for(c in l={},n)c==`ref`?s=n[c]:l[c]=n[c];var u={type:e,props:l,key:r,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--_e,__i:-1,__u:0,__source:a,__self:o};if(typeof e==`function`&&(s=e.defaultProps))for(c in s)l[c]===void 0&&(l[c]=s[c]);return t.vnode&&t.vnode(u),u}function ve({value:e,onInput:t,placeholder:n,id:r}){return Q(`div`,{class:`search-bar`,children:[Q(`label`,{class:`search-label`,htmlFor:r,children:`Filter`}),Q(`input`,{id:r,class:`search-input`,type:`search`,value:e,placeholder:n||`Type to filter rows...`,autoComplete:`off`,spellCheck:!1,onInput:e=>t(e.currentTarget.value)})]})}function $({id:e,caption:t,columns:n,items:r,emptyMessage:i}){let[a,o]=J(``),[s,c]=J({key:null,direction:`asc`}),l=Y(()=>n.map(e=>({...e,searchValue:ye(e)})),[n]),u=Y(()=>{if(!a)return r;let e=a.toLowerCase();return r.filter(t=>l.some(n=>{let r=n.searchValue(t);return r&&r.toLowerCase().includes(e)}))},[r,a,l]),d=Y(()=>{if(!s.key)return u;let e=n.find(e=>e.key===s.key);if(!e)return u;let t=e.sortValue||ye(e),r=s.direction===`desc`?-1:1;return[...u].sort((e,n)=>{let i=t(e),a=t(n);return i===a?0:i==null?1:a==null?-1:i>a?r:-r})},[u,s,n]),f=(e,t)=>{t&&(s.key===e?s.direction===`asc`?c({key:e,direction:`desc`}):c({key:null,direction:`asc`}):c({key:e,direction:`asc`}))};return Q(`div`,{class:`report-table`,children:[Q(ve,{id:`${e}-search`,value:a,onInput:o,placeholder:`Filter ${t.toLowerCase()}...`}),Q(`p`,{class:`report-table__counts`,"aria-live":`polite`,children:[`Showing `,d.length,` of `,r.length]}),Q(`table`,{class:`data-table`,"aria-describedby":`${e}-search`,children:[Q(`caption`,{class:`visually-hidden`,children:t}),Q(`thead`,{children:Q(`tr`,{children:n.map(e=>{let t=s.key===e.key,n=t?s.direction:`none`;return Q(`th`,{scope:`col`,class:`${e.className||``} ${e.sortable?`is-sortable`:``}`.trim(),"aria-sort":t?s.direction===`asc`?`ascending`:`descending`:`none`,children:e.sortable?Q(`button`,{type:`button`,class:`sort-button`,onClick:()=>f(e.key,e.sortable),"data-sort":n,children:[e.label,Q(`span`,{class:`sort-indicator`,"aria-hidden":`true`,children:t?s.direction===`asc`?`▲`:`▼`:`↕`})]}):e.label},e.key)})})}),Q(`tbody`,{children:[d.length===0&&Q(`tr`,{children:Q(`td`,{class:`report-table__empty`,colSpan:n.length,children:i||`No rows to display.`})}),d.map((e,t)=>Q(`tr`,{children:n.map(t=>t.render(e))},e.id||e.example_id||e.file_name||t))]})]})]})}function ye(e){return e.searchValue?e.searchValue:t=>{let n=t[e.key];return n==null?``:String(n)}}var be={passed:`status-passed`,failed:`status-failed`,pending:`status-pending`,skipped:`status-skipped`,flaky:`status-flaky`,interrupted:`status-failed`};function xe(e){return be[e]||`status-unknown`}function Se(e){let t=(e.execution_result||{}).run_time;return typeof t==`number`?t<.001?`${(t*1e6).toFixed(0)} \u00B5s`:t<1?`${(t*1e3).toFixed(1)} ms`:`${t.toFixed(3)} s`:``}var Ce=[{key:`description`,label:`Description`,sortable:!0,render:e=>Q(`td`,{class:`cell-description`,children:e.description||``}),searchValue:e=>`${e.description||``} ${e.id||``}`},{key:`location`,label:`Location`,sortable:!0,render:e=>Q(`td`,{class:`cell-location`,children:Q(`code`,{children:e.location||``})}),searchValue:e=>e.location||``},{key:`status`,label:`Status`,sortable:!0,render:e=>Q(`td`,{class:`cell-status`,children:Q(`span`,{class:`badge ${xe(e.status)}`,children:e.status||`unknown`})}),searchValue:e=>e.status||``},{key:`run_reason`,label:`Run reason`,sortable:!0,render:e=>Q(`td`,{class:`cell-reason`,children:e.run_reason||``}),searchValue:e=>e.run_reason||``},{key:`duration`,label:`Duration`,sortable:!0,className:`is-numeric`,render:e=>Q(`td`,{class:`cell-duration is-numeric`,children:Se(e)}),sortValue:e=>{let t=e.execution_result||{};return typeof t.run_time==`number`?t.run_time:-1},searchValue:e=>Se(e)}];function we({items:e}){return Q($,{id:`all-examples`,caption:`All examples`,columns:Ce,items:e,emptyMessage:`No examples tracked in this run.`})}function Te(e){let t=[];return e.forEach(e=>{(e.entries||[]).forEach((n,r)=>{t.push({id:`${e.id}-${r}`,groupId:e.id,count:e.count,description:n.description||``,location:n.location||``})})}),t}var Ee=[{key:`groupId`,label:`Example ID`,sortable:!0,render:e=>Q(`td`,{class:`cell-id`,children:Q(`code`,{children:e.groupId})}),searchValue:e=>e.groupId||``},{key:`count`,label:`Occurrences`,sortable:!0,className:`is-numeric`,render:e=>Q(`td`,{class:`cell-count is-numeric`,children:e.count}),sortValue:e=>e.count},{key:`description`,label:`Description`,sortable:!0,render:e=>Q(`td`,{class:`cell-description`,children:e.description})},{key:`location`,label:`Location`,sortable:!0,render:e=>Q(`td`,{class:`cell-location`,children:Q(`code`,{children:e.location})})}];function De({items:e}){return Q($,{id:`duplicate-examples`,caption:`Duplicate examples`,columns:Ee,items:Te(e||[]),emptyMessage:`No duplicate examples detected.`})}var Oe=[{key:`id`,label:`Example ID`,sortable:!0,render:e=>Q(`td`,{class:`cell-id`,children:Q(`code`,{children:e.id})})},{key:`description`,label:`Description`,sortable:!0,render:e=>Q(`td`,{class:`cell-description`,children:e.description||``})},{key:`location`,label:`Location`,sortable:!0,render:e=>Q(`td`,{class:`cell-location`,children:Q(`code`,{children:e.location||``})})}];function ke({items:e}){return Q($,{id:`flaky-examples`,caption:`Flaky examples`,columns:Oe,items:e,emptyMessage:`No flaky examples detected.`})}var Ae=[{key:`example_id`,label:`Example ID`,sortable:!0,render:e=>Q(`td`,{class:`cell-id`,children:Q(`code`,{children:e.example_id})})},{key:`files_count`,label:`Files`,sortable:!0,className:`is-numeric`,render:e=>Q(`td`,{class:`cell-count is-numeric`,children:(e.files||[]).length}),sortValue:e=>(e.files||[]).length,searchValue:e=>String((e.files||[]).length)},{key:`env_keys_count`,label:`Env keys`,sortable:!0,className:`is-numeric`,render:e=>Q(`td`,{class:`cell-count is-numeric`,children:(e.env_keys||[]).length}),sortValue:e=>(e.env_keys||[]).length,searchValue:e=>(e.env_keys||[]).join(` `)},{key:`files`,label:`Dependencies`,sortable:!1,render:e=>Q(`td`,{class:`cell-deps`,children:[(e.files||[]).length>0&&Q(`details`,{class:`deps-details`,children:[Q(`summary`,{children:[(e.files||[]).length,` files`]}),Q(`ul`,{class:`deps-list`,children:(e.files||[]).map(e=>Q(`li`,{children:Q(`code`,{children:e})},e))})]}),(e.env_keys||[]).length>0&&Q(`details`,{class:`deps-details deps-env`,children:[Q(`summary`,{children:[(e.env_keys||[]).length,` env keys`]}),Q(`ul`,{class:`deps-list`,children:(e.env_keys||[]).map(e=>Q(`li`,{children:Q(`code`,{children:e})},e))})]})]}),searchValue:e=>[...e.files||[],...e.env_keys||[]].join(` `)}];function je({items:e}){return Q($,{id:`examples-dependency`,caption:`Examples dependency`,columns:Ae,items:e,emptyMessage:`No dependencies tracked.`})}var Me=[{key:`file_name`,label:`File`,sortable:!0,render:e=>Q(`td`,{class:`cell-file`,children:Q(`code`,{children:e.file_name})})},{key:`example_count`,label:`Examples`,sortable:!0,className:`is-numeric`,render:e=>Q(`td`,{class:`cell-count is-numeric`,children:e.example_count||0}),sortValue:e=>e.example_count||0},{key:`spec_file_count`,label:`Spec files`,sortable:!0,className:`is-numeric`,render:e=>Q(`td`,{class:`cell-count is-numeric`,children:Object.keys(e.spec_files||{}).length}),sortValue:e=>Object.keys(e.spec_files||{}).length,searchValue:e=>Object.keys(e.spec_files||{}).join(` `)},{key:`spec_files`,label:`Dependent spec files`,sortable:!1,render:e=>{let t=Object.entries(e.spec_files||{});return t.length===0?Q(`td`,{class:`cell-deps`}):Q(`td`,{class:`cell-deps`,children:Q(`details`,{class:`deps-details`,children:[Q(`summary`,{children:[t.length,` spec files`]}),Q(`ul`,{class:`deps-list`,children:t.map(([e,t])=>Q(`li`,{children:[Q(`code`,{children:e}),Q(`span`,{class:`dep-count`,children:[`×`,t]})]},e))})]})})},searchValue:e=>Object.keys(e.spec_files||{}).join(` `)}];function Ne({items:e}){return Q($,{id:`files-dependency`,caption:`Files dependency`,columns:Me,items:e,emptyMessage:`No file dependencies tracked.`})}function Pe(e){let t=e&&e.reports||{},n=[{id:`all_examples`,label:`All Examples`,count:(t.all_examples||[]).length,render:()=>Q(we,{items:t.all_examples||[]})}];return(t.duplicate_examples||[]).length>0&&n.push({id:`duplicate_examples`,label:`Duplicate Examples`,count:t.duplicate_examples.length,render:()=>Q(De,{items:t.duplicate_examples})}),(t.flaky_examples||[]).length>0&&n.push({id:`flaky_examples`,label:`Flaky Examples`,count:t.flaky_examples.length,render:()=>Q(ke,{items:t.flaky_examples})}),n.push({id:`examples_dependency`,label:`Examples Dependency`,count:(t.examples_dependency||[]).length,render:()=>Q(je,{items:t.examples_dependency||[]})}),n.push({id:`files_dependency`,label:`Files Dependency`,count:(t.files_dependency||[]).length,render:()=>Q(Ne,{items:t.files_dependency||[]})}),n}function Fe({payload:e}){let t=Y(()=>Pe(e),[e]),[n,r]=J(t[0]?t[0].id:null),i=e&&e.summary||{},a=e&&e.generated_at||``,o=e&&e.run_id||``,s=t.find(e=>e.id===n)||t[0];return Q(`div`,{class:`report-root`,children:[Q(`header`,{class:`report-header`,children:[Q(`h1`,{children:`RSpec Tracer Report`}),Q(`dl`,{class:`summary`,children:[Q(`div`,{class:`summary-item`,children:[Q(`dt`,{children:`Total`}),Q(`dd`,{children:i.total_examples??0})]}),Q(`div`,{class:`summary-item summary-passed`,children:[Q(`dt`,{children:`Passed`}),Q(`dd`,{children:i.passed_examples??0})]}),Q(`div`,{class:`summary-item summary-failed`,children:[Q(`dt`,{children:`Failed`}),Q(`dd`,{children:i.failed_examples??0})]}),Q(`div`,{class:`summary-item summary-pending`,children:[Q(`dt`,{children:`Pending`}),Q(`dd`,{children:i.pending_examples??0})]}),Q(`div`,{class:`summary-item summary-skipped`,children:[Q(`dt`,{children:`Skipped`}),Q(`dd`,{children:i.skipped_examples??0})]}),Q(`div`,{class:`summary-item`,children:[Q(`dt`,{children:`Flaky`}),Q(`dd`,{children:i.flaky_examples??0})]})]}),Q(`p`,{class:`report-meta`,children:[Q(`span`,{children:[`Run `,Q(`code`,{children:o})]}),Q(`span`,{children:[`Generated `,a]})]})]}),Q(`nav`,{class:`tab-bar`,role:`tablist`,"aria-label":`Report sections`,children:t.map(e=>Q(`button`,{type:`button`,role:`tab`,class:`tab-button${e.id===(s&&s.id)?` is-active`:``}`,"aria-selected":e.id===(s&&s.id),"aria-controls":`panel-${e.id}`,id:`tab-${e.id}`,onClick:()=>r(e.id),children:[e.label,Q(`span`,{class:`tab-count`,children:e.count})]},e.id))}),s&&Q(`section`,{class:`report-panel`,role:`tabpanel`,id:`panel-${s.id}`,"aria-labelledby":`tab-${s.id}`,children:s.render()})]})}function Ie(){let e=document.getElementById(`app`);if(!e)return;let t=document.getElementById(`report-data`);if(!t)return;let n;try{n=JSON.parse(t.textContent||`{}`)}catch(e){console.error(`rspec-tracer: failed to parse embedded report data`,e);return}oe(Q(Fe,{payload:n}),e),e.setAttribute(`data-hydrate`,`ready`);let r=document.getElementById(`fallback`);r&&r.remove()}document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,Ie):Ie();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="generator" content="rspec-tracer" />
|
|
7
|
+
<title>RSpec Tracer Report</title>
|
|
8
|
+
<script type="module" crossorigin src="./assets/index.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="./assets/index.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<noscript>
|
|
13
|
+
<div class="noscript-banner">
|
|
14
|
+
JavaScript is disabled. Interactive search and sort are unavailable; the static tables below
|
|
15
|
+
contain the full run data.
|
|
16
|
+
</div>
|
|
17
|
+
</noscript>
|
|
18
|
+
<div id="app" data-hydrate="pending"></div>
|
|
19
|
+
<!-- RSPEC_TRACER_FALLBACK -->
|
|
20
|
+
<script id="report-data" type="application/json">
|
|
21
|
+
{}
|
|
22
|
+
</script>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|