comfortable_media_surfer 3.1.6 → 3.2.0

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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +2 -0
  3. data/.github/workflows/claude.yml +99 -1
  4. data/.github/workflows/coveralls.yml +4 -3
  5. data/.github/workflows/rubyonrails.yml +34 -5
  6. data/CHANGELOG.md +27 -5
  7. data/CONTRIBUTING.md +5 -1
  8. data/Gemfile +6 -5
  9. data/README.md +4 -4
  10. data/Rakefile +201 -0
  11. data/app/assets/javascripts/comfy/admin/cms/application.js +3 -0
  12. data/app/assets/javascripts/comfy/admin/cms/base.js +1 -0
  13. data/app/assets/javascripts/comfy/admin/cms/file_link.js +34 -11
  14. data/app/assets/javascripts/comfy/admin/cms/file_upload.js +37 -28
  15. data/app/assets/javascripts/comfy/admin/cms/files_modal.js +76 -26
  16. data/app/controllers/comfy/admin/cms/categories_controller.rb +1 -1
  17. data/app/controllers/comfy/admin/cms/files_controller.rb +1 -1
  18. data/app/controllers/comfy/admin/cms/layouts_controller.rb +1 -1
  19. data/app/controllers/comfy/admin/cms/pages_controller.rb +33 -4
  20. data/app/controllers/comfy/admin/cms/revisions/base_controller.rb +2 -2
  21. data/app/controllers/comfy/admin/cms/revisions/page_controller.rb +5 -5
  22. data/app/controllers/comfy/admin/cms/revisions/translation_controller.rb +5 -5
  23. data/app/controllers/comfy/admin/cms/sites_controller.rb +3 -3
  24. data/app/controllers/comfy/admin/cms/snippets_controller.rb +1 -1
  25. data/app/controllers/comfy/admin/cms/translations_controller.rb +15 -3
  26. data/app/controllers/comfy/cms/base_controller.rb +16 -7
  27. data/app/controllers/comfy/cms/content_controller.rb +4 -4
  28. data/app/controllers/concerns/comfy/reorder_action.rb +3 -4
  29. data/app/models/comfy/cms/categorization.rb +9 -0
  30. data/app/models/comfy/cms/category.rb +1 -1
  31. data/app/models/comfy/cms/fragment.rb +7 -0
  32. data/app/models/comfy/cms/layout.rb +7 -2
  33. data/app/models/comfy/cms/page.rb +13 -4
  34. data/app/models/comfy/cms/site.rb +3 -3
  35. data/app/models/comfy/cms/translation.rb +5 -0
  36. data/app/models/concerns/comfy/cms/with_categories.rb +1 -1
  37. data/app/models/concerns/comfy/cms/with_fragments.rb +1 -1
  38. data/app/views/comfy/admin/cms/pages/edit.html.haml +5 -1
  39. data/config/database.yml +1 -1
  40. data/config/initializers/json.rb +2 -0
  41. data/config/locales/ar.yml +6 -0
  42. data/config/locales/ca.yml +6 -0
  43. data/config/locales/cs.yml +6 -0
  44. data/config/locales/da.yml +6 -0
  45. data/config/locales/de.yml +6 -0
  46. data/config/locales/en.yml +6 -0
  47. data/config/locales/es.yml +6 -0
  48. data/config/locales/fi.yml +6 -0
  49. data/config/locales/fr.yml +6 -0
  50. data/config/locales/gr.yml +6 -0
  51. data/config/locales/hr.yml +6 -0
  52. data/config/locales/it.yml +6 -0
  53. data/config/locales/ja.yml +6 -0
  54. data/config/locales/nb.yml +6 -0
  55. data/config/locales/nl.yml +6 -0
  56. data/config/locales/pl.yml +6 -0
  57. data/config/locales/pt-BR.yml +6 -0
  58. data/config/locales/ru.yml +6 -0
  59. data/config/locales/sk.yml +6 -0
  60. data/config/locales/sv.yml +6 -0
  61. data/config/locales/tr.yml +6 -0
  62. data/config/locales/uk.yml +6 -0
  63. data/config/locales/zh-CN.yml +6 -0
  64. data/config/locales/zh-TW.yml +6 -0
  65. data/config/storage.yml +1 -1
  66. data/gemfiles/7.2.gemfile +1 -0
  67. data/gemfiles/8.0.gemfile +1 -0
  68. data/gemfiles/8.1.gemfile +1 -0
  69. data/lib/comfortable_media_surfer/access_control/admin_authentication.rb +2 -2
  70. data/lib/comfortable_media_surfer/content/tags/date.rb +1 -1
  71. data/lib/comfortable_media_surfer/content/tags/datetime.rb +1 -1
  72. data/lib/comfortable_media_surfer/extensions/has_revisions.rb +2 -2
  73. data/lib/comfortable_media_surfer/routing.rb +2 -0
  74. data/lib/comfortable_media_surfer/seeds/page/importer.rb +40 -12
  75. data/lib/comfortable_media_surfer/seeds.rb +1 -3
  76. data/lib/comfortable_media_surfer/version.rb +1 -1
  77. metadata +2 -3
  78. data/gemfiles/7.1.gemfile +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e5be068cf905c6ec8cb3e1e509554abff7e77d8f2c75a21116f5ee3e80c65f9
4
- data.tar.gz: 723125a4edbd90114426132bf375fe6fd6cc9f501d638e8c48e4caf9a70ad1e7
3
+ metadata.gz: 9f97502706b77eb2d52d4e8bf2ee3bde3bbe30d73523fa969526752610749738
4
+ data.tar.gz: bc9b28a3c19372688e334f6b259325b9d6054913f52d42d255b2d9f8afa0172b
5
5
  SHA512:
6
- metadata.gz: a515bc875c3f765bff181e36b6eb9b7fd9fd2a6f1ce20b2f31390c5d93c81cab36036fea15fc98948220e8ea754f87021c0a96800833712647815f8058650e89
7
- data.tar.gz: 50b001c7926e4de6a6a00bbdddf11964bd976d3735e83f604836826fb5c5c1250683260db1864954493421cee08ed9af1765b664dc11050f2f522c0c7fecf00b
6
+ metadata.gz: 5711fa8354ad641f8667f70554aae551740325a650e86a61216bb68776e90b23cf741ba2760c6727c81da0d34ca5db04725ed7e7aa0add8141f429bd3b1a1559
7
+ data.tar.gz: 81087fabb487b7c0fd1455415051bc2be6fa053ebe50ada16936be258081d4841814e0fadaa744d4f6fbd670dda928db03213e9ea822a273ace8ad07d3be10af
@@ -22,6 +22,8 @@ jobs:
22
22
  - name: Run Claude Code Review
23
23
  id: claude-review
24
24
  uses: anthropics/claude-code-action@v1
25
+ env:
26
+ CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
25
27
  with:
26
28
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
27
29
  prompt: |
@@ -11,19 +11,112 @@ on:
11
11
  types: [submitted]
12
12
 
13
13
  jobs:
14
- claude:
14
+ authorize_claude_actor:
15
15
  if: |
16
16
  (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17
17
  (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18
18
  (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19
19
  (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20
20
  runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: read
23
+ outputs:
24
+ authorized: ${{ steps.permission-gate.outputs.authorized }}
25
+ steps:
26
+ - name: Verify actor can run Claude
27
+ id: permission-gate
28
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
29
+ with:
30
+ script: |
31
+ const owner = context.repo.owner;
32
+ const repo = context.repo.repo;
33
+ const permissionByLogin = new Map();
34
+
35
+ function claudeRequester() {
36
+ if (
37
+ context.eventName === 'issue_comment' ||
38
+ context.eventName === 'pull_request_review_comment'
39
+ ) {
40
+ return context.payload.comment?.user?.login;
41
+ }
42
+
43
+ if (context.eventName === 'pull_request_review') {
44
+ return context.payload.review?.user?.login;
45
+ }
46
+
47
+ if (context.eventName === 'issues') {
48
+ return context.payload.issue?.user?.login;
49
+ }
50
+
51
+ return null;
52
+ }
53
+
54
+ async function hasWriteAccessFor(username) {
55
+ if (!username) {
56
+ return false;
57
+ }
58
+
59
+ if (permissionByLogin.has(username)) {
60
+ return permissionByLogin.get(username);
61
+ }
62
+
63
+ let hasWriteAccess = false;
64
+ try {
65
+ const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
66
+ owner,
67
+ repo,
68
+ username
69
+ });
70
+ hasWriteAccess = ['admin', 'write'].includes(permission.permission);
71
+ } catch (error) {
72
+ core.warning(
73
+ `Unable to verify ${username} write permission for Claude; ` +
74
+ `treating the Claude request as untrusted: ${error.message || error}`
75
+ );
76
+ }
77
+
78
+ permissionByLogin.set(username, hasWriteAccess);
79
+ return hasWriteAccess;
80
+ }
81
+
82
+ const actor = context.actor;
83
+ const requester = claudeRequester();
84
+
85
+ if (!requester) {
86
+ core.setOutput('authorized', 'false');
87
+ core.setFailed('Unable to run Claude because the requester could not be determined.');
88
+ return;
89
+ }
90
+
91
+ if (!(await hasWriteAccessFor(actor))) {
92
+ core.setOutput('authorized', 'false');
93
+ core.setFailed(`Unable to run Claude because ${actor} does not have write/admin repository permission.`);
94
+ return;
95
+ }
96
+
97
+ if (!(await hasWriteAccessFor(requester))) {
98
+ core.setOutput('authorized', 'false');
99
+ core.setFailed(
100
+ `Unable to run Claude because requester ${requester} does not have write/admin repository permission.`
101
+ );
102
+ return;
103
+ }
104
+
105
+ core.setOutput('authorized', 'true');
106
+ core.info(`Authorized ${actor} and Claude requester ${requester} to run Claude.`);
107
+
108
+ claude:
109
+ needs: authorize_claude_actor
110
+ if: needs.authorize_claude_actor.outputs.authorized == 'true'
111
+ runs-on: ubuntu-latest
21
112
  permissions:
22
113
  contents: read
23
114
  pull-requests: read
24
115
  issues: read
25
116
  id-token: write
26
117
  actions: read # Required for Claude to read CI results on PRs
118
+ checks: read
119
+ statuses: read
27
120
  steps:
28
121
  - name: Checkout repository
29
122
  uses: actions/checkout@v4
@@ -33,12 +126,17 @@ jobs:
33
126
  - name: Run Claude Code
34
127
  id: claude
35
128
  uses: anthropics/claude-code-action@v1
129
+ env:
130
+ CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
36
131
  with:
132
+ github_token: ${{ github.token }}
37
133
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38
134
 
39
135
  # This is an optional setting that allows Claude to read CI results on PRs
40
136
  additional_permissions: |
41
137
  actions: read
138
+ checks: read
139
+ statuses: read
42
140
 
43
141
  # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
44
142
  # prompt: 'Update the pull request description to include a summary of changes.'
@@ -3,6 +3,7 @@ on:
3
3
  push:
4
4
  branches:
5
5
  - main
6
+ - master
6
7
  pull_request:
7
8
 
8
9
  jobs:
@@ -10,9 +11,9 @@ jobs:
10
11
  strategy:
11
12
  matrix:
12
13
  ruby-version:
13
- - "3.3"
14
+ - "3.4"
14
15
  rails-version:
15
- - "7.1"
16
+ - "8.1"
16
17
  continue-on-error: [true]
17
18
  name: ${{ format('Coverage (Ruby {0}, Rails {1})', matrix.ruby-version, matrix.rails-version) }}
18
19
  runs-on: ubuntu-latest
@@ -49,4 +50,4 @@ jobs:
49
50
  bin/bundle exec rails db:drop
50
51
  bin/bundle exec rails db:create
51
52
  bin/bundle exec rails db:migrate
52
- bin/bundle exec rails test:db
53
+ bin/bundle exec rake test:serial
@@ -3,6 +3,7 @@ on:
3
3
  push:
4
4
  branches:
5
5
  - main
6
+ - master
6
7
  pull_request:
7
8
 
8
9
  jobs:
@@ -10,19 +11,15 @@ jobs:
10
11
  strategy:
11
12
  matrix:
12
13
  ruby-version:
13
- - "3.2"
14
14
  - "3.3"
15
15
  - "3.4"
16
16
  - "4.0"
17
17
  rails-version:
18
- - "7.1"
19
18
  - "7.2"
20
19
  - "8.0"
21
20
  - "8.1"
22
- continue-on-error: [true]
23
21
  name: ${{ format('Tests (Ruby {0}, Rails {1})', matrix.ruby-version, matrix.rails-version) }}
24
22
  runs-on: ubuntu-latest
25
- continue-on-error: ${{ matrix.continue-on-error }}
26
23
  env:
27
24
  BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.rails-version }}.gemfile
28
25
  RAILS_ENV: test
@@ -54,7 +51,7 @@ jobs:
54
51
  bin/bundle exec rails db:drop
55
52
  bin/bundle exec rails db:create
56
53
  bin/bundle exec rails db:migrate
57
- bin/bundle exec rails test:db
54
+ bin/bundle exec rake test
58
55
 
59
56
  - name: Lint the code
60
57
  run: bin/bundle exec rubocop --parallel
@@ -63,3 +60,35 @@ jobs:
63
60
  run: |
64
61
  bin/bundle exec brakeman -q -w3
65
62
  bin/bundle exec bundler-audit --update --gemfile-lock gemfiles/${{ matrix.rails-version }}.gemfile.lock
63
+
64
+ system_tests:
65
+ name: System tests (Ruby 4.0, Rails 8.1)
66
+ runs-on: ubuntu-latest
67
+ env:
68
+ BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/8.1.gemfile
69
+ RAILS_ENV: test
70
+ SKIP_COV: true
71
+ steps:
72
+ - name: Checkout source
73
+ uses: actions/checkout@v4
74
+
75
+ - name: Install Node.js
76
+ uses: actions/setup-node@v4
77
+ with:
78
+ cache: npm
79
+ - run: npm ci
80
+
81
+ - name: Install Ruby 4.0
82
+ uses: ruby/setup-ruby@v1
83
+ with:
84
+ ruby-version: "4.0"
85
+ bundler-cache: true
86
+
87
+ - name: Compile assets
88
+ run: npm run build && npm run build:css
89
+
90
+ - name: Prepare database
91
+ run: bin/bundle exec rails db:prepare
92
+
93
+ - name: Run system tests
94
+ run: bin/bundle exec rake test:system
data/CHANGELOG.md CHANGED
@@ -12,17 +12,37 @@ For all changes prior to the inception of this project, see the [Release History
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [v3.1.7] - 2026-02-20
16
+
17
+ ### Fixed
18
+
19
+ - Fix wrong number of arguments for `to_s` in date tags from [PR 58](https://github.com/shakacode/comfortable-media-surfer/pull/58)
20
+
21
+ ### Changed
22
+
23
+ - Update rubocop requirement from ~> 1.81.1 to ~> 1.84.0
24
+
25
+ ## [v3.1.6] - 2026-02-19
26
+
27
+ ### Added
28
+
29
+ - Claude code workflow
30
+
31
+ ### Fixed
32
+
33
+ - problem creating root/home page from [P5 45](https://github.com/shakacode/comfortable-media-surfer/pull/45)
34
+
15
35
  ## [v3.1.5] - 2026-01-08
16
36
 
17
- ## Added
37
+ ### Added
18
38
 
19
39
  - Rails 8.1 compatibility
20
40
 
21
- ## Fixed
41
+ ### Fixed
22
42
 
23
- - Strip possible spaces around exclude page slugs, from [PR](https://github.com/shakacode/comfortable-media-surfer/pull/42)
43
+ - Strip possible spaces around exclude page slugs, from [PR 42](https://github.com/shakacode/comfortable-media-surfer/pull/42)
24
44
 
25
- ## Changed
45
+ ### Changed
26
46
 
27
47
  - gem updates for propshaft, brakeman, rubocop, haml-rails
28
48
 
@@ -115,7 +135,9 @@ First release of `comfortable_media_surfer`. This new gem is a revival of [Comfo
115
135
 
116
136
  - Rebranded **ComfortableMexicanSofa** as **ComfortableMediaSurfer** in order to publish new gem (database table names and schema have not changed).
117
137
 
118
- [Unreleased]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.5...master
138
+ [Unreleased]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.7...master
139
+ [v3.1.7]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.6...v3.1.7
140
+ [v3.1.6]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.5...v3.1.6
119
141
  [v3.1.5]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.4...v3.1.5
120
142
  [v3.1.4]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.3...v3.1.4
121
143
  [v3.1.3]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.2...v3.1.3
data/CONTRIBUTING.md CHANGED
@@ -13,7 +13,11 @@ Fork the project. Optionally, create a branch you want to work on.
13
13
  created for you. Just run `bundle exec rake db:migrate`
14
14
  - Prepare the environment by running `rails comfy:compile_assets`, and
15
15
  `rake db:test:prepare`
16
- - Make sure that existing tests are passing by running `bundle exec rake test`
16
+ - Make sure that existing tests are passing by running `bundle exec rake test`.
17
+ This uses six isolated workers by default, merges their coverage, and records
18
+ per-file runtimes in `tmp/parallel_runtime_test.log` for future balancing.
19
+ Set `PARALLEL_WORKERS` to tune the worker count, or run
20
+ `bundle exec rake test:serial` for a serial coverage run.
17
21
  - There are system tests that can be run with `bundle exec rake test:system`.
18
22
  You need to have `chromedriver` installed for that.
19
23
  - You should be able to start the app via `bin/rails s` and navigate to http://localhost:3000/admin
data/Gemfile CHANGED
@@ -9,9 +9,9 @@ gem 'rails', '~> 8.1.0'
9
9
 
10
10
  group :development, :test do
11
11
  gem 'autoprefixer-rails', '~> 10.4.21.0'
12
- gem 'byebug', '~> 12.0.0', platforms: %i[mri mingw x64_mingw]
12
+ gem 'byebug', '~> 13.0.0', platforms: %i[mri mingw x64_mingw]
13
13
  gem 'gem-release'
14
- gem 'image_processing', '>= 1.12.0'
14
+ gem 'image_processing', '>= 1.12.2'
15
15
  gem 'propshaft', '~> 1.3.1'
16
16
  gem 'sqlite3', '>= 2.1'
17
17
  # gem 'mysql2', '~> 0.5'
@@ -19,12 +19,12 @@ group :development, :test do
19
19
  end
20
20
 
21
21
  group :development do
22
- gem 'listen', '~> 3.9.0'
22
+ gem 'listen', '~> 3.10.0'
23
23
  gem 'web-console', '~> 4.2'
24
24
  end
25
25
 
26
26
  group :test do
27
- gem 'brakeman', '~> 7.1.0'
27
+ gem 'brakeman'
28
28
  gem 'bundler-audit', '~> 0.9.1'
29
29
  gem 'coveralls_reborn', '~> 0.29.0', require: false
30
30
  gem 'cuprite', '>= 0.15'
@@ -34,9 +34,10 @@ group :test do
34
34
  gem 'minitest-reporters', '>= 1.6.1'
35
35
  gem 'mocha', '>= 2.3.0', require: false
36
36
  gem 'ostruct'
37
+ gem 'parallel_tests', '~> 5.7', require: false
37
38
  gem 'puma'
38
39
  gem 'rails-controller-testing', '~> 1.0.5'
39
- gem 'rubocop', '~> 1.81.1', require: false
40
+ gem 'rubocop', require: false
40
41
  gem 'rubocop-minitest'
41
42
  gem 'rubocop-rails'
42
43
  gem 'simplecov', '~> 0.22.0', require: false
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  [![Rails CI](https://github.com/shakacode/comfortable-media-surfer/actions/workflows/rubyonrails.yml/badge.svg)](https://github.com/shakacode/comfortable-media-surfer/actions/workflows/rubyonrails.yml)
2
- [![Coverage Status](https://coveralls.io/repos/github/shakacode/comfortable-media-surfer/badge.svg?branch=HEAD)](https://coveralls.io/github/shakacode/comfortable-media-surfer?branch=HEAD)
2
+ [![Coverage Status](https://coveralls.io/repos/github/shakacode/comfortable-media-surfer/badge.svg?branch=master)](https://coveralls.io/github/shakacode/comfortable-media-surfer?branch=master)
3
3
  [![Gem Version](https://img.shields.io/gem/v/comfortable_media_surfer.svg?style=flat)](http://rubygems.org/gems/comfortable_media_surfer)
4
4
  [![Gem Downloads](https://img.shields.io/gem/dt/comfortable_media_surfer.svg?style=flat)](http://rubygems.org/gems/comfortable_media_surfer)
5
5
  [![GitHub Release Date - Published_At](https://img.shields.io/github/release-date/shakacode/comfortable-media-surfer?label=last%20release&color=seagreen)](https://github.com/shakacode/comfortable-media-surfer/releases)
6
6
 
7
7
  # ComfortableMediaSurfer
8
8
 
9
- ComfortableMediaSurfer is a powerful Ruby 7.0+ CMS (Content Management System) Engine, picking up where [ComfortableMexicanSofa](https://github.com/comfy/comfortable-mexican-sofa) left off.
9
+ ComfortableMediaSurfer is a powerful Ruby 7.2+ CMS (Content Management System) Engine, picking up where [ComfortableMexicanSofa](https://github.com/comfy/comfortable-mexican-sofa) left off.
10
10
 
11
11
  ## Features
12
12
 
13
- - Simple drop-in integration with Rails 7.0+ apps with minimal configuration
13
+ - Simple drop-in integration with Rails 7.2+ apps with minimal configuration
14
14
  * The CMS keeps clear from the rest of your application
15
15
  * Powerful page templating capability using [Content Tags](https://github.com/shakacode/comfortable-media-surfer/wiki/Docs:-Content-Tags)
16
16
  * [Multiple Sites](https://github.com/shakacode/comfortable-media-surfer/wiki/Docs:-Sites) from a single installation
@@ -27,7 +27,7 @@ ComfortableMediaSurfer is a powerful Ruby 7.0+ CMS (Content Management System) E
27
27
 
28
28
  ## Compatibility
29
29
 
30
- On Ruby 3.x, Rails 7.x +
30
+ On Ruby 3.2+, 4.x, Rails 7.2+, 8.x
31
31
 
32
32
  ## Installation
33
33
 
data/Rakefile CHANGED
@@ -1,5 +1,206 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'config/application'
4
+ require 'open3'
5
+ require 'parallel_tests/tasks'
6
+ require 'rake/testtask'
7
+ require 'stringio'
4
8
 
5
9
  Rails.application.load_tasks
10
+
11
+ module ComfortableMediaSurferTestOutput
12
+ SUMMARY = %r{(?<assertions>\d+) assertions, (?<errors>\d+) errors, (?<failures>\d+) failures, (?<skips>\d+) skips?, (?<tests>\d+) tests}
13
+ COLORS = { green: 32, yellow: 33, red: 31, cyan: 36 }.freeze
14
+
15
+ class Progress
16
+ def initialize(io)
17
+ @io = io
18
+ @line = +''
19
+ @pending = +''
20
+ @awaiting_blank = false
21
+ @expecting_progress = false
22
+ @printing_progress = false
23
+ end
24
+
25
+ def feed(chunk)
26
+ chunk.each_char { |character| consume(character) }
27
+ flush
28
+ end
29
+
30
+ def finish
31
+ flush
32
+ @io.puts if @printing_progress
33
+ end
34
+
35
+ private
36
+
37
+ def consume(character)
38
+ return if character == "\r"
39
+ return finish_line if character == "\n"
40
+
41
+ if @expecting_progress && character.match?(%r{[.EFS]})
42
+ @printing_progress = true
43
+ @pending << character
44
+ elsif @printing_progress
45
+ flush
46
+ @io.puts
47
+ @printing_progress = false
48
+ @expecting_progress = false
49
+ @line = +character
50
+ else
51
+ @expecting_progress = false if @expecting_progress
52
+ @line << character
53
+ end
54
+ end
55
+
56
+ def finish_line
57
+ flush
58
+ if @printing_progress
59
+ @io.puts
60
+ @expecting_progress = false
61
+ elsif @line.start_with?('# Running tests')
62
+ @awaiting_blank = true
63
+ elsif @awaiting_blank && @line.empty?
64
+ @expecting_progress = true
65
+ @awaiting_blank = false
66
+ end
67
+ @line.clear
68
+ @printing_progress = false
69
+ end
70
+
71
+ def flush
72
+ return if @pending.empty?
73
+
74
+ @io.print ComfortableMediaSurferTestOutput.progress_color(@pending)
75
+ @io.flush
76
+ @pending.clear
77
+ end
78
+ end
79
+
80
+ module_function
81
+
82
+ def capture(env, command)
83
+ output = +''
84
+ errors = +''
85
+ status = nil
86
+ started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
87
+ progress = Progress.new($stdout)
88
+
89
+ Open3.popen3(env, *command) do |stdin, stdout, stderr, wait_thread|
90
+ stdin.close
91
+ error_reader = Thread.new { stderr.read }
92
+ loop do
93
+ chunk = stdout.readpartial(4096)
94
+ output << chunk
95
+ progress.feed(chunk)
96
+ end
97
+ rescue EOFError
98
+ errors = error_reader.value
99
+ status = wait_thread.value
100
+ ensure
101
+ progress.finish
102
+ end
103
+
104
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
105
+ [output, errors, status, elapsed]
106
+ end
107
+
108
+ def color(text, name)
109
+ return text unless $stdout.tty? && !ENV.key?('NO_COLOR')
110
+
111
+ "\e[#{COLORS.fetch(name)}m#{text}\e[0m"
112
+ end
113
+
114
+ def progress_color(text)
115
+ return text unless $stdout.tty? && !ENV.key?('NO_COLOR')
116
+
117
+ highlighted = text.gsub('S', "\e[33mS\e[32m")
118
+ .gsub(%r{[EF]}) { |character| "\e[31m#{character}\e[32m" }
119
+ "\e[32m#{highlighted}\e[0m"
120
+ end
121
+
122
+ def summary(output)
123
+ counts = output.match(SUMMARY)
124
+ raise 'Could not read the parallel test summary' unless counts
125
+
126
+ "#{counts[:tests]} tests, #{counts[:assertions]} assertions, " \
127
+ "#{counts[:failures]} failures, #{counts[:errors]} errors, " \
128
+ "#{counts[:skips]} #{counts[:skips] == '1' ? 'skip' : 'skips'}"
129
+ end
130
+
131
+ def run_parallel
132
+ workers = ENV.fetch('PARALLEL_WORKERS', '6')
133
+ skip_coverage = ENV.fetch('SKIP_COV', nil)
134
+ FileUtils.rm_rf('coverage')
135
+ FileUtils.mkdir_p('tmp')
136
+ Rake::Task['parallel:prepare'].invoke(workers)
137
+
138
+ env = {
139
+ 'PARALLEL_COVERAGE' => skip_coverage ? nil : 'true',
140
+ 'RECORD_RUNTIME' => 'true',
141
+ 'SKIP_COV' => skip_coverage,
142
+ 'NO_COLOR' => '1',
143
+ 'TMPDIR' => ENV.fetch('COMFY_TEST_TMPDIR', '/tmp')
144
+ }
145
+ command = [
146
+ 'bundle', 'exec', 'parallel_test', 'test',
147
+ '--type', 'test',
148
+ '--exclude-pattern', 'test/system/',
149
+ '-n', workers
150
+ ]
151
+
152
+ puts color("Running tests (#{workers} workers)…", :cyan)
153
+ stdout, stderr, status, elapsed = capture(env, command)
154
+ unless status.success?
155
+ $stdout.write(stdout)
156
+ $stderr.write(stderr)
157
+ raise "Parallel tests failed with status #{status.exitstatus}"
158
+ end
159
+ $stderr.write(stderr) unless stderr.empty?
160
+ puts color(summary(stdout), :green)
161
+ puts color(format('Completed in %.1f seconds', elapsed), :cyan)
162
+
163
+ report_coverage unless skip_coverage
164
+ end
165
+
166
+ def report_coverage
167
+ require 'simplecov'
168
+ SimpleCov.coverage_dir File.expand_path('coverage', __dir__)
169
+ result = SimpleCov::ResultMerger.merged_result
170
+ quietly { SimpleCov::Formatter::HTMLFormatter.new.format(result) }
171
+ coverage = format('Coverage: %<percent>.2f%% (%<covered>d/%<total>d) — coverage/index.html',
172
+ percent: result.covered_percent, covered: result.covered_lines, total: result.total_lines)
173
+ puts color(coverage, :green)
174
+ end
175
+
176
+ def quietly
177
+ previous = $stdout
178
+ $stdout = StringIO.new
179
+ yield
180
+ ensure
181
+ $stdout = previous
182
+ end
183
+ end
184
+
185
+ namespace :test do
186
+ task :prepare_serial do
187
+ FileUtils.rm_rf('coverage')
188
+ ENV['TMPDIR'] = ENV.fetch('COMFY_TEST_TMPDIR', '/tmp')
189
+ end
190
+
191
+ desc 'Run tests serially with coverage'
192
+ Rake::TestTask.new(:serial) do |task|
193
+ task.libs << 'test' << 'lib'
194
+ task.test_files = Rake::FileList['test/**/*_test.rb'].exclude('test/system/**/*_test.rb')
195
+ task.warning = false
196
+ end
197
+ Rake::Task['test:serial'].enhance(['test:prepare_serial'])
198
+
199
+ desc 'Run tests in isolated processes'
200
+ task(:parallel) { ComfortableMediaSurferTestOutput.run_parallel }
201
+ end
202
+
203
+ Rake::Task[:test].clear
204
+
205
+ desc 'Run tests in parallel (default: 6 workers)'
206
+ task test: 'test:parallel'
@@ -1,4 +1,7 @@
1
+ import jQuery from "jquery";
1
2
  import ujs from "@rails/ujs";
3
+
4
+ window.jQuery = window.$ = jQuery;
2
5
  ujs.start();
3
6
 
4
7
  import "bootstrap/js/src/tab";
@@ -37,6 +37,7 @@
37
37
  CMS.codemirror.dispose();
38
38
  CMS.wysiwyg.dispose();
39
39
  CMS.files.dispose();
40
+ CMS.fileLinks.dispose();
40
41
  CMS.fileUpload.dispose();
41
42
  CMS.sortableList.dispose();
42
43
  CMS.timepicker.dispose();