glib-web 6.10.3 → 7.0.0.beta1
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/app/controllers/concerns/glib/auth/policy.rb +1 -5
- data/app/helpers/glib/json_ui/action_builder/bottom_banners.rb +0 -1
- data/app/helpers/glib/json_ui/action_builder/components.rb +1 -16
- data/app/helpers/glib/json_ui/action_builder/logics.rb +1 -2
- data/app/helpers/glib/json_ui/action_builder/snackbars.rb +0 -16
- data/app/helpers/glib/json_ui/response_helper.rb +0 -12
- data/app/helpers/glib/json_ui/view_builder/fields.rb +4 -133
- data/app/views/json_ui/garage/actions/_components.json.jbuilder +2 -2
- data/app/views/json_ui/garage/forms/bulk_edit_post.json.jbuilder +1 -1
- data/app/views/json_ui/garage/forms/checkboxes.json.jbuilder +3 -3
- data/app/views/json_ui/garage/forms/selects.json.jbuilder +5 -5
- data/app/views/json_ui/garage/forms/update_options.json.jbuilder +1 -1
- data/app/views/json_ui/garage/panels/flow.json.jbuilder +6 -6
- data/app/views/json_ui/garage/panels/horizontal.json.jbuilder +10 -10
- data/app/views/json_ui/garage/panels/responsive.json.jbuilder +10 -10
- data/app/views/json_ui/garage/panels/ul.json.jbuilder +2 -2
- data/app/views/json_ui/garage/panels/vertical.json.jbuilder +10 -10
- data/app/views/json_ui/garage/tables/bulk_edit.json.jbuilder +8 -8
- data/app/views/json_ui/garage/test_page/_header.json.jbuilder +0 -1
- data/app/views/json_ui/garage/test_page/calendar.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/carousel.json.jbuilder +4 -4
- data/app/views/json_ui/garage/test_page/charts.json.jbuilder +4 -4
- data/app/views/json_ui/garage/test_page/column.json.jbuilder +3 -3
- data/app/views/json_ui/garage/test_page/components.json.jbuilder +7 -7
- data/app/views/json_ui/garage/test_page/custom.json.jbuilder +3 -3
- data/app/views/json_ui/garage/test_page/dialog.json.jbuilder +0 -21
- data/app/views/json_ui/garage/test_page/fields_creditCard.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/fields_date_time.json.jbuilder +8 -8
- data/app/views/json_ui/garage/test_page/fields_dynamicSelect.json.jbuilder +8 -8
- data/app/views/json_ui/garage/test_page/fields_location.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/fields_otp.json.jbuilder +8 -8
- data/app/views/json_ui/garage/test_page/fields_phone.json.jbuilder +9 -9
- data/app/views/json_ui/garage/test_page/fields_radio.json.jbuilder +4 -4
- data/app/views/json_ui/garage/test_page/fields_rating.json.jbuilder +10 -10
- data/app/views/json_ui/garage/test_page/fields_richText.json.jbuilder +10 -10
- data/app/views/json_ui/garage/test_page/fields_select.json.jbuilder +8 -8
- data/app/views/json_ui/garage/test_page/fields_sign.json.jbuilder +8 -56
- data/app/views/json_ui/garage/test_page/fields_stripeExternalAccount.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/fields_stripeToken.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/fields_text.json.jbuilder +5 -5
- data/app/views/json_ui/garage/test_page/fields_timer.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/flow.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/form.json.jbuilder +9 -9
- data/app/views/json_ui/garage/test_page/grid.json.jbuilder +4 -4
- data/app/views/json_ui/garage/test_page/horizontal.json.jbuilder +5 -5
- data/app/views/json_ui/garage/test_page/image.json.jbuilder +9 -9
- data/app/views/json_ui/garage/test_page/lifecycle.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/list.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/multimedia_docx.json.jbuilder +5 -5
- data/app/views/json_ui/garage/test_page/multimedia_video.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/pagination.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/panels_bulkEdit2.json.jbuilder +13 -13
- data/app/views/json_ui/garage/test_page/panels_bulkEdit3.json.jbuilder +1 -1
- data/app/views/json_ui/garage/test_page/progressCircle.json.jbuilder +8 -8
- data/app/views/json_ui/garage/test_page/responsive.json.jbuilder +4 -4
- data/app/views/json_ui/garage/test_page/scroll.json.jbuilder +5 -5
- data/app/views/json_ui/garage/test_page/split.json.jbuilder +1 -1
- data/app/views/json_ui/garage/test_page/table.json.jbuilder +5 -5
- data/app/views/json_ui/garage/test_page/timeline.json.jbuilder +5 -5
- data/app/views/json_ui/garage/test_page/ul.json.jbuilder +4 -4
- data/app/views/json_ui/garage/test_page/vertical.json.jbuilder +5 -5
- data/app/views/json_ui/garage/test_page/web.json.jbuilder +5 -5
- data/app/views/json_ui/garage/views/_expand_shrink_ex.json.jbuilder +4 -4
- data/app/views/json_ui/garage/views/controls.json.jbuilder +4 -4
- data/app/views/json_ui/garage/views/{components_set.json.jbuilder → logics_set.json.jbuilder} +2 -2
- data/lib/glib/engine.rb +0 -32
- data/lib/glib/json_crawler/router.rb +0 -24
- data/lib/glib/json_ui/config.rb +1 -1
- data/lib/glib/test/changed_files_rubocop.rb +55 -0
- data/lib/glib/test_helpers.rb +6 -9
- data/lib/glib-web.rb +2 -4
- metadata +5 -37
- data/app/controllers/concerns/glib/auth/token_authenticatable.rb +0 -37
- data/app/controllers/glib/api_controller.rb +0 -52
- data/app/jobs/glib/purge_unattached_job.rb +0 -32
- data/app/models/concerns/glib/snapshot_v2.rb +0 -485
- data/app/models/glib/model.rb +0 -40
- data/app/models/glib/plain_model.rb +0 -23
- data/app/models/snapshot_blob_reference.rb +0 -20
- data/app/views/json_ui/garage/test_page/fields_change_trigger.json.jbuilder +0 -128
- data/lib/generators/glib/snapshot_v2_generator.rb +0 -54
- data/lib/generators/templates/snapshot_v2/add_snapshot_v2_columns.rb +0 -18
- data/lib/generators/templates/snapshot_v2/create_snapshot_blob_references.rb +0 -10
- data/lib/glib/purge_unattached/config.rb +0 -38
- data/lib/glib/test/full_suite_rubocop.rb +0 -87
- data/lib/glib/test/rubocop_guards.rb +0 -42
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# Top-level (NOT namespaced under Glib::) by design — see issue #960's hard constraint:
|
|
2
|
-
#
|
|
3
|
-
# The gem's SnapshotBlobReference must stay a top-level model on table
|
|
4
|
-
# `snapshot_blob_references` — not namespaced. Drop-in compatibility holds only
|
|
5
|
-
# because `isolate_namespace` is off in lib/glib/engine.rb. Namespacing it (e.g.
|
|
6
|
-
# Glib::SnapshotBlobReference → table `glib_snapshot_blob_references`) or enabling
|
|
7
|
-
# isolate_namespace orphans every existing consumer's table + rows and forces a
|
|
8
|
-
# rename/data migration.
|
|
9
|
-
#
|
|
10
|
-
# This file lives at `app/models/snapshot_blob_reference.rb` (not under `app/models/glib/`)
|
|
11
|
-
# so the Rails autoloader resolves the constant as top-level `SnapshotBlobReference`, which
|
|
12
|
-
# in turn maps to the un-prefixed table name `snapshot_blob_references`. The host app's
|
|
13
|
-
# existing `SnapshotBlobReference` model can be deleted verbatim when it bumps the gem.
|
|
14
|
-
#
|
|
15
|
-
# Inherits from the host's `ApplicationRecord` (the standard since Rails 5.0) so the model
|
|
16
|
-
# shares the primary DB connection — same pattern as every other top-level host model.
|
|
17
|
-
class SnapshotBlobReference < ApplicationRecord
|
|
18
|
-
belongs_to :snapshot, class_name: 'ActiveSnapshot::Snapshot'
|
|
19
|
-
belongs_to :blob, class_name: 'ActiveStorage::Blob'
|
|
20
|
-
end
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
json.title 'Test Page (Fields Change Trigger)'
|
|
2
|
-
|
|
3
|
-
page = json_ui_page json
|
|
4
|
-
|
|
5
|
-
render 'json_ui/garage/test_page/header', json: json, page: page
|
|
6
|
-
|
|
7
|
-
page.body(
|
|
8
|
-
childViews: ->(body) do
|
|
9
|
-
|
|
10
|
-
body.panels_responsive(
|
|
11
|
-
padding: glib_json_padding_body,
|
|
12
|
-
childViews: ->(res) do
|
|
13
|
-
res.h2 text: 'Fields Change Trigger'
|
|
14
|
-
res.spacer height: 8
|
|
15
|
-
res.label text: 'changeOn / changeDelay control WHEN a typed-input field reports its change to onChange.'
|
|
16
|
-
res.spacer height: 12
|
|
17
|
-
|
|
18
|
-
res.panels_form(
|
|
19
|
-
url: json_ui_garage_url(path: 'forms/generic_post'),
|
|
20
|
-
method: 'post',
|
|
21
|
-
childViews: ->(form) do
|
|
22
|
-
|
|
23
|
-
# ── Default: changeOn 'input', 300ms ──────────────────────────────
|
|
24
|
-
form.h4 text: 'Default (changeOn: input)'
|
|
25
|
-
form.spacer height: 8
|
|
26
|
-
form.label text: 'No options set: reports once the user pauses for the default 300ms.'
|
|
27
|
-
form.spacer height: 8
|
|
28
|
-
|
|
29
|
-
form.fields_text(
|
|
30
|
-
name: 'user[default_value]',
|
|
31
|
-
label: 'Default',
|
|
32
|
-
placeholder: 'default',
|
|
33
|
-
width: 'matchParent',
|
|
34
|
-
onChange: ->(action) do
|
|
35
|
-
action.logics_set(
|
|
36
|
-
targetId: 'status_default',
|
|
37
|
-
conditionalData: { text: { 'printf': ['Default: {0}', { 'var': 'user[default_value]' }] } }
|
|
38
|
-
)
|
|
39
|
-
end
|
|
40
|
-
)
|
|
41
|
-
form.spacer height: 8
|
|
42
|
-
form.label id: 'status_default', text: 'Default: idle'
|
|
43
|
-
|
|
44
|
-
form.spacer height: 12
|
|
45
|
-
form.hr width: 'matchParent'
|
|
46
|
-
form.spacer height: 12
|
|
47
|
-
|
|
48
|
-
# ── changeDelay ───────────────────────────────────────────────────
|
|
49
|
-
form.h4 text: 'changeDelay: 1500'
|
|
50
|
-
form.spacer height: 8
|
|
51
|
-
form.label text: 'Same input mode, longer pause. Leaving the field still reports right away, rather than waiting out the remaining delay.'
|
|
52
|
-
form.spacer height: 8
|
|
53
|
-
|
|
54
|
-
form.fields_text(
|
|
55
|
-
name: 'user[slow]',
|
|
56
|
-
label: 'Slow',
|
|
57
|
-
placeholder: 'slow',
|
|
58
|
-
width: 'matchParent',
|
|
59
|
-
changeDelay: 1500,
|
|
60
|
-
onChange: ->(action) do
|
|
61
|
-
action.logics_set(
|
|
62
|
-
targetId: 'status_slow',
|
|
63
|
-
conditionalData: { text: { 'printf': ['Slow: {0}', { 'var': 'user[slow]' }] } }
|
|
64
|
-
)
|
|
65
|
-
end
|
|
66
|
-
)
|
|
67
|
-
form.spacer height: 8
|
|
68
|
-
form.label id: 'status_slow', text: 'Slow: idle'
|
|
69
|
-
|
|
70
|
-
form.spacer height: 12
|
|
71
|
-
form.hr width: 'matchParent'
|
|
72
|
-
form.spacer height: 12
|
|
73
|
-
|
|
74
|
-
# ── changeOn: 'blur' ──────────────────────────────────────────────
|
|
75
|
-
form.h4 text: "changeOn: 'blur'"
|
|
76
|
-
form.spacer height: 8
|
|
77
|
-
form.label text: 'Stays idle no matter how long the user pauses; reports only once focus leaves.'
|
|
78
|
-
form.spacer height: 8
|
|
79
|
-
|
|
80
|
-
form.fields_text(
|
|
81
|
-
name: 'user[on_blur]',
|
|
82
|
-
label: 'Reports on blur',
|
|
83
|
-
placeholder: 'on blur',
|
|
84
|
-
width: 'matchParent',
|
|
85
|
-
changeOn: 'blur',
|
|
86
|
-
onChange: ->(action) do
|
|
87
|
-
action.logics_set(
|
|
88
|
-
targetId: 'status_on_blur',
|
|
89
|
-
conditionalData: { text: { 'printf': ['Blur: {0}', { 'var': 'user[on_blur]' }] } }
|
|
90
|
-
)
|
|
91
|
-
end
|
|
92
|
-
)
|
|
93
|
-
form.spacer height: 8
|
|
94
|
-
form.label id: 'status_on_blur', text: 'Blur: idle'
|
|
95
|
-
|
|
96
|
-
form.spacer height: 12
|
|
97
|
-
form.hr width: 'matchParent'
|
|
98
|
-
form.spacer height: 12
|
|
99
|
-
|
|
100
|
-
# ── Textarea on blur ──────────────────────────────────────────────
|
|
101
|
-
form.h4 text: "fields_textarea with changeOn: 'blur'"
|
|
102
|
-
form.spacer height: 8
|
|
103
|
-
|
|
104
|
-
form.fields_textarea(
|
|
105
|
-
name: 'user[notes]',
|
|
106
|
-
label: 'Notes',
|
|
107
|
-
placeholder: 'notes',
|
|
108
|
-
width: 'matchParent',
|
|
109
|
-
rows: 3,
|
|
110
|
-
changeOn: 'blur',
|
|
111
|
-
onChange: ->(action) do
|
|
112
|
-
action.logics_set(
|
|
113
|
-
targetId: 'status_notes',
|
|
114
|
-
conditionalData: { text: { 'printf': ['Notes: {0}', { 'var': 'user[notes]' }] } }
|
|
115
|
-
)
|
|
116
|
-
end
|
|
117
|
-
)
|
|
118
|
-
form.spacer height: 8
|
|
119
|
-
form.label id: 'status_notes', text: 'Notes: idle'
|
|
120
|
-
|
|
121
|
-
form.spacer height: 16
|
|
122
|
-
form.fields_submit text: 'Submit'
|
|
123
|
-
end
|
|
124
|
-
)
|
|
125
|
-
end
|
|
126
|
-
)
|
|
127
|
-
end
|
|
128
|
-
)
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
module Glib
|
|
2
|
-
module Generators
|
|
3
|
-
# Installs the Glib::SnapshotV2 database prerequisites. Two opt-in pieces, each produced
|
|
4
|
-
# only when the corresponding flag is passed:
|
|
5
|
-
#
|
|
6
|
-
# `rails g glib:snapshot_v2` # adds V2 columns only (required for V2)
|
|
7
|
-
# `rails g glib:snapshot_v2 --with-blob-references` # also creates the blob-reference table
|
|
8
|
-
#
|
|
9
|
-
# Deliberately separate from `glib:install`: that generator overwrites `config/database.yml`
|
|
10
|
-
# and writes dynamic-text migrations to the non-standard `db/dynamic_text_migrate/` path,
|
|
11
|
-
# neither of which a snapshot adopter wants re-run on their app. Snapshot tables live in the
|
|
12
|
-
# primary DB and use the standard `db/migrate/` path.
|
|
13
|
-
#
|
|
14
|
-
# The V2 columns migration adds `action` / `version` / `diff` (jsonb) / `source` plus the
|
|
15
|
-
# two composite indexes Glib::SnapshotV2's queries rely on. It does NOT backfill — adopters
|
|
16
|
-
# who need to migrate legacy V1 snapshots (stored under metadata) own that one-way data
|
|
17
|
-
# conversion themselves. See issue #960.
|
|
18
|
-
class SnapshotV2Generator < Rails::Generators::Base
|
|
19
|
-
include Rails::Generators::Migration
|
|
20
|
-
|
|
21
|
-
source_root File.expand_path('../templates/snapshot_v2', __dir__)
|
|
22
|
-
|
|
23
|
-
class_option :with_blob_references, type: :boolean, default: false,
|
|
24
|
-
desc: 'Also create the snapshot_blob_references table (required to version attached files)'
|
|
25
|
-
|
|
26
|
-
def copy_v2_columns_migration
|
|
27
|
-
migration_template 'add_snapshot_v2_columns.rb',
|
|
28
|
-
'db/migrate/add_snapshot_v2_columns.rb'
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def copy_blob_references_migration
|
|
32
|
-
return unless options[:with_blob_references]
|
|
33
|
-
|
|
34
|
-
migration_template 'create_snapshot_blob_references.rb',
|
|
35
|
-
'db/migrate/create_snapshot_blob_references.rb'
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
private
|
|
39
|
-
# Rails::Generators::Migration requires this class method. Returns the UTC timestamp for
|
|
40
|
-
# the next migration filename. The mutex + @last_number pair guarantees the second template
|
|
41
|
-
# (create_snapshot_blob_references) lands strictly AFTER the first (add_snapshot_v2_columns)
|
|
42
|
-
# within a single invocation — otherwise both share the same timestamp and load order is
|
|
43
|
-
# non-deterministic.
|
|
44
|
-
@@migration_mutex = Mutex.new
|
|
45
|
-
|
|
46
|
-
def self.next_migration_number(_dirname)
|
|
47
|
-
@@migration_mutex.synchronize do
|
|
48
|
-
@last_snapshot_v2_number ||= (Time.now.utc - 1.second).strftime('%Y%m%d%H%M%S')
|
|
49
|
-
@last_snapshot_v2_number = (@last_snapshot_v2_number.to_i + 1).to_s
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
class AddSnapshotV2Columns < ActiveRecord::Migration[8.0]
|
|
2
|
-
def change
|
|
3
|
-
# PostgreSQL has jsonb (binary, indexable); SQLite (used by glib-web's dummy-app) and MySQL
|
|
4
|
-
# only have json. Pick the richest type the adapter supports so consumers on PG get indexable
|
|
5
|
-
# diffs while greenfield adopters on SQLite/MySQL still work without edits.
|
|
6
|
-
diff_type = connection.adapter_name.match?(/PostgreSQL/i) ? :jsonb : :json
|
|
7
|
-
|
|
8
|
-
change_table :snapshots, bulk: true do |t|
|
|
9
|
-
t.string :action
|
|
10
|
-
t.integer :version
|
|
11
|
-
t.column :diff, diff_type
|
|
12
|
-
t.string :source
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
add_index :snapshots, [:action, :item_type, :item_id]
|
|
16
|
-
add_index :snapshots, [:item_type, :item_id, :version]
|
|
17
|
-
end
|
|
18
|
-
end
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
class CreateSnapshotBlobReferences < ActiveRecord::Migration[8.0]
|
|
2
|
-
def change
|
|
3
|
-
create_table :snapshot_blob_references do |t|
|
|
4
|
-
t.belongs_to :snapshot, null: false, foreign_key: { to_table: :snapshots }
|
|
5
|
-
t.belongs_to :blob, null: false, foreign_key: { to_table: :active_storage_blobs }
|
|
6
|
-
t.timestamps
|
|
7
|
-
end
|
|
8
|
-
add_index :snapshot_blob_references, [:snapshot_id, :blob_id], unique: true
|
|
9
|
-
end
|
|
10
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
require 'active_support/core_ext/numeric/time'
|
|
2
|
-
|
|
3
|
-
module Glib
|
|
4
|
-
module PurgeUnattached
|
|
5
|
-
# Global settings for Glib::PurgeUnattachedJob.
|
|
6
|
-
#
|
|
7
|
-
# `min_age` is how long an unattached ActiveStorage blob must exist before
|
|
8
|
-
# the purge may remove it. The 8-hour default leaves room for multi-step
|
|
9
|
-
# upload flows (direct upload -> form submit -> attach) to finish before
|
|
10
|
-
# their blob becomes eligible; apps with slower flows raise it:
|
|
11
|
-
#
|
|
12
|
-
# # config/initializers/glib.rb
|
|
13
|
-
# Glib::PurgeUnattached::Config.min_age = 10.hours
|
|
14
|
-
#
|
|
15
|
-
# Deliberately NOT a setting on the job class: the job is autoloaded from
|
|
16
|
-
# app/, so an initializer-assigned value would be wiped the first time the
|
|
17
|
-
# class is reloaded. This file lives in lib/ and is required eagerly by
|
|
18
|
-
# glib-web.rb, which is what makes the setting survive reloads.
|
|
19
|
-
class Config
|
|
20
|
-
# The shipped default. Apps opt into a different window via the setter.
|
|
21
|
-
DEFAULT_MIN_AGE = 8.hours
|
|
22
|
-
|
|
23
|
-
@@min_age = DEFAULT_MIN_AGE
|
|
24
|
-
|
|
25
|
-
def self.min_age
|
|
26
|
-
@@min_age
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def self.min_age=(value)
|
|
30
|
-
unless value.is_a?(ActiveSupport::Duration) && value.positive?
|
|
31
|
-
raise ArgumentError, "Invalid min_age: #{value.inspect}. Expected a positive ActiveSupport::Duration (e.g. 10.hours)."
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
@@min_age = value
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'English'
|
|
4
|
-
|
|
5
|
-
module Glib
|
|
6
|
-
module Test
|
|
7
|
-
# Suite-wide RuboCop auto-remediation, lifted from the per-project
|
|
8
|
-
# `test/linters/rubocop_test.rb` convention (originated in neowork-str) so
|
|
9
|
-
# projects get it from the gem via a CONSCIOUS opt-in: include
|
|
10
|
-
# Glib::Test::RubocopGuards once in the test setup. Never auto-require
|
|
11
|
-
# this from Glib::TestHelpers — that would enable a file-rewriting test
|
|
12
|
-
# in every project without a decision. It lints the WHOLE repo, so files
|
|
13
|
-
# nobody touches cannot silently accumulate offenses.
|
|
14
|
-
#
|
|
15
|
-
# Runs `rubocop -a` (safe autocorrect), which REWRITES files on disk to
|
|
16
|
-
# fix offenses, then asserts nothing remains AND nothing was rewritten.
|
|
17
|
-
# The mutation is deliberate (the repo's auto-remediation convention —
|
|
18
|
-
# i18n-tasks normalize runs the same way): a run that dirties a file is
|
|
19
|
-
# reported as a FAILURE so the rewrite cannot go unnoticed — that is the
|
|
20
|
-
# drift signal. Do NOT strip the `-a`; review the diff, keep the corrected
|
|
21
|
-
# output, and re-run. If a long-passing test suddenly breaks, investigate
|
|
22
|
-
# WHY first: usually a non-canonical file was committed, or a rubocop
|
|
23
|
-
# version bump changed a cop's rules.
|
|
24
|
-
#
|
|
25
|
-
# It self-registers with Minitest, so it runs on EVERY `rails test`
|
|
26
|
-
# invocation, single-file runs included. That is cheap on purpose: RuboCop
|
|
27
|
-
# caches per-file results, so a full-repo run on an unchanged tree costs
|
|
28
|
-
# about a second on a mid-size app.
|
|
29
|
-
#
|
|
30
|
-
# Deliberately does NOT skip under ENV['CI']: the local rails-test
|
|
31
|
-
# wrapper sets CI=true, and skipping here would silence this layer
|
|
32
|
-
# exactly where it runs. Opt out per-run with SKIP_FULL_SUITE_RUBOCOP=1.
|
|
33
|
-
class FullSuiteRubocop < ::ActiveSupport::TestCase
|
|
34
|
-
# `--format simple` offense lines: "C: 12: 3: [Correctable] Cop/Name: message"
|
|
35
|
-
# (line/column right-aligned to width 3, so the padding is 0-2 spaces).
|
|
36
|
-
OFFENSE_LINE = /^[A-Z]: *\d+: *\d+: /
|
|
37
|
-
|
|
38
|
-
def test_rubocop_autocorrect_then_clean
|
|
39
|
-
skip 'opted out via SKIP_FULL_SUITE_RUBOCOP' if ENV['SKIP_FULL_SUITE_RUBOCOP'] == '1'
|
|
40
|
-
|
|
41
|
-
# `scrub`: stderr is merged in, and one invalid byte would turn the
|
|
42
|
-
# scans below into an ArgumentError instead of a lint failure.
|
|
43
|
-
output = `bundle exec rubocop -a --format simple 2>&1`.scrub
|
|
44
|
-
clean = $CHILD_STATUS.success?
|
|
45
|
-
|
|
46
|
-
# Three distinct outcomes hide in one output, and each needs a
|
|
47
|
-
# different action — the message must say which, or an agent told
|
|
48
|
-
# only "offenses detected" reaches for a `rubocop:disable`:
|
|
49
|
-
# [Corrected] -a already rewrote it on disk (exit 0 if that's all)
|
|
50
|
-
# [Correctable] survived -a because only the UNSAFE -A can fix it
|
|
51
|
-
# neither no autocorrect exists — semantic, read the cop
|
|
52
|
-
# Counted on the offense LINES only, so a cop message that happens to
|
|
53
|
-
# contain a marker cannot shift a bucket.
|
|
54
|
-
offenses = output.lines.grep(OFFENSE_LINE)
|
|
55
|
-
corrected = offenses.count { |line| line.include?('[Corrected]') }
|
|
56
|
-
unsafe = offenses.count { |line| line.include?('[Correctable]') }
|
|
57
|
-
semantic = offenses.size - corrected - unsafe
|
|
58
|
-
|
|
59
|
-
# A Proc message is built only on failure (Minitest calls it lazily).
|
|
60
|
-
assert(clean && corrected.zero?, -> { failure_message(output, corrected: corrected, unsafe: unsafe, semantic: semantic) })
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
def failure_message(output, corrected:, unsafe:, semantic:)
|
|
65
|
-
parts = []
|
|
66
|
-
if corrected.positive?
|
|
67
|
-
parts << "`rubocop -a` REWROTE #{corrected} offense(s) on disk (the [Corrected] lines below). That is the " \
|
|
68
|
-
'drift signal, not a problem with this test: review `git diff`, keep the corrected output, ' \
|
|
69
|
-
'and re-run — do not strip the -a.'
|
|
70
|
-
end
|
|
71
|
-
if unsafe.positive?
|
|
72
|
-
parts << "#{unsafe} offense(s) marked [Correctable] survived `-a` because their fix is UNSAFE: apply it " \
|
|
73
|
-
'with `rubocop -A <file>` only after checking what it changes, or fix by hand.'
|
|
74
|
-
end
|
|
75
|
-
if semantic.positive?
|
|
76
|
-
parts << "#{semantic} offense(s) have NO autocorrect, so they are semantic, not mechanical: fix each the " \
|
|
77
|
-
"way the cop's own message prescribes — DevDoc cops name their prescribed alternative in the " \
|
|
78
|
-
"offense itself (full docs: the dev-doc repo's docs/cops/). A `rubocop:disable` is only for " \
|
|
79
|
-
"the case a cop's own doc calls genuinely unavoidable; disables already present in a file are " \
|
|
80
|
-
'narrow exceptions, not precedent.'
|
|
81
|
-
end
|
|
82
|
-
parts << 'RuboCop did not finish cleanly (see output below).' if parts.empty?
|
|
83
|
-
"#{parts.join("\n\n")}\n\n#{output}"
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Glib
|
|
4
|
-
module Test
|
|
5
|
-
# Conscious opt-in for the suite-time RuboCop guard. Including this module
|
|
6
|
-
# ONCE in the project's test setup IS the project's explicit decision to
|
|
7
|
-
# run it. It must stay a separate include precisely because
|
|
8
|
-
# Glib::TestHelpers is mixed in for unrelated reasons (crawlers, travel
|
|
9
|
-
# helpers): auto-requiring the guard from there would enable it without
|
|
10
|
-
# any conscious decision, and FullSuiteRubocop runs `rubocop -a`, which
|
|
11
|
-
# REWRITES files on disk.
|
|
12
|
-
#
|
|
13
|
-
# class ActiveSupport::TestCase
|
|
14
|
-
# include Glib::Test::RubocopGuards # opts in: full-suite `rubocop -a`
|
|
15
|
-
# end
|
|
16
|
-
#
|
|
17
|
-
# This module is defined as soon as the gem loads (glib/test_helpers
|
|
18
|
-
# requires it at file level), so the include works on its own — in
|
|
19
|
-
# ActiveSupport::TestCase or in a single test class — with no ordering
|
|
20
|
-
# dependency on Glib::TestHelpers.
|
|
21
|
-
#
|
|
22
|
-
# What the include switches on: Glib::Test::FullSuiteRubocop — `rubocop -a`
|
|
23
|
-
# over the whole repo on every `rails test` invocation, failing when
|
|
24
|
-
# anything was rewritten or remains (runs under CI too).
|
|
25
|
-
#
|
|
26
|
-
# MIGRATION NOTE: in the published releases before this change (6.9.3 and
|
|
27
|
-
# earlier as released), Glib::TestHelpers auto-required a report-only
|
|
28
|
-
# ChangedFilesRubocop guard (working-tree edits only, skipped under CI),
|
|
29
|
-
# so every project had it without asking. That guard is retired:
|
|
30
|
-
# FullSuiteRubocop is a superset, and the local rails-test wrapper sets
|
|
31
|
-
# CI=true, where the old guard never ran anyway. A project bumping to the
|
|
32
|
-
# first release that contains this change must add the include line above
|
|
33
|
-
# or it silently has no suite-time RuboCop guard at all.
|
|
34
|
-
module RubocopGuards
|
|
35
|
-
extend ActiveSupport::Concern
|
|
36
|
-
|
|
37
|
-
included do
|
|
38
|
-
require 'glib/test/full_suite_rubocop'
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|