Compare commits

..

89 Commits

Author SHA1 Message Date
google-labs-jules[bot]
564f5f0dca feat: Convert navbar to Bootstrap 5.1 offcanvas menu
This commit changes the navbar to use the Bootstrap 5.1 offcanvas component.

The following changes were made:
- Modified `app/views/layouts/_header.html.haml` to update the navbar-toggler to trigger the offcanvas menu.
- Modified `app/views/layouts/_menu.haml` to wrap the menu in an offcanvas component.
2025-09-01 07:56:38 +00:00
google-labs-jules[bot]
76a6c1d849 Merge pull request #4131 from Growstuff/feature/add-social-media-links
feat: Add social media links to user profiles
2025-08-24 16:21:05 +09:30
dependabot[bot]
a7d100fe7e Merge pull request #4137 from Growstuff/dependabot/bundler/selenium-webdriver-4.35.0 2025-08-14 14:15:43 +00:00
dependabot[bot]
810a9e39e3 Bump selenium-webdriver from 4.34.0 to 4.35.0
Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.34.0 to 4.35.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)
- [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.34.0...selenium-4.35.0)

---
updated-dependencies:
- dependency-name: selenium-webdriver
  dependency-version: 4.35.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-14 14:09:03 +00:00
dependabot[bot]
b320435ddf Merge pull request #4141 from Growstuff/dependabot/bundler/rubocop-rails-2.33.3 2025-08-14 14:07:43 +00:00
dependabot[bot]
2f7ce2721f Bump rubocop-rails from 2.32.0 to 2.33.3
Bumps [rubocop-rails](https://github.com/rubocop/rubocop-rails) from 2.32.0 to 2.33.3.
- [Release notes](https://github.com/rubocop/rubocop-rails/releases)
- [Changelog](https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop-rails/compare/v2.32.0...v2.33.3)

---
updated-dependencies:
- dependency-name: rubocop-rails
  dependency-version: 2.33.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-14 13:53:23 +00:00
dependabot[bot]
c9e19203a9 Merge pull request #4142 from Growstuff/dependabot/bundler/rails-7.2.2.2 2025-08-14 13:52:01 +00:00
dependabot[bot]
005efab3be Bump rails from 7.2.2.1 to 7.2.2.2
Bumps [rails](https://github.com/rails/rails) from 7.2.2.1 to 7.2.2.2.
- [Release notes](https://github.com/rails/rails/releases)
- [Commits](https://github.com/rails/rails/compare/v7.2.2.1...v7.2.2.2)

---
updated-dependencies:
- dependency-name: rails
  dependency-version: 7.2.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-14 07:08:38 +00:00
dependabot[bot]
46599937d9 Merge pull request #4138 from Growstuff/dependabot/bundler/rspec-rails-8.0.2 2025-08-13 09:47:01 +00:00
dependabot[bot]
496a6ac2b6 Bump rspec-rails from 8.0.1 to 8.0.2
Bumps [rspec-rails](https://github.com/rspec/rspec-rails) from 8.0.1 to 8.0.2.
- [Changelog](https://github.com/rspec/rspec-rails/blob/main/Changelog.md)
- [Commits](https://github.com/rspec/rspec-rails/compare/v8.0.1...v8.0.2)

---
updated-dependencies:
- dependency-name: rspec-rails
  dependency-version: 8.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-13 07:17:23 +00:00
Daniel O'Connor
59bd6f3450 Merge pull request #4065 from Growstuff/dependabot/bundler/rake-13.3.0
Bump rake from 13.2.1 to 13.3.0
2025-08-10 22:47:04 +09:30
dependabot[bot]
3e9bf73297 Merge pull request #4067 from Growstuff/dependabot/bundler/bullet-8.0.8 2025-08-10 08:50:09 +00:00
Daniel O'Connor
dcff643637 Merge branch 'dev' into dependabot/bundler/bullet-8.0.8 2025-08-10 17:53:48 +09:30
dependabot[bot]
a83966aa92 Merge pull request #4108 from Growstuff/dependabot/bundler/scout_apm-5.7.0 2025-08-10 07:40:46 +00:00
dependabot[bot]
1c47e421b9 Bump scout_apm from 5.6.4 to 5.7.0
Bumps [scout_apm](https://github.com/scoutapp/scout_apm_ruby) from 5.6.4 to 5.7.0.
- [Changelog](https://github.com/scoutapp/scout_apm_ruby/blob/master/CHANGELOG.markdown)
- [Commits](https://github.com/scoutapp/scout_apm_ruby/compare/v5.6.4...v5.7.0)

---
updated-dependencies:
- dependency-name: scout_apm
  dependency-version: 5.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 07:19:44 +00:00
Daniel O'Connor
6289ab0951 Bump faker from 3.5.1 to 3.5.2 (#4082)
Bumps [faker](https://github.com/faker-ruby/faker) from 3.5.1 to 3.5.2.
- [Release notes](https://github.com/faker-ruby/faker/releases)
- [Changelog](https://github.com/faker-ruby/faker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/faker-ruby/faker/compare/v3.5.1...v3.5.2)

---
updated-dependencies:
- dependency-name: faker
  dependency-version: 3.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-10 16:48:30 +09:30
dependabot[bot]
d127647eeb Merge pull request #4105 from Growstuff/dependabot/bundler/puma-6.6.1 2025-08-10 07:05:39 +00:00
dependabot[bot]
9eda4bb2f2 Bump puma from 6.6.0 to 6.6.1
Bumps [puma](https://github.com/puma/puma) from 6.6.0 to 6.6.1.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v6.6.0...v6.6.1)

---
updated-dependencies:
- dependency-name: puma
  dependency-version: 6.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 06:44:35 +00:00
dependabot[bot]
f1d524130b Merge pull request #4110 from Growstuff/dependabot/bundler/rspec-activemodel-mocks-1.3.0 2025-08-10 06:43:20 +00:00
dependabot[bot]
e4de08f47f Bump rspec-activemodel-mocks from 1.2.1 to 1.3.0
Bumps [rspec-activemodel-mocks](https://github.com/rspec/rspec-activemodel-mocks) from 1.2.1 to 1.3.0.
- [Changelog](https://github.com/rspec/rspec-activemodel-mocks/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rspec/rspec-activemodel-mocks/compare/v1.2.1...v1.3.0)

---
updated-dependencies:
- dependency-name: rspec-activemodel-mocks
  dependency-version: 1.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 06:29:48 +00:00
Daniel O'Connor
0b4820e5df Add rspec-retry (#4129)
* Add rspec-retry

* Add config

* Swap to rspec-rebound, which is a fork

* Swap to rspec-rebound, which is a fork
2025-08-10 15:55:52 +09:30
dependabot[bot]
d264905aa9 Bump faraday from 2.13.2 to 2.13.4 (#4101)
Bumps [faraday](https://github.com/lostisland/faraday) from 2.13.2 to 2.13.4.
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lostisland/faraday/compare/v2.13.2...v2.13.4)

---
updated-dependencies:
- dependency-name: faraday
  dependency-version: 2.13.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-10 15:54:37 +09:30
Daniel O'Connor
25d2c5f854 Merge branch 'dev' into dependabot/bundler/bullet-8.0.8 2025-08-10 15:46:09 +09:30
google-labs-jules[bot]
d383c8e2e4 Add filtering for tradeable seeds (#4111)
* feat: Add filtering for tradeable seeds

This change introduces a new feature to filter seeds based on their tradeability.

- Adds a link on the homepage to view all tradeable seeds.
- Modifies the seeds controller to support filtering by `tradeable_to`, allowing multiple values to be selected.
- Adds links to the seeds index page to filter by the various `tradeable_to` values.

* Update index.html.haml

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Daniel O'Connor <daniel.oconnor@gmail.com>
2025-08-10 15:44:01 +09:30
dependabot[bot]
ed89163311 Bump faker from 3.5.1 to 3.5.2
Bumps [faker](https://github.com/faker-ruby/faker) from 3.5.1 to 3.5.2.
- [Release notes](https://github.com/faker-ruby/faker/releases)
- [Changelog](https://github.com/faker-ruby/faker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/faker-ruby/faker/compare/v3.5.1...v3.5.2)

---
updated-dependencies:
- dependency-name: faker
  dependency-version: 3.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 05:43:20 +00:00
Daniel O'Connor
6ae2de7e47 Merge pull request #4086 from Growstuff/mailboxer-translations
Mailboxer translations
2025-08-10 14:57:39 +09:30
Daniel O'Connor
da74c19c2a Merge pull request #4094 from Growstuff/dependabot/bundler/recaptcha-5.20.1
Bump recaptcha from 5.19.0 to 5.20.1
2025-08-10 14:57:23 +09:30
Daniel O'Connor
d767a011a4 Merge pull request #4126 from Growstuff/split-up-ci
Split up ci
2025-08-10 13:53:47 +09:30
Daniel O'Connor
bbe6a3fd36 Merge pull request #4123 from Growstuff/view-transitions
Opt into view transitions
2025-08-10 13:52:34 +09:30
dependabot[bot]
3eb2fe7637 Bump recaptcha from 5.19.0 to 5.20.1
Bumps [recaptcha](https://github.com/ambethia/recaptcha) from 5.19.0 to 5.20.1.
- [Changelog](https://github.com/ambethia/recaptcha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ambethia/recaptcha/compare/v5.19.0...v5.20.1)

---
updated-dependencies:
- dependency-name: recaptcha
  dependency-version: 5.20.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 04:16:17 +00:00
dependabot[bot]
7f0af10637 Merge pull request #4109 from Growstuff/dependabot/bundler/rubocop-1.79.2 2025-08-10 04:15:01 +00:00
dependabot[bot]
75d93bb3c3 Bump rake from 13.2.1 to 13.3.0
Bumps [rake](https://github.com/ruby/rake) from 13.2.1 to 13.3.0.
- [Release notes](https://github.com/ruby/rake/releases)
- [Changelog](https://github.com/ruby/rake/blob/master/History.rdoc)
- [Commits](https://github.com/ruby/rake/compare/v13.2.1...v13.3.0)

---
updated-dependencies:
- dependency-name: rake
  dependency-version: 13.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 04:04:03 +00:00
Daniel O'Connor
c49f2bab19 Merge branch 'dev' into view-transitions 2025-08-10 13:24:41 +09:30
dependabot[bot]
ed71023306 Bump rubocop from 1.79.1 to 1.79.2
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.79.1 to 1.79.2.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.79.1...v1.79.2)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.79.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 03:47:36 +00:00
Daniel O'Connor
59d95dfd77 Split up 2025-08-10 03:22:32 +00:00
Daniel O'Connor
e999c5870f Merge branch 'dev' into split-up-ci 2025-08-10 12:51:39 +09:30
Daniel O'Connor
072ec1dd82 Merge pull request #4125 from Growstuff/set-window-size
Specs: Target a desktop, fixing tests in codespaces at least.
2025-08-10 12:51:06 +09:30
Daniel O'Connor
8ecae23d61 Split up posts 2025-08-10 03:20:57 +00:00
Daniel O'Connor
d20ceb54b5 Avoid double running plantings features 2025-08-10 03:19:24 +00:00
Daniel O'Connor
7e3be99aac Comment out and explain why 2025-08-10 03:12:58 +00:00
Daniel O'Connor
d99d4a1bbe Disable dev shm usage on some environments 2025-08-10 03:08:57 +00:00
Daniel O'Connor
ea530754aa Disable dev shm usage on some environments 2025-08-10 03:06:38 +00:00
Daniel O'Connor
4bd322d8ca Target a desktop 2025-08-10 02:55:34 +00:00
Daniel O'Connor
5fc41ca50f Opt into view transitions 2025-08-10 02:47:44 +00:00
Daniel O'Connor
d0d31edd5e Merge pull request #4121 from Growstuff/delete-openfarm-pictures-rake-task
Swap to iteration for now, so callbacks are happy
2025-08-10 11:52:14 +09:30
Daniel O'Connor
67a2005f1e Swap to iteration for now, so callbacks are happy 2025-08-10 02:21:39 +00:00
Daniel O'Connor
b528b40052 Merge pull request #4119 from Growstuff/delete-openfarm-pictures-rake-task
Swap to iteration for now, so callbacks are happy
2025-08-10 11:36:10 +09:30
Daniel O'Connor
3b17265a92 Swap to iteration for now, so callbacks are happy 2025-08-10 02:05:29 +00:00
Daniel O'Connor
f8c93f16db Merge pull request #4113 from Growstuff/delete-openfarm-pictures-rake-task
Add rake task to delete pictures with source OpenFarm
2025-08-10 11:16:05 +09:30
Daniel O'Connor
8006d26c6e Fix source 2025-08-10 01:45:29 +00:00
Daniel O'Connor
2766082696 Merge branch 'dev' of https://github.com/Growstuff/growstuff into delete-openfarm-pictures-rake-task 2025-08-10 01:43:11 +00:00
Daniel O'Connor
d887d5c788 Merge pull request #4116 from Growstuff/dev-fixed
Add crowdin tools
2025-08-10 11:09:32 +09:30
Daniel O'Connor
0634719afc Add crowdin tools 2025-08-10 01:38:53 +00:00
Daniel O'Connor
a13cf93aeb Merge pull request #4115 from Growstuff/3.3.8
Bump to Ruby 3.3.8
2025-08-10 11:07:34 +09:30
Daniel O'Connor
04376f15a2 Merge pull request #4114 from Growstuff/add-crowdin-support
Add CrowdIn translations support
2025-08-10 10:59:16 +09:30
Daniel O'Connor
82d3f84e0c Ruby 3.3.8 2025-08-10 01:09:08 +00:00
Daniel O'Connor
af6cee2f16 Merge branch 'dev' into dependabot/bundler/bullet-8.0.8 2025-08-10 10:36:54 +09:30
google-labs-jules[bot]
4f705ff37b Add CrowdIn translations support
This change adds support for CrowdIn to manage translations.

- Adds the `crowdin-cli` gem to the `Gemfile`.
- Adds a `crowdin.yml` configuration file to the project root.
2025-08-10 01:02:39 +00:00
google-labs-jules[bot]
4829d5513c This commit adds a rake task to delete all pictures that have 'OpenFarm' as their source. This is useful for cleaning up data imported from OpenFarm. 2025-08-10 00:58:03 +00:00
Daniel O'Connor
d1419aaca4 Merge pull request #4104 from Growstuff/dependabot/bundler/rubocop-1.79.1
Bump rubocop from 1.78.0 to 1.79.1
2025-08-05 18:29:49 +09:30
dependabot[bot]
2458afc451 Bump rubocop from 1.78.0 to 1.79.1
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.78.0 to 1.79.1.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.78.0...v1.79.1)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.79.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 23:21:45 +00:00
Daniel O'Connor
278be7067f Merge pull request #4107 from Growstuff/dependabot/bundler/haml_lint-0.66.0
Bump haml_lint from 0.62.0 to 0.66.0
2025-08-05 08:50:22 +09:30
dependabot[bot]
bc71c4a706 Bump haml_lint from 0.62.0 to 0.66.0
Bumps [haml_lint](https://github.com/sds/haml-lint) from 0.62.0 to 0.66.0.
- [Release notes](https://github.com/sds/haml-lint/releases)
- [Changelog](https://github.com/sds/haml-lint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sds/haml-lint/compare/v0.62.0...v0.66.0)

---
updated-dependencies:
- dependency-name: haml_lint
  dependency-version: 0.66.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 23:06:28 +00:00
Daniel O'Connor
b1a42a2ed6 Merge pull request #4106 from Growstuff/dependabot/bundler/pg-1.6.1
Bump pg from 1.5.9 to 1.6.1
2025-08-05 08:35:03 +09:30
dependabot[bot]
fe22c92a63 Bump pg from 1.5.9 to 1.6.1
Bumps [pg](https://github.com/ged/ruby-pg) from 1.5.9 to 1.6.1.
- [Changelog](https://github.com/ged/ruby-pg/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ged/ruby-pg/compare/v1.5.9...v1.6.1)

---
updated-dependencies:
- dependency-name: pg
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 09:49:20 +00:00
Daniel O'Connor
f00b31cf46 Merge pull request #4093 from Growstuff/dependabot/bundler/nokogiri-1.18.9
Bump nokogiri from 1.18.8 to 1.18.9
2025-07-25 20:32:04 +09:30
dependabot[bot]
7cfee1d89c Bump nokogiri from 1.18.8 to 1.18.9
---
updated-dependencies:
- dependency-name: nokogiri
  dependency-version: 1.18.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-22 03:24:14 +00:00
Daniel O'Connor
6c89cef5bc Merge pull request #4078 from Growstuff/dependabot/bundler/icalendar-2.11.2
Bump icalendar from 2.11.0 to 2.11.2
2025-07-17 08:56:42 +09:30
dependabot[bot]
5d79aa3e60 Bump icalendar from 2.11.0 to 2.11.2
Bumps [icalendar](https://github.com/icalendar/icalendar) from 2.11.0 to 2.11.2.
- [Changelog](https://github.com/icalendar/icalendar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/icalendar/icalendar/compare/v2.11.0...v2.11.2)

---
updated-dependencies:
- dependency-name: icalendar
  dependency-version: 2.11.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-16 23:17:21 +00:00
Daniel O'Connor
29c8d87703 Merge pull request #4071 from Growstuff/dependabot/bundler/factory_bot_rails-6.5.0
Bump factory_bot_rails from 6.4.4 to 6.5.0
2025-07-17 08:46:41 +09:30
Daniel O'Connor
83ef779c11 Merge pull request #4076 from Growstuff/dependabot/bundler/rspec-rails-8.0.1
Bump rspec-rails from 8.0.0 to 8.0.1
2025-07-17 08:46:20 +09:30
Daniel O'Connor
4ada3e1585 Merge pull request #4080 from Growstuff/dependabot/bundler/selenium-webdriver-4.34.0
Bump selenium-webdriver from 4.32.0 to 4.34.0
2025-07-17 08:46:00 +09:30
Daniel O'Connor
88f5622d0c Merge branch 'dev' into dependabot/bundler/bullet-8.0.8 2025-07-15 09:11:20 +09:30
dependabot[bot]
fa6752c668 Bump selenium-webdriver from 4.32.0 to 4.34.0
Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.32.0 to 4.34.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)
- [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.32.0...selenium-4.34.0)

---
updated-dependencies:
- dependency-name: selenium-webdriver
  dependency-version: 4.34.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-13 10:53:23 +00:00
dependabot[bot]
f9186c1a8a Bump rspec-rails from 8.0.0 to 8.0.1
Bumps [rspec-rails](https://github.com/rspec/rspec-rails) from 8.0.0 to 8.0.1.
- [Changelog](https://github.com/rspec/rspec-rails/blob/main/Changelog.md)
- [Commits](https://github.com/rspec/rspec-rails/compare/v8.0.0...v8.0.1)

---
updated-dependencies:
- dependency-name: rspec-rails
  dependency-version: 8.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-13 10:53:10 +00:00
dependabot[bot]
82dcd1d86c Bump factory_bot_rails from 6.4.4 to 6.5.0
Bumps [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails) from 6.4.4 to 6.5.0.
- [Release notes](https://github.com/thoughtbot/factory_bot_rails/releases)
- [Changelog](https://github.com/thoughtbot/factory_bot_rails/blob/main/NEWS.md)
- [Commits](https://github.com/thoughtbot/factory_bot_rails/compare/v6.4.4...v6.5.0)

---
updated-dependencies:
- dependency-name: factory_bot_rails
  dependency-version: 6.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-13 10:52:57 +00:00
Daniel O'Connor
b2146e791f Merge pull request #4083 from Growstuff/dependabot/bundler/faraday-2.13.2
Bump faraday from 2.13.1 to 2.13.2
2025-07-13 20:01:54 +09:30
dependabot[bot]
a9c33a30d2 Bump faraday from 2.13.1 to 2.13.2
Bumps [faraday](https://github.com/lostisland/faraday) from 2.13.1 to 2.13.2.
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lostisland/faraday/compare/v2.13.1...v2.13.2)

---
updated-dependencies:
- dependency-name: faraday
  dependency-version: 2.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-13 10:30:42 +00:00
Daniel O'Connor
497cb6627d Merge pull request #4085 from Growstuff/dependabot/bundler/rubocop-1.78.0
Bump rubocop from 1.76.0 to 1.78.0
2025-07-13 19:59:21 +09:30
dependabot[bot]
a8a96ae125 Bump rubocop from 1.76.0 to 1.78.0
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.76.0 to 1.78.0.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.76.0...v1.78.0)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.78.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-13 07:37:05 +00:00
Daniel O'Connor
4dcafdb117 Merge pull request #4092 from Growstuff/fix-ci3
Drop simplecov for now
2025-07-13 16:44:29 +09:30
Daniel O'Connor
bd132c32fa Drop simplecov for now 2025-07-13 07:04:02 +00:00
Daniel O'Connor
2b5ed05628 Merge pull request #4087 from Growstuff/remove-codeclimate
Yeet codeclimate into the sun
2025-07-13 15:56:00 +09:30
Daniel O'Connor
df9f8445ad Yeet codeclimate into the sun 2025-07-13 06:22:31 +00:00
dependabot[bot]
534d493693 Bump bullet from 8.0.7 to 8.0.8
Bumps [bullet](https://github.com/flyerhzm/bullet) from 8.0.7 to 8.0.8.
- [Changelog](https://github.com/flyerhzm/bullet/blob/main/CHANGELOG.md)
- [Commits](https://github.com/flyerhzm/bullet/compare/8.0.7...8.0.8)

---
updated-dependencies:
- dependency-name: bullet
  dependency-version: 8.0.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 09:27:09 +00:00
dependabot[bot]
3bfa1af67b Merge pull request #4060 from Growstuff/dependabot/bundler/jquery-ui-rails-8.0.0 2025-06-09 09:26:01 +00:00
dependabot[bot]
e5e0aacfeb Bump jquery-ui-rails from 413265e to 8.0.0
Bumps [jquery-ui-rails](https://github.com/jquery-ui-rails/jquery-ui-rails) from `413265e` to 8.0.0. This release includes the previously tagged commit.
- [Release notes](https://github.com/jquery-ui-rails/jquery-ui-rails/releases)
- [Commits](413265e81f...v8.0.0)

---
updated-dependencies:
- dependency-name: jquery-ui-rails
  dependency-version: 8.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 09:03:05 +00:00
dependabot[bot]
962cf0bca7 Merge pull request #4068 from Growstuff/dependabot/bundler/rubocop-1.76.0 2025-06-09 09:01:37 +00:00
dependabot[bot]
419ee298ff Bump rubocop from 1.75.5 to 1.76.0
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.75.5 to 1.76.0.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.75.5...v1.76.0)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.76.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-04 07:03:20 +00:00
62 changed files with 833 additions and 1811 deletions

View File

@@ -1,47 +0,0 @@
plugins:
brakeman:
enabled: false # codeclimate's brakeman is stuck in rails 5 rules
bundler-audit:
enabled: true
coffeelint:
enabled: true
duplication:
enabled: true
config:
languages:
- ruby
- javascript
editorconfig:
enabled: true
eslint:
enabled: true
fixme:
enabled: true
haml-lint:
enabled: true
nodesecurity:
enabled: true
rubocop:
enabled: true
channel: "rubocop-1-11"
scss-lint:
enabled: true
shellcheck:
enabled: true
ratings:
paths:
- "**.rb"
- "**.ru"
- "**.js"
- "**.coffee"
- "**.scss"
- "**.haml"
- Gemfile.lock
exclude_paths:
- config/
- db/
- spec/
- public/
- app/assets/stylesheets/bootstrap-accessibility.css
- app/assets/javascripts/bootstrap*
- app/assets/stylesheets/leaflet_overrides.scss

102
.github/workflows/ci-features-places.yml vendored Normal file
View File

@@ -0,0 +1,102 @@
name: CI Features - Admin
on: [pull_request]
jobs:
rspec:
runs-on: ubuntu-latest
services:
db:
image: postgres
env:
##
# The Postgres service fails its docker health check unless you
# specify these environment variables
#
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: growstuff_test
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
APP_DOMAIN_NAME: localhost:3000
APP_PROTOCOL: http
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
DATABASE_URL: postgres://postgres:postgres@localhost:5432/growstuff_test
DEVISE_SECRET_KEY: secret
ELASTIC_SEARCH_VERSION: "7.5.1-amd64"
GROWSTUFF_EMAIL: "noreply@test.growstuff.org"
GROWSTUFF_FLICKR_KEY: secretkey"
GROWSTUFF_FLICKR_SECRET: secretsecret
GROWSTUFF_SITE_NAME: "Growstuff (travis)"
RAILS_ENV: test
RAILS_SECRET_TOKEN: supersecret
steps:
- name: Checkout this repo
uses: actions/checkout@v4
- name: Configure sysctl limits
run: |
sudo swapoff -a
sudo sysctl -w vm.swappiness=1
sudo sysctl -w fs.file-max=262144
sudo sysctl -w vm.max_map_count=262144
- name: Start Elasticsearch
uses: elastic/elastic-github-actions/elasticsearch@master
with:
stack-version: 7.5.1
##
# Cache Yarn modules
#
# See https://github.com/actions/cache/blob/master/examples.md#node---yarn for details
#
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Setup yarn cache
uses: actions/cache@v4
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install required OS packages
run: |
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v4
with:
node-version: '12'
- name: Install Ruby (version given by .ruby-version) and Bundler
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Install required JS packages
run: yarn install
- name: install chrome
run: sudo apt-get install google-chrome-stable
- name: Prepare database for testing
run: bundle exec rails db:prepare
- name: precompile assets
run: bundle exec rails assets:precompile
- name: index into elastic search
run: bundle exec rails search:reindex
- name: Run rspec (places/)
run: bundle exec rspec spec/features/places/ -fd

102
.github/workflows/ci-features-posts.yml vendored Normal file
View File

@@ -0,0 +1,102 @@
name: CI Features - Admin
on: [pull_request]
jobs:
rspec:
runs-on: ubuntu-latest
services:
db:
image: postgres
env:
##
# The Postgres service fails its docker health check unless you
# specify these environment variables
#
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: growstuff_test
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
APP_DOMAIN_NAME: localhost:3000
APP_PROTOCOL: http
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
DATABASE_URL: postgres://postgres:postgres@localhost:5432/growstuff_test
DEVISE_SECRET_KEY: secret
ELASTIC_SEARCH_VERSION: "7.5.1-amd64"
GROWSTUFF_EMAIL: "noreply@test.growstuff.org"
GROWSTUFF_FLICKR_KEY: secretkey"
GROWSTUFF_FLICKR_SECRET: secretsecret
GROWSTUFF_SITE_NAME: "Growstuff (travis)"
RAILS_ENV: test
RAILS_SECRET_TOKEN: supersecret
steps:
- name: Checkout this repo
uses: actions/checkout@v4
- name: Configure sysctl limits
run: |
sudo swapoff -a
sudo sysctl -w vm.swappiness=1
sudo sysctl -w fs.file-max=262144
sudo sysctl -w vm.max_map_count=262144
- name: Start Elasticsearch
uses: elastic/elastic-github-actions/elasticsearch@master
with:
stack-version: 7.5.1
##
# Cache Yarn modules
#
# See https://github.com/actions/cache/blob/master/examples.md#node---yarn for details
#
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Setup yarn cache
uses: actions/cache@v4
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install required OS packages
run: |
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v4
with:
node-version: '12'
- name: Install Ruby (version given by .ruby-version) and Bundler
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Install required JS packages
run: yarn install
- name: install chrome
run: sudo apt-get install google-chrome-stable
- name: Prepare database for testing
run: bundle exec rails db:prepare
- name: precompile assets
run: bundle exec rails assets:precompile
- name: index into elastic search
run: bundle exec rails search:reindex
- name: Run rspec (posts/)
run: bundle exec rspec spec/features/posts/ -fd

View File

@@ -107,19 +107,5 @@ jobs:
- name: Run rspec (photos/)
run: bundle exec rspec spec/features/photos/ -fd
- name: Run rspec (places/)
run: bundle exec rspec spec/features/places/ -fd
- name: Run rspec (plantings/)
run: bundle exec rspec spec/features/plantings/ -fd
- name: Run rspec (posts/)
run: bundle exec rspec spec/features/posts/ -fd
- name: Run rspec (rss/)
run: bundle exec rspec spec/features/rss/ -fd
- name: Report to code climate
run: |
gem install codeclimate-test-reporter
codeclimate-test-reporter
run: bundle exec rspec spec/features/rss/ -fd

View File

@@ -127,9 +127,4 @@ jobs:
run: bundle exec rspec spec/routing/ -fd --fail-fast
- name: Run rspec (request)
run: bundle exec rspec spec/requests/ -fd --fail-fast
- name: Report to code climate
run: |
gem install codeclimate-test-reporter
codeclimate-test-reporter
run: bundle exec rspec spec/requests/ -fd --fail-fast

View File

@@ -1 +1 @@
3.3.7
3.3.8

View File

@@ -175,6 +175,7 @@ group :development, :test do
gem 'rubocop-rspec_rails'
gem 'webrat' # provides HTML matchers for view tests
gem 'crowdin-cli' # for translations
gem 'dotenv-rails'
# cli utils
@@ -188,15 +189,16 @@ end
group :test do
gem 'axe-core-capybara'
gem 'axe-core-rspec'
gem 'codeclimate-test-reporter', require: false
gem 'rails-controller-testing'
gem 'selenium-webdriver'
gem 'timecop'
gem 'vcr'
gem "rspec-rebound"
gem "percy-capybara", "~> 5.0.0"
end
group :travis do
gem 'platform-api'
end
gem "percy-capybara", "~> 5.0.0"

View File

@@ -33,29 +33,29 @@ GEM
GEM
remote: https://rubygems.org/
specs:
actioncable (7.2.2.1)
actionpack (= 7.2.2.1)
activesupport (= 7.2.2.1)
actioncable (7.2.2.2)
actionpack (= 7.2.2.2)
activesupport (= 7.2.2.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.2.2.1)
actionpack (= 7.2.2.1)
activejob (= 7.2.2.1)
activerecord (= 7.2.2.1)
activestorage (= 7.2.2.1)
activesupport (= 7.2.2.1)
actionmailbox (7.2.2.2)
actionpack (= 7.2.2.2)
activejob (= 7.2.2.2)
activerecord (= 7.2.2.2)
activestorage (= 7.2.2.2)
activesupport (= 7.2.2.2)
mail (>= 2.8.0)
actionmailer (7.2.2.1)
actionpack (= 7.2.2.1)
actionview (= 7.2.2.1)
activejob (= 7.2.2.1)
activesupport (= 7.2.2.1)
actionmailer (7.2.2.2)
actionpack (= 7.2.2.2)
actionview (= 7.2.2.2)
activejob (= 7.2.2.2)
activesupport (= 7.2.2.2)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (7.2.2.1)
actionview (= 7.2.2.1)
activesupport (= 7.2.2.1)
actionpack (7.2.2.2)
actionview (= 7.2.2.2)
activesupport (= 7.2.2.2)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4, < 3.2)
@@ -64,15 +64,15 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (7.2.2.1)
actionpack (= 7.2.2.1)
activerecord (= 7.2.2.1)
activestorage (= 7.2.2.1)
activesupport (= 7.2.2.1)
actiontext (7.2.2.2)
actionpack (= 7.2.2.2)
activerecord (= 7.2.2.2)
activestorage (= 7.2.2.2)
activesupport (= 7.2.2.2)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.2.2.1)
activesupport (= 7.2.2.1)
actionview (7.2.2.2)
activesupport (= 7.2.2.2)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
@@ -87,22 +87,22 @@ GEM
active_utils (3.5.0)
activesupport (>= 4.2)
i18n
activejob (7.2.2.1)
activesupport (= 7.2.2.1)
activejob (7.2.2.2)
activesupport (= 7.2.2.2)
globalid (>= 0.3.6)
activemodel (7.2.2.1)
activesupport (= 7.2.2.1)
activerecord (7.2.2.1)
activemodel (= 7.2.2.1)
activesupport (= 7.2.2.1)
activemodel (7.2.2.2)
activesupport (= 7.2.2.2)
activerecord (7.2.2.2)
activemodel (= 7.2.2.2)
activesupport (= 7.2.2.2)
timeout (>= 0.4.0)
activestorage (7.2.2.1)
actionpack (= 7.2.2.1)
activejob (= 7.2.2.1)
activerecord (= 7.2.2.1)
activesupport (= 7.2.2.1)
activestorage (7.2.2.2)
actionpack (= 7.2.2.2)
activejob (= 7.2.2.2)
activerecord (= 7.2.2.2)
activesupport (= 7.2.2.2)
marcel (~> 1.0)
activesupport (7.2.2.1)
activesupport (7.2.2.2)
base64
benchmark (>= 0.3)
bigdecimal
@@ -135,14 +135,14 @@ GEM
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
base64 (0.2.0)
base64 (0.3.0)
bcrypt (3.1.20)
benchmark (0.4.0)
benchmark (0.4.1)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
rouge (>= 1.0.0)
bigdecimal (3.1.9)
bigdecimal (3.2.2)
bluecloth (2.2.0)
bonsai-elasticsearch-rails (7.0.1)
elasticsearch-model (< 8)
@@ -156,7 +156,7 @@ GEM
actionpack (>= 6.1)
activemodel (>= 6.1)
builder (3.3.0)
bullet (8.0.7)
bullet (8.0.8)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (12.0.0)
@@ -185,8 +185,6 @@ GEM
ssrf_filter (~> 1.0)
chartkick (5.1.5)
childprocess (5.0.0)
codeclimate-test-reporter (1.0.9)
simplecov (<= 0.13)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
@@ -202,6 +200,14 @@ GEM
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
crass (1.0.6)
crowdin-api (1.12.0)
open-uri (>= 0.1.0, < 0.2.0)
rest-client (>= 2.0.0, < 2.2.0)
crowdin-cli (0.2.2)
crowdin-api (>= 0.2.0)
gli (>= 2.7.0)
i18n (>= 0.6.4)
rubyzip (>= 1.0.0)
csv (3.3.1)
csv_shaper (1.4.0)
activesupport (>= 3.0.0)
@@ -222,15 +228,15 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
diff-lcs (1.6.1)
diff-lcs (1.6.2)
discard (1.4.0)
activerecord (>= 4.2, < 9.0)
docile (1.1.5)
domain_name (0.6.20240107)
dotenv (3.1.8)
dotenv-rails (3.1.8)
dotenv (= 3.1.8)
railties (>= 6.1)
drb (2.2.1)
drb (2.2.3)
dumb_delegator (1.1.0)
elasticsearch (7.0.0)
elasticsearch-api (= 7.0.0)
@@ -245,23 +251,24 @@ GEM
elasticsearch-transport (7.0.0)
faraday
multi_json
erb (5.0.2)
erubi (1.13.1)
erubis (2.7.0)
excon (1.2.5)
logger
execjs (2.10.0)
factory_bot (6.5.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.4)
factory_bot (6.5.4)
activesupport (>= 6.1.0)
factory_bot_rails (6.5.0)
factory_bot (~> 6.5)
railties (>= 5.0.0)
faker (3.5.1)
railties (>= 6.1.0)
faker (3.5.2)
i18n (>= 1.8.11, < 2)
faraday (2.13.1)
faraday (2.13.4)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-net_http (3.4.0)
faraday-net_http (3.4.1)
net-http (>= 0.5.0)
ffi (1.16.3)
flickraw (0.9.10)
@@ -276,6 +283,8 @@ GEM
gibbon (1.2.1)
httparty
multi_json (>= 1.9.0)
gli (2.22.2)
ostruct
globalid (1.2.1)
activesupport (>= 6.1)
gravatar-ultimate (2.0.0)
@@ -296,7 +305,7 @@ GEM
activesupport (>= 5.1)
haml (>= 4.0.6)
railties (>= 5.1)
haml_lint (0.62.0)
haml_lint (0.66.0)
haml (>= 5.0)
parallel (~> 1.10)
rainbow
@@ -312,6 +321,9 @@ GEM
webrick
highline (3.1.2)
reline
http-accept (1.7.0)
http-cookie (1.0.8)
domain_name (~> 0.5)
httparty (0.22.0)
csv
mini_mime (>= 1.0.0)
@@ -329,7 +341,7 @@ GEM
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.8, >= 1.8.1)
terminal-table (>= 1.5.1)
icalendar (2.11.0)
icalendar (2.11.2)
base64
ice_cube (~> 0.16)
logger
@@ -339,7 +351,7 @@ GEM
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
io-console (0.8.0)
io-console (0.8.1)
irb (1.15.2)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
@@ -348,7 +360,7 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.12.0)
json (2.13.2)
json-schema (5.1.0)
addressable (~> 2.8)
jsonapi-resources (0.10.7)
@@ -393,6 +405,10 @@ GEM
matrix (0.4.2)
memcachier (0.0.2)
method_source (1.1.0)
mime-types (3.7.0)
logger
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2025.0805)
mimemagic (0.4.3)
nokogiri (~> 1)
rake
@@ -407,20 +423,21 @@ GEM
bigdecimal (~> 3.1)
net-http (0.6.0)
uri
net-imap (0.4.20)
net-imap (0.5.9)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.5.0)
net-smtp (0.5.1)
net-protocol
netrc (0.11.0)
nio4r (2.7.4)
nokogiri (1.18.8)
nokogiri (1.18.9)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.18.8-x86_64-linux-gnu)
nokogiri (1.18.9-x86_64-linux-gnu)
racc (~> 1.4)
oauth (0.5.6)
oj (3.16.10)
@@ -438,15 +455,17 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
open-uri (0.1.0)
orm_adapter (0.5.0)
ostruct (0.6.1)
ostruct (0.6.2)
parallel (1.27.0)
parser (3.3.8.0)
parser (3.3.9.0)
ast (~> 2.4.1)
racc
percy-capybara (5.0.0)
capybara (>= 3)
pg (1.5.9)
pg (1.6.1)
pg (1.6.1-x86_64-linux)
platform-api (3.8.0)
heroics (~> 0.1.1)
moneta (~> 1.0.0)
@@ -463,11 +482,11 @@ GEM
date
stringio
public_suffix (6.0.1)
puma (6.6.0)
puma (6.6.1)
nio4r (~> 2.0)
query_diet (0.7.2)
racc (1.8.1)
rack (2.2.15)
rack (2.2.17)
rack-cors (2.0.2)
rack (>= 2.0.0)
rack-protection (3.2.0)
@@ -480,25 +499,25 @@ GEM
rackup (1.0.1)
rack (< 3)
webrick
rails (7.2.2.1)
actioncable (= 7.2.2.1)
actionmailbox (= 7.2.2.1)
actionmailer (= 7.2.2.1)
actionpack (= 7.2.2.1)
actiontext (= 7.2.2.1)
actionview (= 7.2.2.1)
activejob (= 7.2.2.1)
activemodel (= 7.2.2.1)
activerecord (= 7.2.2.1)
activestorage (= 7.2.2.1)
activesupport (= 7.2.2.1)
rails (7.2.2.2)
actioncable (= 7.2.2.2)
actionmailbox (= 7.2.2.2)
actionmailer (= 7.2.2.2)
actionpack (= 7.2.2.2)
actiontext (= 7.2.2.2)
actionview (= 7.2.2.2)
activejob (= 7.2.2.2)
activemodel (= 7.2.2.2)
activerecord (= 7.2.2.2)
activestorage (= 7.2.2.2)
activesupport (= 7.2.2.2)
bundler (>= 1.15.0)
railties (= 7.2.2.1)
railties (= 7.2.2.2)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.2.0)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
@@ -513,9 +532,9 @@ GEM
rails_stdout_logging
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (7.2.2.1)
actionpack (= 7.2.2.1)
activesupport (= 7.2.2.1)
railties (7.2.2.2)
actionpack (= 7.2.2.2)
activesupport (= 7.2.2.2)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
@@ -523,41 +542,47 @@ GEM
zeitwerk (~> 2.6)
rainbow (3.1.1)
raindrops (0.20.1)
rake (13.2.1)
rake (13.3.0)
rate_throttle_client (0.1.2)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rdoc (6.13.1)
rdoc (6.14.2)
erb
psych (>= 4.0.0)
recaptcha (5.19.0)
recaptcha (5.20.1)
redis-client (0.23.2)
connection_pool
regexp_parser (2.10.0)
reline (0.6.1)
regexp_parser (2.11.2)
reline (0.6.2)
io-console (~> 0.5)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
rexml (3.4.1)
rouge (4.1.2)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-activemodel-mocks (1.2.1)
rspec-activemodel-mocks (1.3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
rspec-mocks (>= 2.99, < 4.0)
rspec-core (3.13.3)
rspec-core (3.13.5)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.4)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.4)
rspec-mocks (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (8.0.0)
rspec-rails (8.0.2)
actionpack (>= 7.2)
activesupport (>= 7.2)
railties (>= 7.2)
@@ -565,7 +590,9 @@ GEM
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.3)
rspec-rebound (0.2.1)
rspec-core (~> 3.3)
rspec-support (3.13.4)
rspectre (0.2.0)
parser (>= 3.3.7.1)
prism (~> 1.3)
@@ -581,7 +608,7 @@ GEM
rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rubocop (1.75.5)
rubocop (1.79.2)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -589,10 +616,10 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.44.0, < 2.0)
rubocop-ast (>= 1.46.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.44.1)
rubocop-ast (1.46.0)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-capybara (2.22.1)
@@ -601,7 +628,7 @@ GEM
rubocop-factory_bot (2.27.1)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rails (2.32.0)
rubocop-rails (2.33.3)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
@@ -621,7 +648,7 @@ GEM
ruby-units (4.1.0)
ruby-vips (2.2.1)
ffi (~> 1.12)
rubyzip (2.4.1)
rubyzip (3.0.1)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
@@ -635,17 +662,17 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
scout_apm (5.6.4)
scout_apm (5.7.0)
parser
searchkick (5.3.1)
activemodel (>= 6.1)
hashie
securerandom (0.4.1)
selenium-webdriver (4.32.0)
selenium-webdriver (4.35.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
rubyzip (>= 1.2.2, < 4.0)
websocket (~> 1.0)
sidekiq (7.3.9)
base64
@@ -653,11 +680,6 @@ GEM
logger
rack (>= 2.2.4)
redis-client (>= 0.22.2)
simplecov (0.13.0)
docile (~> 1.1.0)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
sprockets (3.7.5)
base64
concurrent-ruby (~> 1.0)
@@ -669,14 +691,14 @@ GEM
ssrf_filter (1.1.2)
stringio (3.1.7)
sysexits (1.2.0)
temple (0.10.3)
temple (0.10.4)
terminal-table (4.0.0)
unicode-display_width (>= 1.1.1, < 4)
terser (1.2.5)
execjs (>= 0.3.0, < 3)
thor (1.3.2)
thor (1.4.0)
thread_safe (0.3.6)
tilt (2.6.0)
tilt (2.6.1)
timecop (0.9.10)
timeout (0.4.3)
trollop (1.16.2)
@@ -708,7 +730,8 @@ GEM
rack-test (>= 0.5.3)
webrick (1.9.1)
websocket (1.2.11)
websocket-driver (0.7.6)
websocket-driver (0.8.0)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
will_paginate (4.0.1)
@@ -718,7 +741,7 @@ GEM
webrick
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.7.2)
zeitwerk (2.7.3)
PLATFORMS
ruby
@@ -744,9 +767,9 @@ DEPENDENCIES
capybara-email
capybara-screenshot
chartkick
codeclimate-test-reporter
coffee-rails
comfortable_mexican_sofa!
crowdin-cli
csv_shaper
dalli
database_cleaner
@@ -805,6 +828,7 @@ DEPENDENCIES
responders
rspec-activemodel-mocks
rspec-rails
rspec-rebound
rspectre
rswag-api
rswag-specs
@@ -834,7 +858,7 @@ DEPENDENCIES
xmlrpc
RUBY VERSION
ruby 3.3.7p123
ruby 3.3.8p144
BUNDLED WITH
2.4.22

View File

@@ -1,7 +1,6 @@
# 🌱 Growstuff
![Build status](https://github.com/Growstuff/growstuff/workflows/CI/badge.svg)
[![Code Climate](https://codeclimate.com/github/Growstuff/growstuff/badges/gpa.svg)](https://codeclimate.com/github/Growstuff/growstuff)
Welcome to the Growstuff project.

View File

@@ -30,3 +30,7 @@
@import "predictions";
@import "homepage";
@import "maps";
@view-transition {
navigation: auto;
}

View File

@@ -24,9 +24,6 @@ class ActivitiesController < DataController
end
def show
# @activity is loaded by load_and_authorize_resource.
# We need to ensure comments are eager-loaded.
@activity = Activity.includes(comments: :author).find(params[:id])
respond_with @activity
end

View File

@@ -78,6 +78,7 @@ class ApplicationController < ActionController::Base
:tos_agreement,
# profile stuff
:bio, :location, :latitude, :longitude,
:website_url, :instagram_handle, :facebook_handle, :bluesky_handle, :other_url,
# email settings
:show_email, :newsletter, :send_notification_email, :send_planting_reminder,
# update password

View File

@@ -13,66 +13,43 @@ class CommentsController < ApplicationController
end
def new
@commentable = find_commentable
if @commentable
@comment = @commentable.comments.new
@comments = @commentable.comments.post_order # Assuming post_order is generic enough or will be adapted
respond_with(@comment) # Changed from @comments to @comment, or @commentable
@comment = Comment.new
@post = Post.find_by(id: params[:post_id])
if @post
@comments = @post.comments
respond_with(@comments)
else
redirect_to(request.referer || root_url,
alert: "Cannot add a comment to a non-existent or unspecified item.")
alert: "Can't post a comment on a non-existent post")
end
end
def edit
# @comment is loaded by load_and_authorize_resource
@comments = @comment.commentable.comments.post_order # Assuming post_order is generic
@comments = @comment.post.comments
end
def create
@commentable = find_commentable
if @commentable
@comment = @commentable.comments.new(comment_params)
@comment.author = current_member
@comment.save
respond_with @comment, location: @comment.commentable # Redirect to the commentable parent
else
redirect_to(request.referer || root_url,
alert: "Cannot create comment for a non-existent or unspecified item.")
end
@comment = Comment.new(comment_params)
@comment.author = current_member
@comment.save
respond_with @comment, location: @comment.post
end
def update
# @comment is loaded by load_and_authorize_resource
@comment.update(comment_params) # body is permitted by comment_params
respond_with @comment, location: @comment.commentable # Redirect to the commentable parent
@comment.update(body: comment_params['body'])
respond_with @comment, location: @comment.post
end
def destroy
# @comment is loaded by load_and_authorize_resource
@commentable = @comment.commentable # Store before destroying
@post = @comment.post
@comment.destroy
respond_with @comment, location: @commentable # Redirect to the commentable parent
respond_with(@post)
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
model_name = $1.classify
# Ensure model_name is one of the expected commentable types
# to prevent arbitrary model lookups.
allowed_commentables = %w[Post Photo Planting Harvest Activity]
if allowed_commentables.include?(model_name)
return model_name.constantize.find_by(id: value)
end
end
end
nil
end
def comment_params
params.require(:comment).permit(:body) # Removed post_id
params.require(:comment).permit(:body, :post_id)
end
end

View File

@@ -32,9 +32,6 @@ class HarvestsController < DataController
end
def show
# @harvest is loaded by load_and_authorize_resource.
# We need to ensure comments are eager-loaded.
@harvest = Harvest.includes(comments: :author).find(params[:id])
@matching_plantings = matching_plantings if @harvest.owner == current_member
@photos = @harvest.photos.order(created_at: :desc).paginate(page: params[:page])
respond_with(@harvest)

View File

@@ -20,9 +20,6 @@ class PhotosController < ApplicationController
end
def show
# @photo is loaded by load_and_authorize_resource.
# We need to ensure comments are eager-loaded.
@photo = Photo.includes(comments: :author).find(params[:id])
@crops = Crop.distinct.joins(:photo_associations).where(photo_associations: { photo: @photo })
respond_with(@photo)
end

View File

@@ -34,9 +34,6 @@ class PlantingsController < DataController
end
def show
# @planting is loaded by load_and_authorize_resource.
# We need to ensure comments are eager-loaded.
@planting = Planting.includes(comments: :author).find(params[:id])
@photos = @planting.photos.includes(:owner).order(date_taken: :desc)
@harvests = Harvest.search(where: { planting_id: @planting.id })
@current_activities = @planting.activities.current.includes(:owner).order(created_at: :desc)

View File

@@ -19,6 +19,10 @@ class SeedsController < DataController
where['parent_planting'] = @planting.id
end
if params[:tradeable_to].present?
where['tradeable_to'] = params[:tradeable_to]
end
@show_all = (params[:all] == '1')
where['finished'] = false unless @show_all

View File

@@ -98,19 +98,7 @@ class Ability
can :destroy, Like, member_id: member.id
can :create, Comment
can :update, Comment, author_id: member.id
can :destroy, Comment do |comment|
is_author = comment.author_id == member.id
is_commentable_owner = false
if comment.commentable.present?
if comment.commentable.respond_to?(:owner_id) && comment.commentable.owner_id == member.id
is_commentable_owner = true
# Posts use author_id for their "owner"
elsif comment.commentable.respond_to?(:author_id) && comment.commentable.author_id == member.id
is_commentable_owner = true
end
end
is_author || is_commentable_owner
end
can :destroy, Comment, author_id: member.id
# same deal for gardens and plantings
can :create, Garden

View File

@@ -2,31 +2,26 @@
class Comment < ApplicationRecord
belongs_to :author, class_name: 'Member', inverse_of: :comments
belongs_to :commentable, polymorphic: true, counter_cache: true
belongs_to :post, counter_cache: true
scope :post_order, -> { order(created_at: :asc) } # for display on post page
after_create do
recipient = if commentable.respond_to?(:author)
commentable.author.id
elsif commentable.respond_to?(:owner)
commentable.owner.id
end
recipient = post.author.id
sender = author.id
# don't send notifications to yourself
if recipient && recipient != sender
if recipient != sender
Notification.create(
recipient_id: recipient,
sender_id: sender,
subject: "#{author} commented on your #{commentable.class.name.downcase}",
subject: "#{author} commented on #{post.subject}",
body:,
commentable_id: commentable.id,
commentable_type: commentable.class.name
post_id: post.id
)
end
end
def to_s
"#{author.login_name} commented on #{commentable.class.name.downcase} ##{commentable.id}"
"#{author.login_name} commented on #{post.subject}"
end
end

View File

@@ -91,6 +91,9 @@ class Member < ApplicationRecord
uniqueness: {
case_sensitive: false
}
validates :website_url, format: { with: /\Ahttps?:\/\//, message: "must start with http:// or https://" }, allow_blank: true
validates :other_url, format: { with: /\Ahttps?:\/\//, message: "must start with http:// or https://" }, allow_blank: true
validates :instagram_handle, :facebook_handle, :bluesky_handle, format: { without: %r{\Ahttps?:\/\/|\/}, message: "should be a handle, not a URL" }, allow_blank: true
#
# Triggers

View File

@@ -3,7 +3,7 @@
class Notification < ApplicationRecord
belongs_to :sender, class_name: 'Member', inverse_of: :sent_notifications
belongs_to :recipient, class_name: 'Member', inverse_of: :notifications
belongs_to :commentable, polymorphic: true, optional: true
belongs_to :post, optional: true
validates :subject, length: { maximum: 255 }

View File

@@ -44,8 +44,5 @@
%a{name: 'plantings'}
= render 'plantings/card', planting: @activity.planting
%section.comments.mt-4
= render 'comments/comments', commentable: @activity
.col-md-4.col-xs-12

View File

@@ -1,21 +0,0 @@
.comments-section.mt-4
%h4.mb-3 Comments
- if commentable.comments.any?
.list-group
- commentable.comments.post_order.each do |comment|
.list-group-item.p-0.mb-2.border-0
= render 'comments/comment', comment: comment
- else
%p No comments yet.
- if can? :create, Comment # Assuming a general ability to create comments
.mt-3
= link_to "Add Comment", new_polymorphic_path([commentable, Comment.new]), class: 'btn btn-primary'
%hr/
-# This section is for rendering the new/edit form directly on the page if needed,
-# but the primary "Add Comment" link above goes to the comments/new page.
-# If @new_comment is passed, it means we want to show the form.
- if defined?(@new_comment) && @new_comment && can?(:create, Comment) # Check @new_comment specifically
%h5.mt-3 Leave a comment
= render 'comments/form', commentable: commentable, comment: @new_comment

View File

@@ -3,14 +3,14 @@
- if content_for? :title
%h1.h2-responsive.text-center
%strong=yield :title
= form_for(commentable ? [commentable, comment] : comment, html: { class: "form-horizontal" }) do |f|
- if comment.errors.any?
= form_for(@comment, html: { class: "form-horizontal" }) do |f|
- if @comment.errors.any?
#error_explanation
%h2
= pluralize(comment.errors.size, "error")
= pluralize(@comment.errors.size, "error")
prohibited this comment from being saved:
%ul
- comment.errors.full_messages.each do |msg|
- @comment.errors.full_messages.each do |msg|
%li= msg
.md-form
@@ -21,3 +21,6 @@
= render partial: "shared/markdown_help"
.actions.text-right
= f.submit 'Post comment', class: 'btn btn-primary'
- if defined?(@post)
.field
= f.hidden_field :post_id, value: @post.id

View File

@@ -1,2 +1,7 @@
%h2 Edit Comment
= render 'comments/form', commentable: @comment.commentable, comment: @comment
= content_for :title, "Editing comment"
%p
Editing comment on
= link_to @comment.post.subject, @comment.post
= render 'form'

View File

@@ -1,5 +1,11 @@
%h2
Add comment to
= @commentable.class.name.downcase
= content_for :title, "New comment"
= render 'comments/form', commentable: @commentable, comment: @comment
%section.blog-post
.card.post{ id: "post-#{@post.id}" }
.card-header
%h2.display-3= @post.subject
.card-body= render "posts/single", post: @post || @comment.post, subject: true
= render partial: "posts/comments", locals: { post: @post || @comment.post }
= render 'form'

View File

@@ -13,7 +13,32 @@
.form-group
= f.label :bio, class: 'control-label col-md-2'
.col-md-8
= f.text_area :bio, rows: 6, class: 'form-control'
= f.text_area :bio, rows: 6, class: 'form-control', placeholder: "I'm am XYZ gardener interested in A, B, C"
.form-group
= f.label :website_url, 'Website', class: 'control-label col-md-2'
.col-md-8
= f.url_field :website_url, class: 'form-control', placeholder: "https://you.example.com/"
.form-group
= f.label :instagram_handle, 'Instagram', class: 'control-label col-md-2'
.col-md-8
= f.text_field :instagram_handle, class: 'form-control', placeholder: 'your_handle'
.form-group
= f.label :facebook_handle, 'Facebook', class: 'control-label col-md-2'
.col-md-8
= f.text_field :facebook_handle, class: 'form-control', placeholder: 'your_handle'
.form-group
= f.label :bluesky_handle, 'Bluesky', class: 'control-label col-md-2'
.col-md-8
= f.text_field :bluesky_handle, class: 'form-control', placeholder: 'your_handle'
.form-group
= f.label :other_url, 'Other URL', class: 'control-label col-md-2'
.col-md-8
= f.url_field :other_url, class: 'form-control', placeholder: "https://you.example.com/"
.form-group
%label.control-label.col-md-2

View File

@@ -66,8 +66,5 @@
Havested from
= link_to @harvest.planting, @harvest.planting
%section.comments.mt-4
= render 'comments/comments', commentable: @harvest
.col-md-4.col-xs-12
= render @harvest.crop

View File

@@ -51,7 +51,8 @@
%section.seeds
= cute_icon
= render 'seeds'
%p.text-right= link_to "#{t('home.seeds.view_all')} »", seeds_path, class: 'btn btn-block'
%p.text-right
= link_to "#{t('home.seeds.view_all')} »", seeds_path(tradeable_to: ['locally', 'nationally', 'internationally']), class: 'btn btn-block'
.col-12.col-lg-6
%section.discussion.text-center
= cute_icon

View File

@@ -6,6 +6,6 @@
%span.site-name Growstuff
.nav= render 'crops/search_bar'
.nav
%button.navbar-toggler{ "aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-bs-target" => "#navbarSupportedContent", "data-bs-toggle" => "collapse", type: "button" }
%button.navbar-toggler{ "aria-controls" => "offcanvasNavbar", "aria-label" => "Toggle navigation", "data-bs-target" => "#offcanvasNavbar", "data-bs-toggle" => "offcanvas", type: "button" }
%i.fas.fa-ellipsis-v.navbar-toggler-icon
= render 'layouts/menu'

View File

@@ -1,98 +1,102 @@
#navbarSupportedContent.collapse.navbar-collapse
%ul.navbar-nav.mr-auto
- if signed_in?
%li.nav-item
= link_to timeline_index_path, method: :get, class: 'nav-link text-white' do
= image_tag 'icons/notification.svg', class: 'img img-icon', alt: "Notifications"
%li.nav-item
= link_to member_gardens_path(current_member), class: 'nav-link text-white', title: "My gardens" do
= image_icon 'gardens'
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
= image_tag "icons/gardener.svg", class: 'img img-icon', alt: t('.record'), aria: { hidden: "true" }
= t('.record')
.dropdown-menu
= link_to new_planting_path, class: 'dropdown-item' do
= image_icon('planting-add')
= t('buttons.new_planting')
= link_to new_harvest_path, class: 'dropdown-item' do
= image_icon('harvest-add')
= t('buttons.new_harvest')
= link_to new_activity_path, class: 'dropdown-item' do
= image_icon('activity-add')
= t('buttons.new_activity')
= link_to new_seed_path, class: 'dropdown-item' do
= image_icon('seed-add')
= t('buttons.new_seeds')
= link_to new_post_path, class: 'dropdown-item' do
= post_icon
= t('buttons.new_post')
- cache("everyone-menu", expires_in: 1.week) do
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.crops')
.dropdown-menu
= link_to crops_path, class: 'dropdown-item' do
= t('.browse_crops')
= link_to seeds_path, class: 'dropdown-item' do
= seed_icon
= t('.seeds')
= link_to plantings_path, class: 'dropdown-item' do
= planting_icon
= t('.plantings')
= link_to harvests_path, class: 'dropdown-item' do
= harvest_icon
= t('.harvests')
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.community')
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
= link_to t('.community_map'), places_path, class: 'dropdown-item'
= link_to t('.browse_members'), members_path, class: 'dropdown-item'
= link_to t('.posts'), posts_path, class: 'dropdown-item'
= link_to t('.forums'), forums_path, class: 'dropdown-item'
- if member_signed_in?
- if current_member.role?(:crop_wrangler) || current_member.role?(:admin)
.offcanvas.offcanvas-start#offcanvasNavbar{ 'aria-labelledby' => 'offcanvasNavbarLabel', 'tabindex' => '-1' }
.offcanvas-header
%h5.offcanvas-title#offcanvasNavbarLabel Menu
%button.btn-close.text-reset{ 'data-bs-dismiss' => 'offcanvas', 'aria-label' => 'Close' }
.offcanvas-body
%ul.navbar-nav.mr-auto
- if signed_in?
%li.nav-item
= link_to timeline_index_path, method: :get, class: 'nav-link' do
= image_tag 'icons/notification.svg', class: 'img img-icon', alt: "Notifications"
%li.nav-item
= link_to member_gardens_path(current_member), class: 'nav-link', title: "My gardens" do
= image_icon 'gardens'
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.admin')
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
= image_tag "icons/gardener.svg", class: 'img img-icon', alt: t('.record'), aria: { hidden: "true" }
= t('.record')
.dropdown-menu
= link_to new_planting_path, class: 'dropdown-item' do
= image_icon('planting-add')
= t('buttons.new_planting')
= link_to new_harvest_path, class: 'dropdown-item' do
= image_icon('harvest-add')
= t('buttons.new_harvest')
= link_to new_activity_path, class: 'dropdown-item' do
= image_icon('activity-add')
= t('buttons.new_activity')
= link_to new_seed_path, class: 'dropdown-item' do
= image_icon('seed-add')
= t('buttons.new_seeds')
= link_to new_post_path, class: 'dropdown-item' do
= post_icon
= t('buttons.new_post')
- cache("everyone-menu", expires_in: 1.week) do
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.crops')
.dropdown-menu
= link_to crops_path, class: 'dropdown-item' do
= t('.browse_crops')
= link_to seeds_path, class: 'dropdown-item' do
= seed_icon
= t('.seeds')
= link_to plantings_path, class: 'dropdown-item' do
= planting_icon
= t('.plantings')
= link_to harvests_path, class: 'dropdown-item' do
= harvest_icon
= t('.harvests')
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.community')
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
- if current_member.role?(:crop_wrangler)
= link_to t('.crop_wrangling'), wrangle_crops_path, class: 'dropdown-item'
- if current_member.role?(:admin)
= link_to t('.admin'), admin_path, class: 'dropdown-item'
= link_to t('.community_map'), places_path, class: 'dropdown-item'
= link_to t('.browse_members'), members_path, class: 'dropdown-item'
= link_to t('.posts'), posts_path, class: 'dropdown-item'
= link_to t('.forums'), forums_path, class: 'dropdown-item'
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
= image_tag(avatar_uri(current_member, 50), alt: 'Avatar of current member', height: 25, width: 25, aria: { hidden: "true" })
= current_member.login_name
- if current_member.unread_count.positive?
%span.badge.badge-info= current_member.unread_count
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
= link_to member_path(current_member), class: 'dropdown-item' do
= t('.profile')
= link_to member_activities_path(current_member), class: 'dropdown-item' do
= t('.activities')
= link_to member_gardens_path(current_member), class: 'dropdown-item' do
= t('.gardens')
= link_to member_plantings_path(current_member), class: 'dropdown-item' do
= t('.plantings')
= link_to member_harvests_path(current_member), class: 'dropdown-item' do
= t('.harvest')
= link_to member_seeds_path(current_member), class: 'dropdown-item' do
= t('.seeds')
= link_to t('.posts'), member_posts_path(current_member), class: 'dropdown-item'
- if member_signed_in?
- if current_member.role?(:crop_wrangler) || current_member.role?(:admin)
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.admin')
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
- if current_member.role?(:crop_wrangler)
= link_to t('.crop_wrangling'), wrangle_crops_path, class: 'dropdown-item'
- if current_member.role?(:admin)
= link_to t('.admin'), admin_path, class: 'dropdown-item'
- if current_member.unread_count.positive?
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
= image_tag(avatar_uri(current_member, 50), alt: 'Avatar of current member', height: 25, width: 25, aria: { hidden: "true" })
= current_member.login_name
- if current_member.unread_count.positive?
%span.badge.badge-info= current_member.unread_count
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
= link_to member_path(current_member), class: 'dropdown-item' do
= t('.profile')
= link_to member_activities_path(current_member), class: 'dropdown-item' do
= t('.activities')
= link_to member_gardens_path(current_member), class: 'dropdown-item' do
= t('.gardens')
= link_to member_plantings_path(current_member), class: 'dropdown-item' do
= t('.plantings')
= link_to member_harvests_path(current_member), class: 'dropdown-item' do
= t('.harvest')
= link_to member_seeds_path(current_member), class: 'dropdown-item' do
= t('.seeds')
= link_to t('.posts'), member_posts_path(current_member), class: 'dropdown-item'
- if current_member.unread_count.positive?
.dropdown-divider
%strong
= link_to(conversations_path, class: 'dropdown-item') do
= t('.inbox')
%span.badge.badge-info= current_member.unread_count
- else
= link_to t('.inbox'), conversations_path, class: 'dropdown-item'
.dropdown-divider
%strong
= link_to(conversations_path, class: 'dropdown-item') do
= t('.inbox')
%span.badge.badge-info= current_member.unread_count
- else
= link_to t('.inbox'), conversations_path, class: 'dropdown-item'
.dropdown-divider
= link_to t('.sign_out'), destroy_member_session_path, method: :delete, class: 'dropdown-item'
= link_to t('.sign_out'), destroy_member_session_path, method: :delete, class: 'dropdown-item'
- else
%li.nav-item= link_to t('.sign_in'), new_member_session_path, id: 'navbar-signin', class: 'btn btn-signin'
%li.nav-item= link_to t('.sign_up'), new_member_registration_path, id: 'navbar-signup', class: 'btn btn-signup'
- else
%li.nav-item= link_to t('.sign_in'), new_member_session_path, id: 'navbar-signin', class: 'btn btn-signin'
%li.nav-item= link_to t('.sign_up'), new_member_registration_path, id: 'navbar-signup', class: 'btn btn-signup'

View File

@@ -1,17 +1,42 @@
- if twitter_auth || flickr_auth || member.show_email
- if member.website_url.present? || member.instagram_handle.present? || member.facebook_handle.present? || member.bluesky_handle.present? || member.other_url.present? || twitter_auth || flickr_auth || member.show_email
%h4 Contact
- if member.website_url.present?
%p
= icon 'fas', 'globe', class: 'fa-fw'
= link_to "Website", member.website_url, target: '_blank', rel: 'noopener noreferrer'
- if member.instagram_handle.present?
%p
= icon 'fab', 'instagram', class: 'fa-fw'
= link_to member.instagram_handle, "https://instagram.com/#{member.instagram_handle}", target: '_blank', rel: 'noopener noreferrer'
- if member.facebook_handle.present?
%p
= icon 'fab', 'facebook', class: 'fa-fw'
= link_to member.facebook_handle, "https://facebook.com/#{member.facebook_handle}", target: '_blank', rel: 'noopener noreferrer'
- if member.bluesky_handle.present?
%p
= icon 'fas', 'comment-dots', class: 'fa-fw'
= link_to member.bluesky_handle, "https://bsky.app/profile/#{member.bluesky_handle}", target: '_blank', rel: 'noopener noreferrer'
- if member.other_url.present?
%p
= icon 'fas', 'link', class: 'fa-fw'
= link_to "More...", member.other_url, target: '_blank', rel: 'noopener noreferrer'
- if twitter_auth
%p
= image_tag "twitter_32.png", size: "32x32", alt: 'Twitter logo'
= link_to twitter_auth.name, "https://twitter.com/#{twitter_auth.name}"
= link_to twitter_auth.name, "https://twitter.com/#{twitter_auth.name}", target: '_blank', rel: 'noopener noreferrer'
- if flickr_auth
%p
= image_tag "flickr_32.png", size: "32x32", alt: 'Flickr logo'
= link_to flickr_auth.name, "https://flickr.com/photos/#{flickr_auth.uid}"
= link_to flickr_auth.name, "https://flickr.com/photos/#{flickr_auth.uid}", target: '_blank', rel: 'noopener noreferrer'
- if member.show_email
%p
Email:
= icon 'fas', 'envelope', class: 'fa-fw'
= mail_to member.email

View File

@@ -46,8 +46,4 @@
- else
= @photo.license_name
= render "associations", photo: @photo
.row
.col-md-9
= render 'comments/comments', commentable: @photo
= render "associations", photo: @photo

View File

@@ -79,9 +79,6 @@
.col-md-12
%p Nothing is currently planned here.
%section.comments.mt-4
= render 'comments/comments', commentable: @planting
.col-md-4.col-xs-12
= render @planting.crop

View File

@@ -22,6 +22,10 @@
= render "shared/signin_signup",
to: "or to start using #{ENV['GROWSTUFF_SITE_NAME']} to track what you're planting and harvesting"
- content_for :buttonbar do
- if @post.comments.count > 10 && can?(:create, Comment)
= link_to 'Comment', new_comment_path(post_id: @post.id), class: 'btn'
- content_for :breadcrumbs do
%li.breadcrumb-item= link_to @post.author, @post.author
%li.breadcrumb-item= link_to 'posts', member_posts_path(member_slug: @post.author.slug)
@@ -43,10 +47,13 @@
.card-footer
= render 'likes/likes', object: @post
.float-right
-# Link removed as it's now part of the _comments partial
- if can? :create, Comment
= link_to new_comment_path(post_id: @post.id), class: 'btn' do
= icon 'fas', 'comment'
Comment
%section.comments
= render 'comments/comments', commentable: @post
= render "comments", post: @post
.col-md-4.col-12
= render @post.author

View File

@@ -17,6 +17,14 @@
= check_box_tag 'active', 'all', @show_all
include finished
%hr/
%section.filters
%h2 Tradeable
%ul.nav.flex-column
%li.nav-item= link_to "All tradable seeds", seeds_path(tradeable_to: ['locally', 'nationally', 'internationally'])
- Seed::TRADABLE_TO_VALUES.each do |value|
- unless value == 'nowhere'
%li.nav-item= link_to value.capitalize, seeds_path(tradeable_to: value)
%hr/
- if @owner
= render @owner
- if @crop

View File

@@ -5,7 +5,7 @@ Mailboxer.setup do |config|
config.uses_emails = true
# Configures the default from for emails sent for Messages and Notifications
config.default_from = "no-reply@growstuff.org"
config.default_from = "Growstuff <#{ENV.fetch('GROWSTUFF_EMAIL', "no-reply@growstuff.org")}>"
# Configures the methods needed by mailboxer
# config.email_method = :email

View File

@@ -0,0 +1,7 @@
en:
mailboxer:
message_mailer:
subject_new: "New Notification: %{subject}"
subject_reply: "New Reply: %{subject}"
notification_mailer:
subject: "New notification: %{subject}"

View File

@@ -28,17 +28,13 @@ Rails.application.routes.draw do
resources :photos, only: :index
end
concern :commentable do
resources :comments, only: [:new, :create, :edit, :update, :destroy], shallow: true
end
resources :gardens, concerns: :has_photos, param: :slug do
get 'timeline' => 'charts/gardens#timeline', constraints: { format: 'json' }
resources :garden_collaborators
end
resources :plantings, concerns: [:has_photos, :commentable], param: :slug do
resources :plantings, concerns: :has_photos, param: :slug do
resources :harvests
resources :seeds
collection do
@@ -51,11 +47,11 @@ Rails.application.routes.draw do
get 'crop/:crop' => 'seeds#index', as: 'seeds_by_crop', on: :collection
end
resources :harvests, concerns: [:has_photos, :commentable], param: :slug do
resources :harvests, concerns: :has_photos, param: :slug do
get 'crop/:crop' => 'harvests#index', as: 'harvests_by_crop', on: :collection
end
resources :posts, concerns: :commentable do
resources :posts do
get 'author/:author' => 'posts#index', as: 'by_author', on: :collection
end
@@ -66,7 +62,7 @@ Rails.application.routes.draw do
end
resources :alternate_names
resources :plant_parts
resources :photos, concerns: :commentable
resources :photos
resources :photo_associations, only: :destroy
@@ -116,7 +112,7 @@ Rails.application.routes.draw do
end
resources :messages
resources :activities, concerns: :commentable, param: :slug
resources :activities, param: :slug
resources :conversations do
collection do
delete 'destroy_multiple'

22
crowdin.yml Normal file
View File

@@ -0,0 +1,22 @@
# Hi there!
#
# This is a configuration file for the Crowdin CLI.
# You'll need to replace the placeholder project_id with your actual
# project ID from CrowdIn.
#
# For more information, see the Crowdin CLI documentation:
# https://support.crowdin.com/enterprise/cli-v3/
#
project_id: "your-project-id"
api_token_env: "CROWDIN_API_TOKEN"
base_path: "."
files:
- source: '/config/locales/en.yml'
translation: '/config/locales/%two_letters_code%.yml'
# The 'ignore' property is used to exclude files from processing.
# We are ignoring the existing Japanese translation file.
ignore:
- '/config/locales/ja.yml'
- source: '/config/locales/*.en.yml'
translation: '/config/locales/%file_name%.%two_letters_code%.yml'

View File

@@ -1,32 +0,0 @@
class AddCommentableToComments < ActiveRecord::Migration[6.0]
def up
add_column :comments, :commentable_id, :integer
add_column :comments, :commentable_type, :string
add_index :comments, [:commentable_type, :commentable_id]
# Data migration
execute <<-SQL
UPDATE comments
SET commentable_id = post_id,
commentable_type = 'Post'
WHERE post_id IS NOT NULL;
SQL
remove_column :comments, :post_id
end
def down
add_column :comments, :post_id, :integer
# Data migration back
execute <<-SQL
UPDATE comments
SET post_id = commentable_id
WHERE commentable_type = 'Post';
SQL
remove_index :comments, [:commentable_type, :commentable_id]
remove_column :comments, :commentable_type
remove_column :comments, :commentable_id
end
end

View File

@@ -0,0 +1,9 @@
class AddSocialMediaToMembers < ActiveRecord::Migration[6.0]
def change
add_column :members, :website_url, :string
add_column :members, :instagram_handle, :string
add_column :members, :facebook_handle, :string
add_column :members, :bluesky_handle, :string
add_column :members, :other_handle, :string
end
end

View File

@@ -0,0 +1,5 @@
class RenameOtherHandleToOtherUrlInMembers < ActiveRecord::Migration[6.0]
def change
rename_column :members, :other_handle, :other_url
end
end

View File

@@ -446,6 +446,11 @@ ActiveRecord::Schema[7.2].define(version: 2024_09_29_041435) do
t.integer "photos_count"
t.integer "forums_count"
t.integer "activities_count"
t.string "website_url"
t.string "instagram_handle"
t.string "facebook_handle"
t.string "bluesky_handle"
t.string "other_url"
t.index ["confirmation_token"], name: "index_members_on_confirmation_token", unique: true
t.index ["discarded_at"], name: "index_members_on_discarded_at"
t.index ["email"], name: "index_members_on_email", unique: true

View File

@@ -8,4 +8,18 @@ namespace :openfarm do
Rails.logger = Logger.new(STDOUT)
OpenfarmService.new.import!
end
desc "Delete all pictures with source OpenFarm"
task delete_pictures: :environment do
puts "Deleting pictures with source OpenFarm..."
photos_to_delete = Photo.where(source: 'openfarm')
count = photos_to_delete.count
photos_to_delete.each do |photo|
photo.associations.each do |photo_association|
photo_association.delete
end
photo.delete
end
puts "Deleted #{count} pictures."
end
end

View File

@@ -31,316 +31,95 @@ describe CommentsController do
end
end
# Shared examples for commentable controllers
RSpec.shared_examples "a commentable controller" do |commentable_factory_name, commentable_param_key|
let(:commentable_owner) { FactoryBot.create(:member) }
let(:comment_author) { FactoryBot.create(:member) }
let(:admin_user) { FactoryBot.create(:member, :admin) }
let!(:commentable) do
if [:post].include?(commentable_factory_name)
FactoryBot.create(commentable_factory_name, author: commentable_owner)
else
FactoryBot.create(commentable_factory_name, owner: commentable_owner)
describe "GET new" do
let(:post) { FactoryBot.create(:post) }
describe "with valid params" do
before { get :new, params: { post_id: post.id } }
let(:old_comment) { FactoryBot.create(:comment, post:) }
it "picks up post from params" do
expect(assigns(:post)).to eq(post)
end
it "assigns the old comments as @comments" do
expect(assigns(:comments)).to eq [old_comment]
end
end
describe "GET #new" do
context "when not logged in" do
before { sign_out member }
it "redirects to login" do
get :new, params: { commentable_param_key => commentable.id }
expect(response).to redirect_to(new_member_session_path)
end
end
it "dies if no post specified" do
get :new
expect(response).not_to be_successful
end
end
context "when logged in" do
before { sign_in comment_author }
it "assigns @commentable and new @comment" do
get :new, params: { commentable_param_key => commentable.id }
expect(assigns(:commentable)).to eq(commentable)
expect(assigns(:comment)).to be_a_new(Comment)
expect(response).to render_template(:new)
end
describe "GET edit" do
let(:post) { FactoryBot.create(:post) }
it "redirects if commentable not found" do
get :new, params: { commentable_param_key => -1 }
expect(response).to redirect_to(request.referer || root_url)
expect(flash[:alert]).to match(/Cannot add a comment to a non-existent or unspecified item/)
end
before { get :edit, params: { id: comment.to_param } }
describe "my comment" do
let!(:comment) { FactoryBot.create(:comment, author: member, post:) }
let!(:old_comment) { FactoryBot.create(:comment, post:, created_at: Time.zone.yesterday) }
it "assigns previous comments as @comments" do
expect(assigns(:comments)).to eq([comment, old_comment])
end
end
describe "POST #create" do
let(:valid_comment_params) { { body: "This is a great comment." } }
let(:invalid_comment_params) { { body: "" } }
describe "not my comment" do
let(:comment) { FactoryBot.create(:comment, post:) }
context "when not logged in" do
before { sign_out member }
it "redirects to login" do
post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params }
expect(response).to redirect_to(new_member_session_path)
end
end
it { expect(response).not_to be_successful }
end
end
context "when logged in" do
before { sign_in comment_author }
describe "PUT update" do
before { put :update, params: { id: comment.to_param, comment: valid_attributes } }
context "with valid params" do
it "creates a new Comment" do
expect {
post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params }
}.to change(Comment, :count).by(1)
end
describe "my comment" do
let(:comment) { FactoryBot.create(:comment, author: member) }
it "assigns the comment's author to current_member" do
post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params }
expect(Comment.last.author).to eq(comment_author)
end
it "redirects to the commentable's show page" do
post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params }
expect(response).to redirect_to(commentable)
end
end
context "with invalid params" do
it "does not create a comment" do
expect {
post :create, params: { commentable_param_key => commentable.id, comment: invalid_comment_params }
}.not_to change(Comment, :count)
end
it "re-renders the 'new' template (or commentable show with errors)" do
# The controller currently redirects if commentable is not found in create,
# but for invalid comment params, it should re-render or show errors.
# The current controller's create action saves and then responds.
# If save fails, it typically re-renders the form via respond_with.
# For this test, we'll assume it re-renders 'new' if save fails.
# A more precise test would check the response if @comment.save fails.
# For now, we'll check that it doesn't redirect to the commentable if save fails.
post :create, params: { commentable_param_key => commentable.id, comment: invalid_comment_params }
# Depending on how `respond_with` handles failure for new comment on commentable,
# it might render the commentable's show page or the comments/new template.
# The key is that it shouldn't be a successful redirect to the commentable.
# And @comment.errors should be present.
expect(assigns(:comment).errors).not_to be_empty
# Check for re-render of new or specific error handling view
# For now, checking that it's not a successful redirect if save fails.
# This might need adjustment based on actual controller error flow.
# A common pattern is to render the 'new' template again or the parent's show page.
# The `respond_with @comment, location: @comment.commentable` will try to redirect if valid.
# If invalid, it should re-render the action that led to the form.
# For `create` failing, it's often the `new` view or the parent resource's view.
# The controller has `respond_with @comment, location: @comment.commentable`.
# If `@comment` is not persisted, `respond_with` might render the `new` template by convention,
# or the template of the action (`create.js.erb` or `create.html.erb` if they exist).
# Given the setup, it's likely to re-render 'new' or the controller action's default.
# Let's assume for now the form is on the `new` page.
# This is a weak assertion. A better one would be to check for `render_template(:new)`
# if the controller is set up to do that on failure.
# However, `respond_with` is tricky. It might also render the `commentable` show page with errors.
# The `new` action in controller renders `respond_with(@comment)`.
# The `create` action has `respond_with @comment, location: @comment.commentable`.
# If `@comment.save` fails, `respond_with` will typically render the `new` template by default
# if `create.html.haml` doesn't exist, or it might try to render `commentable` show page
# with errors displayed by the form partial.
# Given the form is rendered via `comments/new`, let's assume it re-renders new.
# This part of the test may need refinement based on actual error rendering flow.
# A simple check: ensure it doesn't redirect to the commentable.
expect(response).not_to redirect_to(commentable_path(commentable))
# And that @commentable is still assigned for the form.
expect(assigns(:commentable)).to eq(commentable)
end
end
it "redirects if commentable not found" do
post :create, params: { commentable_param_key => -1, comment: valid_comment_params }
expect(response).to redirect_to(request.referer || root_url)
expect(flash[:alert]).to match(/Cannot create comment for a non-existent or unspecified item/)
end
it "redirects to the comment's post" do
expect(response).to redirect_to(comment.post)
end
end
describe "GET #edit" do
let!(:comment) { FactoryBot.create(:comment, commentable: commentable, author: comment_author) }
describe "not my comment" do
let(:comment) { FactoryBot.create(:comment) }
context "when not logged in" do
before { sign_out member }
it "redirects to login" do
get :edit, params: { id: comment.id }
expect(response).to redirect_to(new_member_session_path)
end
end
context "as comment author" do
before { sign_in comment_author }
it "assigns @comment and renders edit" do
get :edit, params: { id: comment.id }
expect(assigns(:comment)).to eq(comment)
expect(response).to render_template(:edit)
end
end
context "as admin" do
before { sign_in admin_user }
it "assigns @comment and renders edit" do
get :edit, params: { id: comment.id }
expect(assigns(:comment)).to eq(comment)
expect(response).to render_template(:edit)
end
end
context "as unauthorized user" do
let(:other_user) { FactoryBot.create(:member) }
before { sign_in other_user }
it "redirects or shows error" do
get :edit, params: { id: comment.id }
expect(response).to redirect_to(root_url) # Or some other unauthorized path
expect(flash[:alert]).to match(/You are not authorized to access this page./)
end
end
it { expect(response).not_to be_successful }
end
describe "PUT #update" do
let!(:comment) { FactoryBot.create(:comment, commentable: commentable, author: comment_author, body: "Original body") }
let(:updated_body) { "Updated comment body." }
describe "attempting to change post_id" do
let(:post) { FactoryBot.create(:post, subject: 'our post') }
let(:other_post) { FactoryBot.create(:post, subject: 'the other post') }
let(:valid_attributes) { { post_id: other_post.id, body: "kōrero" } }
let(:comment) { FactoryBot.create(:comment, author: member, post:) }
context "when not logged in" do
before { sign_out member }
it "redirects to login" do
put :update, params: { id: comment.id, comment: { body: updated_body } }
expect(response).to redirect_to(new_member_session_path)
end
end
context "as comment author" do
before { sign_in comment_author }
context "with valid params" do
it "updates the comment" do
put :update, params: { id: comment.id, comment: { body: updated_body } }
comment.reload
expect(comment.body).to eq(updated_body)
end
it "redirects to commentable show page" do
put :update, params: { id: comment.id, comment: { body: updated_body } }
expect(response).to redirect_to(commentable_path(commentable))
end
end
context "with invalid params (empty body)" do
it "does not update the comment and re-renders edit" do
put :update, params: { id: comment.id, comment: { body: "" } }
comment.reload
expect(comment.body).to eq("Original body") # Should not change
expect(response).to render_template(:edit)
end
end
end
context "as admin" do
before { sign_in admin_user }
it "updates the comment" do
put :update, params: { id: comment.id, comment: { body: updated_body } }
comment.reload
expect(comment.body).to eq(updated_body)
expect(response).to redirect_to(commentable_path(commentable))
end
end
context "as unauthorized user" do
let(:other_user) { FactoryBot.create(:member) }
before { sign_in other_user }
it "redirects or shows error" do
put :update, params: { id: comment.id, comment: { body: updated_body } }
expect(response).to redirect_to(root_url)
expect(flash[:alert]).to match(/You are not authorized to access this page./)
end
end
end
describe "DELETE #destroy" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: commentable, author: comment_author) }
context "when not logged in" do
before { sign_out member }
it "redirects to login" do
delete :destroy, params: { id: comment_to_delete.id }
expect(response).to redirect_to(new_member_session_path)
end
end
context "as comment author" do
before { sign_in comment_author }
it "deletes the comment" do
expect {
delete :destroy, params: { id: comment_to_delete.id }
}.to change(Comment, :count).by(-1)
end
it "redirects to commentable show page" do
delete :destroy, params: { id: comment_to_delete.id }
expect(response).to redirect_to(commentable_path(commentable))
end
end
context "as commentable owner" do
before { sign_in commentable_owner }
it "deletes the comment" do
# Ensure comment_author is not the same as commentable_owner for this test case
expect(comment_to_delete.author).not_to eq(commentable_owner)
expect {
delete :destroy, params: { id: comment_to_delete.id }
}.to change(Comment, :count).by(-1)
end
it "redirects to commentable show page" do
delete :destroy, params: { id: comment_to_delete.id }
expect(response).to redirect_to(commentable_path(commentable))
end
end
context "as admin" do
before { sign_in admin_user }
it "deletes the comment" do
expect {
delete :destroy, params: { id: comment_to_delete.id }
}.to change(Comment, :count).by(-1)
end
it "redirects to commentable show page" do
delete :destroy, params: { id: comment_to_delete.id }
expect(response).to redirect_to(commentable_path(commentable))
end
end
context "as unauthorized user" do
let(:other_user) { FactoryBot.create(:member) }
before { sign_in other_user }
it "does not delete the comment and redirects or shows error" do
expect {
delete :destroy, params: { id: comment_to_delete.id }
}.not_to change(Comment, :count)
expect(response).to redirect_to(root_url)
expect(flash[:alert]).to match(/You are not authorized to access this page./)
end
it "does not change post_id" do
comment.reload
expect(comment.post_id).to eq(post.id)
end
end
end
# Apply shared examples for each commentable type
context "for Post" do
it_behaves_like "a commentable controller", :post, :post_id
end
describe "DELETE destroy" do
before { delete :destroy, params: { id: comment.to_param } }
context "for Photo" do
it_behaves_like "a commentable controller", :photo, :photo_id
end
describe "my comment" do
let(:comment) { FactoryBot.create(:comment, author: member) }
context "for Planting" do
it_behaves_like "a commentable controller", :planting, :planting_id
end
it "redirects to the post the comment was on" do
expect(response).to redirect_to(comment.post)
end
end
context "for Harvest" do
it_behaves_like "a commentable controller", :harvest, :harvest_id
end
describe "not my comment" do
let(:comment) { FactoryBot.create(:comment) }
context "for Activity" do
it_behaves_like "a commentable controller", :activity, :activity_id
it { expect(response).not_to be_successful }
end
end
end

View File

@@ -1,19 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :activity do
association :owner, factory: :member
sequence(:name) { |n| "Test Activity #{n}" }
description { "This is a test activity." }
category { "General" } # Example category
due_date { Time.zone.today + 1.week }
# Optional associations, uncomment and adjust if Activity model has these
# association :garden, factory: :garden
# association :planting, factory: :planting
trait :finished do
finished { true }
end
end
end

View File

@@ -2,30 +2,9 @@
FactoryBot.define do
factory :comment do
association :author, factory: :member # Explicitly use :member factory for author
post
author
sequence(:body) { |n| "OMG LOL #{n}" }
# Default to associating with a post if no specific commentable is provided
association :commentable, factory: :post
trait :for_post do
association :commentable, factory: :post
end
trait :for_photo do
association :commentable, factory: :photo
end
trait :for_planting do
association :commentable, factory: :planting
end
trait :for_harvest do
association :commentable, factory: :harvest
end
trait :for_activity do
association :commentable, factory: :activity
end
# because our commenters are more polite than YouTube's
end
end

View File

@@ -1,17 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :member do
sequence(:login_name) { |n| "member#{n}" }
sequence(:email) { |n| "member#{n}@example.com" }
password { "password123" }
password_confirmation { "password123" }
confirmed_at { Time.zone.now } # Assuming Devise confirmable is used
trait :admin do
after(:create) { |member| member.add_role(:admin) }
end
# Add other traits if needed, e.g., for specific member states or roles
end
end

View File

@@ -1,38 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :planting do
association :owner, factory: :member
association :crop
association :garden
planted_at { Time.zone.today - 1.month }
description { "My awesome planting." }
quantity { 1 } # Example attribute
# Add traits if needed
trait :finished do
finished { true }
finished_at { Time.zone.today - 1.day }
end
end
end
# Assuming Crop and Garden factories are needed and might not exist
# Minimal Crop factory
FactoryBot.define do
factory :crop do
sequence(:name) { |n| "Test Crop #{n}" }
# Add other necessary attributes for Crop
# e.g., approval_status if relevant for comments
approval_status { 'approved' } # Default to approved for simplicity
end
end
# Minimal Garden factory
FactoryBot.define do
factory :garden do
association :owner, factory: :member
sequence(:name) { |n| "Test Garden #{n}" }
# Add other necessary attributes for Garden
end
end

View File

@@ -1,37 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :post do
association :author, factory: :member
association :forum # Assuming posts belong to a forum
sequence(:subject) { |n| "Test Post Subject #{n}" }
sequence(:body) { |n| "This is the body of test post #{n}." }
# Add traits if needed, e.g., for posts with photos, crops, etc.
trait :with_photos do
transient do
photos_count { 1 }
end
after(:create) do |post, evaluator|
create_list(:photo, evaluator.photos_count, owner: post.author, post_id: post.id) # Assuming Photo has post_id or similar
end
end
trait :with_crops do
transient do
crops_count { 1 }
end
after(:create) do |post, evaluator|
create_list(:crop, evaluator.crops_count, posts: [post]) # Assuming a has_and_belongs_to_many or has_many :through
end
end
end
end
# Assuming a Forum model exists and has a factory
FactoryBot.define do
factory :forum do
sequence(:name) { |n| "Test Forum #{n}" }
# Add other attributes for Forum as needed
end
end

View File

@@ -1,156 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.feature "Commenting on Activities", type: :feature, js: true do
let(:activity_owner) { FactoryBot.create(:member, login_name: "ActivityOwner") }
let(:commenter) { FactoryBot.create(:member, login_name: "Commenter") }
let(:other_member) { FactoryBot.create(:member, login_name: "OtherMember") }
let(:admin) { FactoryBot.create(:member, :admin, login_name: "AdminUser") }
let!(:activity) { FactoryBot.create(:activity, owner: activity_owner, name: "Gardening Day") }
def login_as(user)
visit new_member_session_path
fill_in "Login Name or Email", with: user.login_name
fill_in "Password", with: user.password
click_button "Log in"
expect(page).to have_content("Signed in successfully")
end
describe "User comments on an Activity" do
before do
login_as(commenter)
visit activity_path(activity)
end
it "allows a user to create a comment" do
expect(page).to have_content("Comments")
click_link "Add Comment"
expect(page).to have_current_path(new_activity_comment_path(activity))
expect(page).to have_content("Add comment to activity")
fill_in "comment_body", with: "Sounds like a fun activity!"
click_button "Post comment"
expect(page).to have_current_path(activity_path(activity))
expect(page).to have_content("Sounds like a fun activity!")
expect(page).to have_content(commenter.login_name)
end
end
describe "Editing comments on an Activity" do
let!(:comment_to_edit) { FactoryBot.create(:comment, commentable: activity, author: commenter, body: "Initial activity comment") }
context "as comment author" do
before do
login_as(commenter)
visit activity_path(activity)
find('.comment-body', text: "Initial activity comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows the author to edit their comment" do
expect(page).to have_current_path(edit_comment_path(comment_to_edit))
fill_in "comment_body", with: "Updated activity comment."
click_button "Post comment"
expect(page).to have_current_path(activity_path(activity))
expect(page).to have_content("Updated activity comment.")
expect(page).not_to have_content("Initial activity comment")
end
end
context "as admin" do
before do
login_as(admin)
visit activity_path(activity)
find('.comment-body', text: "Initial activity comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows admin to edit any comment" do
fill_in "comment_body", with: "Admin edited this activity comment."
click_button "Post comment"
expect(page).to have_content("Admin edited this activity comment.")
end
end
context "as unauthorized user" do
before do
login_as(other_member)
visit activity_path(activity)
end
it "does not show edit link for other's comment" do
comment_element = find('.comment-body', text: "Initial activity comment").ancestor('.comment')
expect(comment_element).not_to have_link("Edit")
expect(comment_element).not_to have_button("Actions")
end
end
end
describe "Deleting comments on an Activity" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: activity, author: commenter, body: "Delete this activity comment") }
context "as comment author" do
before do
login_as(commenter)
visit activity_path(activity)
find('.comment-body', text: "Delete this activity comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the author to delete their comment" do
expect(page).not_to have_content("Delete this activity comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as activity owner" do
before do
login_as(activity_owner)
visit activity_path(activity)
find('.comment-body', text: "Delete this activity comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the activity owner to delete any comment on their activity" do
expect(page).not_to have_content("Delete this activity comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as admin" do
before do
login_as(admin)
visit activity_path(activity)
find('.comment-body', text: "Delete this activity comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows admin to delete any comment" do
expect(page).not_to have_content("Delete this activity comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as unauthorized user" do
let!(:another_comment) { FactoryBot.create(:comment, commentable: activity, author: activity_owner, body: "Activity owner's comment") }
before do
login_as(other_member)
visit activity_path(activity)
end
it "does not show delete link for other's comment" do
comment_element = find('.comment-body', text: "Activity owner's comment").ancestor('.comment')
expect(comment_element).not_to have_link("Delete")
comment_element_2 = find('.comment-body', text: "Delete this activity comment").ancestor('.comment')
expect(comment_element_2).not_to have_link("Delete")
end
end
end
end

View File

@@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Commenting on a post' do
include_context 'signed in member'
let(:member) { create(:member) }
let(:post) { create(:post, author: member) }
before { visit new_comment_path post_id: post.id }
include_examples 'is accessible'
it "creating a comment" do
fill_in "comment_body", with: "This is a sample test for comment"
click_button "Post comment"
expect(page).to have_content "comment was successfully created."
expect(page).to have_content "Posted by"
page.percy_snapshot(page, name: 'Posting a comment')
end
context "editing a comment" do
let(:existing_comment) { create(:comment, post:, author: member) }
before do
visit edit_comment_path existing_comment
end
include_examples 'is accessible'
it "saving edit" do
fill_in "comment_body", with: "Testing edit for comment"
click_button "Post comment"
expect(page).to have_content "comment was successfully updated."
expect(page).to have_content "edited at"
end
end
end

View File

@@ -1,160 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.feature "Commenting on Harvests", type: :feature, js: true do
let(:harvest_owner) { FactoryBot.create(:member, login_name: "HarvestOwner") }
let(:commenter) { FactoryBot.create(:member, login_name: "Commenter") }
let(:other_member) { FactoryBot.create(:member, login_name: "OtherMember") }
let(:admin) { FactoryBot.create(:member, :admin, login_name: "AdminUser") }
# Ensure crop, planting, and garden are created for the harvest
let(:crop) { FactoryBot.create(:crop) }
let(:garden) { FactoryBot.create(:garden, owner: harvest_owner) } # Harvest owner also owns garden for simplicity
let(:planting) { FactoryBot.create(:planting, owner: harvest_owner, crop: crop, garden: garden) }
let!(:harvest) { FactoryBot.create(:harvest, owner: harvest_owner, planting: planting, description: "My test harvest") }
def login_as(user)
visit new_member_session_path
fill_in "Login Name or Email", with: user.login_name
fill_in "Password", with: user.password
click_button "Log in"
expect(page).to have_content("Signed in successfully")
end
describe "User comments on a Harvest" do
before do
login_as(commenter)
visit harvest_path(harvest)
end
it "allows a user to create a comment" do
expect(page).to have_content("Comments")
click_link "Add Comment"
expect(page).to have_current_path(new_harvest_comment_path(harvest))
expect(page).to have_content("Add comment to harvest")
fill_in "comment_body", with: "This harvest looks bountiful!"
click_button "Post comment"
expect(page).to have_current_path(harvest_path(harvest))
expect(page).to have_content("This harvest looks bountiful!")
expect(page).to have_content(commenter.login_name)
end
end
describe "Editing comments on a Harvest" do
let!(:comment_to_edit) { FactoryBot.create(:comment, commentable: harvest, author: commenter, body: "Initial harvest comment") }
context "as comment author" do
before do
login_as(commenter)
visit harvest_path(harvest)
find('.comment-body', text: "Initial harvest comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows the author to edit their comment" do
expect(page).to have_current_path(edit_comment_path(comment_to_edit))
fill_in "comment_body", with: "Updated harvest comment."
click_button "Post comment"
expect(page).to have_current_path(harvest_path(harvest))
expect(page).to have_content("Updated harvest comment.")
expect(page).not_to have_content("Initial harvest comment")
end
end
context "as admin" do
before do
login_as(admin)
visit harvest_path(harvest)
find('.comment-body', text: "Initial harvest comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows admin to edit any comment" do
fill_in "comment_body", with: "Admin edited this harvest comment."
click_button "Post comment"
expect(page).to have_content("Admin edited this harvest comment.")
end
end
context "as unauthorized user" do
before do
login_as(other_member)
visit harvest_path(harvest)
end
it "does not show edit link for other's comment" do
comment_element = find('.comment-body', text: "Initial harvest comment").ancestor('.comment')
expect(comment_element).not_to have_link("Edit")
expect(comment_element).not_to have_button("Actions")
end
end
end
describe "Deleting comments on a Harvest" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: harvest, author: commenter, body: "Delete this harvest comment") }
context "as comment author" do
before do
login_as(commenter)
visit harvest_path(harvest)
find('.comment-body', text: "Delete this harvest comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the author to delete their comment" do
expect(page).not_to have_content("Delete this harvest comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as harvest owner" do
before do
login_as(harvest_owner)
visit harvest_path(harvest)
find('.comment-body', text: "Delete this harvest comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the harvest owner to delete any comment on their harvest" do
expect(page).not_to have_content("Delete this harvest comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as admin" do
before do
login_as(admin)
visit harvest_path(harvest)
find('.comment-body', text: "Delete this harvest comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows admin to delete any comment" do
expect(page).not_to have_content("Delete this harvest comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as unauthorized user" do
let!(:another_comment) { FactoryBot.create(:comment, commentable: harvest, author: harvest_owner, body: "Harvest owner's comment") }
before do
login_as(other_member)
visit harvest_path(harvest)
end
it "does not show delete link for other's comment" do
comment_element = find('.comment-body', text: "Harvest owner's comment").ancestor('.comment')
expect(comment_element).not_to have_link("Delete")
comment_element_2 = find('.comment-body', text: "Delete this harvest comment").ancestor('.comment')
expect(comment_element_2).not_to have_link("Delete")
end
end
end
end

View File

@@ -1,158 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.feature "Commenting on Photos", type: :feature, js: true do
let(:photo_owner) { FactoryBot.create(:member, login_name: "PhotoOwner") }
let(:commenter) { FactoryBot.create(:member, login_name: "Commenter") }
let(:other_member) { FactoryBot.create(:member, login_name: "OtherMember") }
let(:admin) { FactoryBot.create(:member, :admin, login_name: "AdminUser") }
let!(:photo) { FactoryBot.create(:photo, owner: photo_owner, title: "Beautiful Sunset") }
def login_as(user)
visit new_member_session_path
fill_in "Login Name or Email", with: user.login_name
fill_in "Password", with: user.password
click_button "Log in"
expect(page).to have_content("Signed in successfully")
end
describe "User comments on a Photo" do
before do
login_as(commenter)
visit photo_path(photo)
end
it "allows a user to create a comment" do
expect(page).to have_content("Comments")
click_link "Add Comment" # From the _comments partial
expect(page).to have_current_path(new_photo_comment_path(photo))
expect(page).to have_content("Add comment to photo")
fill_in "comment_body", with: "What a stunning photo!"
click_button "Post comment"
expect(page).to have_current_path(photo_path(photo))
expect(page).to have_content("What a stunning photo!")
expect(page).to have_content(commenter.login_name)
end
end
describe "Editing comments on a Photo" do
let!(:comment_to_edit) { FactoryBot.create(:comment, commentable: photo, author: commenter, body: "Initial comment body") }
context "as comment author" do
before do
login_as(commenter)
visit photo_path(photo)
# Find the comment section and then the edit button within it.
# This assumes the comment body is unique enough or we can find by specific data-testid attributes if added.
find('.comment-body', text: "Initial comment body").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows the author to edit their comment" do
expect(page).to have_current_path(edit_comment_path(comment_to_edit))
fill_in "comment_body", with: "Updated comment body here."
click_button "Post comment"
expect(page).to have_current_path(photo_path(photo))
expect(page).to have_content("Updated comment body here.")
expect(page).not_to have_content("Initial comment body")
end
end
context "as admin" do
before do
login_as(admin)
visit photo_path(photo)
find('.comment-body', text: "Initial comment body").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows admin to edit any comment" do
fill_in "comment_body", with: "Admin edited this comment."
click_button "Post comment"
expect(page).to have_content("Admin edited this comment.")
end
end
context "as unauthorized user" do
before do
login_as(other_member)
visit photo_path(photo)
end
it "does not show edit link for other's comment" do
# Check within the specific comment's scope
comment_element = find('.comment-body', text: "Initial comment body").ancestor('.comment')
expect(comment_element).not_to have_link("Edit")
expect(comment_element).not_to have_button("Actions") # Or check that Actions doesn't show Edit
end
end
end
describe "Deleting comments on a Photo" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: photo, author: commenter, body: "This will be deleted") }
context "as comment author" do
before do
login_as(commenter)
visit photo_path(photo)
find('.comment-body', text: "This will be deleted").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the author to delete their comment" do
expect(page).not_to have_content("This will be deleted")
expect(page).to have_content("Comment was successfully destroyed.") # Assuming flash message
end
end
context "as photo owner" do
before do
login_as(photo_owner)
visit photo_path(photo)
find('.comment-body', text: "This will be deleted").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the photo owner to delete any comment on their photo" do
expect(page).not_to have_content("This will be deleted")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as admin" do
before do
login_as(admin)
visit photo_path(photo)
find('.comment-body', text: "This will be deleted").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows admin to delete any comment" do
expect(page).not_to have_content("This will be deleted")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as unauthorized user" do
let!(:another_comment) { FactoryBot.create(:comment, commentable: photo, author: photo_owner, body: "Photo owner's comment") }
before do
login_as(other_member) # other_member is not admin, not photo_owner, not author of `another_comment`
visit photo_path(photo)
end
it "does not show delete link for other's comment" do
comment_element = find('.comment-body', text: "Photo owner's comment").ancestor('.comment')
expect(comment_element).not_to have_link("Delete")
# Also check the original comment_to_delete by commenter
comment_element_2 = find('.comment-body', text: "This will be deleted").ancestor('.comment')
expect(comment_element_2).not_to have_link("Delete")
end
end
end
end

View File

@@ -1,158 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.feature "Commenting on Plantings", type: :feature, js: true do
let(:planting_owner) { FactoryBot.create(:member, login_name: "PlantingOwner") }
let(:commenter) { FactoryBot.create(:member, login_name: "Commenter") }
let(:other_member) { FactoryBot.create(:member, login_name: "OtherMember") }
let(:admin) { FactoryBot.create(:member, :admin, login_name: "AdminUser") }
# Ensure crop and garden are created for the planting
let(:crop) { FactoryBot.create(:crop) }
let(:garden) { FactoryBot.create(:garden, owner: planting_owner) }
let!(:planting) { FactoryBot.create(:planting, owner: planting_owner, crop: crop, garden: garden, description: "My test planting") }
def login_as(user)
visit new_member_session_path
fill_in "Login Name or Email", with: user.login_name
fill_in "Password", with: user.password
click_button "Log in"
expect(page).to have_content("Signed in successfully")
end
describe "User comments on a Planting" do
before do
login_as(commenter)
visit planting_path(planting)
end
it "allows a user to create a comment" do
expect(page).to have_content("Comments")
click_link "Add Comment"
expect(page).to have_current_path(new_planting_comment_path(planting))
expect(page).to have_content("Add comment to planting")
fill_in "comment_body", with: "This planting looks great!"
click_button "Post comment"
expect(page).to have_current_path(planting_path(planting))
expect(page).to have_content("This planting looks great!")
expect(page).to have_content(commenter.login_name)
end
end
describe "Editing comments on a Planting" do
let!(:comment_to_edit) { FactoryBot.create(:comment, commentable: planting, author: commenter, body: "Initial planting comment") }
context "as comment author" do
before do
login_as(commenter)
visit planting_path(planting)
find('.comment-body', text: "Initial planting comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows the author to edit their comment" do
expect(page).to have_current_path(edit_comment_path(comment_to_edit))
fill_in "comment_body", with: "Updated planting comment."
click_button "Post comment"
expect(page).to have_current_path(planting_path(planting))
expect(page).to have_content("Updated planting comment.")
expect(page).not_to have_content("Initial planting comment")
end
end
context "as admin" do
before do
login_as(admin)
visit planting_path(planting)
find('.comment-body', text: "Initial planting comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows admin to edit any comment" do
fill_in "comment_body", with: "Admin edited this planting comment."
click_button "Post comment"
expect(page).to have_content("Admin edited this planting comment.")
end
end
context "as unauthorized user" do
before do
login_as(other_member)
visit planting_path(planting)
end
it "does not show edit link for other's comment" do
comment_element = find('.comment-body', text: "Initial planting comment").ancestor('.comment')
expect(comment_element).not_to have_link("Edit")
expect(comment_element).not_to have_button("Actions")
end
end
end
describe "Deleting comments on a Planting" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: planting, author: commenter, body: "Delete this planting comment") }
context "as comment author" do
before do
login_as(commenter)
visit planting_path(planting)
find('.comment-body', text: "Delete this planting comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the author to delete their comment" do
expect(page).not_to have_content("Delete this planting comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as planting owner" do
before do
login_as(planting_owner)
visit planting_path(planting)
find('.comment-body', text: "Delete this planting comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the planting owner to delete any comment on their planting" do
expect(page).not_to have_content("Delete this planting comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as admin" do
before do
login_as(admin)
visit planting_path(planting)
find('.comment-body', text: "Delete this planting comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows admin to delete any comment" do
expect(page).not_to have_content("Delete this planting comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as unauthorized user" do
let!(:another_comment) { FactoryBot.create(:comment, commentable: planting, author: planting_owner, body: "Planting owner's comment") }
before do
login_as(other_member)
visit planting_path(planting)
end
it "does not show delete link for other's comment" do
comment_element = find('.comment-body', text: "Planting owner's comment").ancestor('.comment')
expect(comment_element).not_to have_link("Delete")
comment_element_2 = find('.comment-body', text: "Delete this planting comment").ancestor('.comment')
expect(comment_element_2).not_to have_link("Delete")
end
end
end
end

View File

@@ -1,176 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.feature "Commenting on Posts", type: :feature, js: true do
# Use existing 'signed in member' shared context if it provides Capybara login helper
# Otherwise, define one locally or ensure one is available.
# For consistency with other new specs, defining a local login_as helper.
let(:post_author) { FactoryBot.create(:member, login_name: "PostAuthor") }
let(:commenter) { FactoryBot.create(:member, login_name: "Commenter") }
let(:other_member) { FactoryBot.create(:member, login_name: "OtherMember") }
let(:admin) { FactoryBot.create(:member, :admin, login_name: "AdminUser") }
# Ensure a forum is created for the post if your Post factory/model requires it
let!(:forum) { FactoryBot.create(:forum) }
let!(:post) { FactoryBot.create(:post, author: post_author, forum: forum, subject: "My Test Post") }
def login_as(user)
visit new_member_session_path
fill_in "Login Name or Email", with: user.login_name
fill_in "Password", with: user.password
click_button "Log in"
expect(page).to have_content("Signed in successfully")
end
# Include shared examples for accessibility if they exist and are relevant
# For now, focusing on the commenting CRUD operations.
# The original spec had: include_examples 'is accessible'
# If this shared example exists and is desired, it can be added to relevant `before` blocks.
describe "User comments on a Post" do
before do
login_as(commenter)
visit post_path(post)
end
it "allows a user to create a comment" do
expect(page).to have_content("Comments") # From the _comments partial
# The "Add Comment" link is now within the _comments partial
# If the _comments partial is set up to show the form directly or link to new, this will work.
# Assuming it has a link:
click_link "Add Comment"
expect(page).to have_current_path(new_post_comment_path(post))
expect(page).to have_content("Add comment to post")
fill_in "comment_body", with: "This is a great post!"
click_button "Post comment"
expect(page).to have_current_path(post_path(post)) # Should redirect back to the post
expect(page).to have_content("This is a great post!")
expect(page).to have_content(commenter.login_name)
# Check for flash message if your controller sets one, e.g.:
# expect(page).to have_content("Comment was successfully created.")
end
end
describe "Editing comments on a Post" do
let!(:comment_to_edit) { FactoryBot.create(:comment, commentable: post, author: commenter, body: "Initial post comment") }
context "as comment author" do
before do
login_as(commenter)
visit post_path(post)
find('.comment-body', text: "Initial post comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows the author to edit their comment" do
expect(page).to have_current_path(edit_comment_path(comment_to_edit))
fill_in "comment_body", with: "Updated post comment."
click_button "Post comment"
expect(page).to have_current_path(post_path(post))
expect(page).to have_content("Updated post comment.")
expect(page).not_to have_content("Initial post comment")
# Check for flash message if your controller sets one, e.g.:
# expect(page).to have_content("Comment was successfully updated.")
end
end
context "as admin" do
before do
login_as(admin)
visit post_path(post)
find('.comment-body', text: "Initial post comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows admin to edit any comment" do
fill_in "comment_body", with: "Admin edited this post comment."
click_button "Post comment"
expect(page).to have_content("Admin edited this post comment.")
end
end
context "as unauthorized user" do
before do
login_as(other_member)
visit post_path(post)
end
it "does not show edit link for other's comment" do
comment_element = find('.comment-body', text: "Initial post comment").ancestor('.comment')
expect(comment_element).not_to have_link("Edit")
expect(comment_element).not_to have_button("Actions")
end
end
end
describe "Deleting comments on a Post" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: post, author: commenter, body: "Delete this post comment") }
context "as comment author" do
before do
login_as(commenter)
visit post_path(post)
find('.comment-body', text: "Delete this post comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the author to delete their comment" do
expect(page).not_to have_content("Delete this post comment")
expect(page).to have_content("Comment was successfully destroyed.") # Assuming flash message
end
end
context "as post author (owner of commentable)" do
before do
# Ensure commenter is not the post_author for this specific test
expect(commenter).not_to eq(post_author)
login_as(post_author)
visit post_path(post)
find('.comment-body', text: "Delete this post comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the post author to delete any comment on their post" do
expect(page).not_to have_content("Delete this post comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as admin" do
before do
login_as(admin)
visit post_path(post)
find('.comment-body', text: "Delete this post comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows admin to delete any comment" do
expect(page).not_to have_content("Delete this post comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as unauthorized user" do
# Create a comment by the post_author to test against
let!(:another_comment) { FactoryBot.create(:comment, commentable: post, author: post_author, body: "Post author's comment") }
before do
login_as(other_member) # other_member is not admin, not post_author, not author of `another_comment`
visit post_path(post)
end
it "does not show delete link for other's comment" do
comment_element = find('.comment-body', text: "Post author's comment").ancestor('.comment')
expect(comment_element).not_to have_link("Delete")
# Also check the original comment_to_delete by commenter
comment_element_2 = find('.comment-body', text: "Delete this post comment").ancestor('.comment')
expect(comment_element_2).not_to have_link("Delete")
end
end
end
end

View File

@@ -3,152 +3,53 @@
require 'rails_helper'
describe Comment do
context "associations" do
let(:member) { FactoryBot.create(:member) } # Common author
context "basic" do
let(:comment) { FactoryBot.create(:comment) }
it "belongs to a commentable (Post)" do
post = FactoryBot.create(:post, author: member)
comment = FactoryBot.create(:comment, commentable: post, author: member)
comment.commentable.should be_an_instance_of Post
end
it "belongs to a commentable (Photo)" do
photo = FactoryBot.create(:photo, owner: member)
comment = FactoryBot.create(:comment, commentable: photo, author: member)
comment.commentable.should be_an_instance_of Photo
end
it "belongs to a commentable (Planting)" do
planting = FactoryBot.create(:planting, owner: member)
comment = FactoryBot.create(:comment, commentable: planting, author: member)
comment.commentable.should be_an_instance_of Planting
end
it "belongs to a commentable (Harvest)" do
harvest = FactoryBot.create(:harvest, owner: member)
comment = FactoryBot.create(:comment, commentable: harvest, author: member)
comment.commentable.should be_an_instance_of Harvest
end
it "belongs to a commentable (Activity)" do
activity = FactoryBot.create(:activity, owner: member)
comment = FactoryBot.create(:comment, commentable: activity, author: member)
comment.commentable.should be_an_instance_of Activity
it "belongs to a post" do
comment.post.should be_an_instance_of Post
end
it "belongs to an author" do
comment = FactoryBot.create(:comment, author: member) # Default commentable is Post
comment.author.should be_an_instance_of Member
end
end
RSpec.shared_examples "comment notifications" do |commentable_type, commentable_factory_name|
let(:commentable_owner) { FactoryBot.create(:member) }
let(:comment_author) { FactoryBot.create(:member) }
let!(:commentable) do
# For :post, the owner is :author. For others, it's :owner.
if commentable_factory_name == :post
FactoryBot.create(commentable_factory_name, author: commentable_owner)
else
FactoryBot.create(commentable_factory_name, owner: commentable_owner)
end
end
context "notifications" do
it "sends a notification when a comment is posted" do
expect do
FactoryBot.create(:comment, commentable: commentable, author: comment_author)
FactoryBot.create(:comment)
end.to change(Notification, :count).by(1)
end
it "sets the notification fields correctly" do
comment = FactoryBot.create(:comment, commentable: commentable, author: comment_author)
notification = Notification.last # More robust than Notification.first
notification.sender.should eq comment.author
notification.recipient.should eq commentable_owner
notification.subject.should include "commented on your #{commentable_type.downcase}"
notification.body.should eq comment.body
notification.commentable_id.should eq commentable.id
notification.commentable_type.should eq commentable_type
it "sets the notification fields" do
@c = FactoryBot.create(:comment)
@n = Notification.first
@n.sender.should eq @c.author
@n.recipient.should eq @c.post.author
@n.subject.should include 'commented on'
@n.body.should eq @c.body
@n.post.should eq @c.post
end
it "doesn't send notifications to yourself (when comment author is commentable owner)" do
it "doesn't send notifications to yourself" do
@m = FactoryBot.create(:member)
@p = FactoryBot.create(:post, author: @m)
expect do
FactoryBot.create(:comment, commentable: commentable, author: commentable_owner)
FactoryBot.create(:comment, post: @p, author: @m)
end.not_to change(Notification, :count)
end
end
context "notifications for Post" do
include_examples "comment notifications", "Post", :post
end
context "notifications for Photo" do
include_examples "comment notifications", "Photo", :photo
end
context "notifications for Planting" do
include_examples "comment notifications", "Planting", :planting
end
context "notifications for Harvest" do
include_examples "comment notifications", "Harvest", :harvest
end
context "notifications for Activity" do
include_examples "comment notifications", "Activity", :activity
end
RSpec.shared_examples "comment to_s method" do |commentable_type, commentable_factory_name|
let(:commentable_owner) { FactoryBot.create(:member) }
let(:comment_author) { FactoryBot.create(:member, login_name: "CommenterUser") }
let!(:commentable) do
# For :post, the owner is :author. For others, it's :owner.
obj = if commentable_factory_name == :post
FactoryBot.create(commentable_factory_name, author: commentable_owner)
else
FactoryBot.create(commentable_factory_name, owner: commentable_owner)
end
# Ensure commentable has a consistent ID for the test if possible, or just use its class name
obj
end
let(:comment) { FactoryBot.create(:comment, commentable: commentable, author: comment_author) }
it "returns a descriptive string" do
expected_string = "#{comment_author.login_name} commented on #{commentable_type.downcase} ##{commentable.id}"
comment.to_s.should eq expected_string
end
end
context "to_s method for Post" do
include_examples "comment to_s method", "Post", :post
end
context "to_s method for Photo" do
include_examples "comment to_s method", "Photo", :photo
end
context "to_s method for Planting" do
include_examples "comment to_s method", "Planting", :planting
end
context "to_s method for Harvest" do
include_examples "comment to_s method", "Harvest", :harvest
end
context "to_s method for Activity" do
include_examples "comment to_s method", "Activity", :activity
end
context "ordering" do
before do
@m = FactoryBot.create(:member)
# Ensure the commentable for ordering test is a Post, as it was originally
@p = FactoryBot.create(:post, author: @m)
@c1 = FactoryBot.create(:comment, commentable: @p, author: @m)
@c2 = FactoryBot.create(:comment, commentable: @p, author: @m)
@c1 = FactoryBot.create(:comment, post: @p, author: @m)
@c2 = FactoryBot.create(:comment, post: @p, author: @m)
end
it 'has a scope for ASC order for displaying on commentable page' do # Renamed for clarity
it 'has a scope for ASC order for displaying on post page' do
described_class.post_order.should eq [@c1, @c2]
end
end

View File

@@ -2,11 +2,6 @@
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require 'simplecov'
# output coverage locally AND send it to coveralls
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([SimpleCov::Formatter::HTMLFormatter])
require 'spec_helper'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'
@@ -20,13 +15,13 @@ require 'capybara-screenshot/rspec'
require 'axe-capybara'
require 'axe-rspec'
# TODO: We may want to trial options.add_argument('--disable-dev-shm-usage') ### optional
# Required for running in the dev container
Capybara.register_driver :selenium_chrome_customised_headless do |app|
options = Selenium::WebDriver::Options.chrome
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--window-size=1920,1080")
options.add_argument("--disable-dev-shm-usage")
# driver = Selenium::WebDriver.for :chrome, options: options
@@ -125,8 +120,8 @@ RSpec.configure do |config|
# Prevent Poltergeist from fetching external URLs during feature tests
config.before(:each, :js) do
# TODO: Why are we setting this page size then straight afterwards, maximising?
width = 1280
height = 1280
width = 1920
height = 1080
Capybara.current_session.driver.browser.manage.window.resize_to(width, height)
if page.driver.browser.respond_to?(:url_blacklist)
@@ -137,6 +132,9 @@ RSpec.configure do |config|
]
end
page.driver.browser.manage.window.maximize if page.driver.browser.respond_to?(:manage)
# Historically, we wanted to .maximize; but this actually undoes the resize_to step above
# with chrome headless
# page.driver.browser.manage.window.maximize if page.driver.browser.respond_to?(:manage)
# puts "Maximized window size: #{page.driver.browser.manage.window.size}"
end
end

View File

@@ -16,8 +16,8 @@
# users commonly want.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
require 'simplecov'
require 'percy/capybara'
require 'rspec/rebound'
require 'vcr'
VCR.configure do |c|
@@ -27,8 +27,6 @@ VCR.configure do |c|
c.configure_rspec_metadata!
end
SimpleCov.start
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
@@ -126,4 +124,20 @@ RSpec.configure do |config|
# Remember which tests failed, so you can run rspec with the `--only-failures` flag.
config.example_status_persistence_file_path = "tmp/examples.txt"
# show retry status in spec process
config.verbose_retry = true
# show exception that triggers a retry if verbose_retry is set to true
config.display_try_failure_messages = true
# run retry only on features
config.around :each, :js do |ex|
ex.run_with_retry retry: 3
end
# callback to be run between retries
config.retry_callback = proc do |ex|
# run some additional clean up task - can be filtered by example metadata
Capybara.reset! if ex.metadata[:js]
end
end

View File

@@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'rails_helper'
require 'rake'
describe 'openfarm:delete_pictures' do
before(:all) do
Rails.application.load_tasks
end
# We need to do this because Rake tasks normally output to STDOUT, but we
# don't want to clutter up the test output.
before(:each) do
$stdout = StringIO.new
end
after(:each) do
$stdout = STDOUT
end
it 'deletes pictures with source OpenFarm' do
create(:photo, source: 'OpenFarm')
create(:photo, source: 'flickr')
expect(Photo.where(source: 'OpenFarm').count).to eq(1)
expect(Photo.where(source: 'flickr').count).to eq(1)
Rake::Task['openfarm:delete_pictures'].invoke
expect(Photo.where(source: 'OpenFarm').count).to eq(0)
expect(Photo.where(source: 'flickr').count).to eq(1)
end
end