├── .babelrc
├── .gitignore
├── .postcssrc.yml
├── .rspec
├── .rubocop.yml
├── .ruby-version
├── .tool-versions
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── Rakefile
├── USER-STORIES.md
├── app
├── assets
│ ├── config
│ │ └── manifest.js
│ ├── images
│ │ └── .keep
│ ├── javascripts
│ │ ├── application.js
│ │ ├── cable.js
│ │ └── channels
│ │ │ └── .keep
│ └── stylesheets
│ │ ├── application.scss
│ │ ├── events.scss
│ │ ├── organizations.scss
│ │ └── scaffolds.scss
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
├── controllers
│ ├── application_controller.rb
│ ├── concerns
│ │ └── .keep
│ ├── events_controller.rb
│ └── organizations_controller.rb
├── helpers
│ ├── application_helper.rb
│ └── committees_helper.rb
├── javascript
│ └── packs
│ │ ├── AgendaList
│ │ ├── agenda_manager.js
│ │ ├── checkbox_committee.js
│ │ ├── daily_agenda.js
│ │ ├── event.js
│ │ └── tmp_list.js
│ │ ├── agenda.js
│ │ ├── application.js
│ │ ├── hello_react.jsx
│ │ └── tmp_data.json
├── jobs
│ └── application_job.rb
├── lib
│ ├── house_events_scraper.rb
│ └── organization_loader.rb
├── mailers
│ └── application_mailer.rb
├── models
│ ├── application_record.rb
│ ├── concerns
│ │ └── .keep
│ ├── event.rb
│ └── organization.rb
└── views
│ ├── events
│ └── index.html.erb
│ ├── layouts
│ ├── application.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
│ └── shared
│ ├── _footer.html.erb
│ └── _nav.html.erb
├── bin
├── bundle
├── rails
├── rake
├── rspec
├── setup
├── spring
├── update
├── webpack
├── webpack-dev-server
└── yarn
├── config.ru
├── config
├── application.rb
├── boot.rb
├── cable.yml
├── credentials.yml.enc
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── application_controller_renderer.rb
│ ├── assets.rb
│ ├── backtrace_silencers.rb
│ ├── content_security_policy.rb
│ ├── cookies_serializer.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ └── wrap_parameters.rb
├── locales
│ └── en.yml
├── puma.rb
├── routes.rb
├── spring.rb
├── storage.yml
├── webpack
│ ├── development.js
│ ├── environment.js
│ ├── production.js
│ └── test.js
└── webpacker.yml
├── cypress.json
├── cypress
├── fixtures
│ └── example.json
├── integration
│ └── examples
│ │ ├── actions.spec.js
│ │ ├── aliasing.spec.js
│ │ ├── assertions.spec.js
│ │ ├── connectors.spec.js
│ │ ├── cookies.spec.js
│ │ ├── cypress_api.spec.js
│ │ ├── files.spec.js
│ │ ├── local_storage.spec.js
│ │ ├── location.spec.js
│ │ ├── misc.spec.js
│ │ ├── navigation.spec.js
│ │ ├── network_requests.spec.js
│ │ ├── querying.spec.js
│ │ ├── spies_stubs_clocks.spec.js
│ │ ├── traversal.spec.js
│ │ ├── utilities.spec.js
│ │ ├── viewport.spec.js
│ │ ├── waiting.spec.js
│ │ └── window.spec.js
├── plugins
│ └── index.js
└── support
│ ├── commands.js
│ └── index.js
├── db
├── migrate
│ ├── 20180608143102_create_committees.rb
│ ├── 20180608180059_add_ancestry_to_committee.rb
│ ├── 20180608182158_create_events.rb
│ ├── 20180610012137_change_commitees_to_organizations.rb
│ └── 20180610121037_add_raw_location_to_event.rb
├── sample
│ └── events.rb
├── schema.rb
└── seeds.rb
├── lib
├── assets
│ └── .keep
└── tasks
│ ├── .keep
│ ├── committees.rake
│ ├── db.rake
│ └── events.rake
├── log
└── .keep
├── logfile
├── package-lock.json
├── package.json
├── public
├── 404.html
├── 422.html
├── 500.html
├── apple-touch-icon-precomposed.png
├── apple-touch-icon.png
├── favicon.ico
└── robots.txt
├── spec
├── controllers
│ ├── committees_controller_spec.rb
│ └── events_controller_spec.rb
├── factories
│ └── events.rb
├── fixtures
│ └── files
│ │ ├── HHRG-115-ED00-20180606.xml
│ │ ├── HHRG-115-RU00-20180605.xml
│ │ └── HMKP-115-HM00-20180606.xml
├── lib
│ ├── house_events_scraper
│ │ └── event_parser_spec.rb
│ └── house_events_scraper_spec.rb
├── models
│ ├── event_spec.rb
│ └── organization_spec.rb
├── rails_helper.rb
├── spec_helper.rb
└── support
│ └── factory_bot.rb
├── tmp
└── .keep
├── vendor
└── .keep
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false,
7 | "targets": {
8 | "browsers": "> 1%",
9 | "uglify": true
10 | },
11 | "useBuiltIns": true
12 | }
13 | ],
14 | "react"
15 | ],
16 | "plugins": [
17 | "syntax-dynamic-import",
18 | "transform-object-rest-spread",
19 | [
20 | "transform-class-properties",
21 | {
22 | "spec": true
23 | }
24 | ]
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore all logfiles and tempfiles.
11 | /log/*
12 | /tmp/*
13 | !/log/.keep
14 | !/tmp/.keep
15 |
16 | # Ignore uploaded files in development
17 | /storage/*
18 |
19 | /node_modules
20 | /yarn-error.log
21 |
22 | /public/assets
23 | .byebug_history
24 |
25 | # Ignore master key for decrypting credentials and more.
26 | /config/master.key
27 | /public/packs
28 | /public/packs-test
29 | /node_modules
30 | yarn-debug.log*
31 | yarn-error.log*
32 | .yarn-integrity
33 |
--------------------------------------------------------------------------------
/.postcssrc.yml:
--------------------------------------------------------------------------------
1 | plugins:
2 | postcss-import: {}
3 | postcss-cssnext: {}
4 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --require spec_helper
2 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_gem:
2 | rubocop-rails:
3 | - config/rails.yml
4 |
5 | AllCops:
6 | TargetRubyVersion: 2.3
7 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.3.1
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | ruby 2.3.1
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.5.1
4 | services:
5 | - postgresql
6 | script:
7 | - RAILS_ENV=test bundle exec rake db:setup
8 | - RAILS_ENV=test bundle exec rake
9 | before_script:
10 | - psql -c 'create database demand_progress_test' -U demand-progress
11 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of conduct
2 |
3 | Like the technical community as a whole, the Ruby for Good team and community is made up of a mixture of professionals and volunteers from all over the world, working on every aspect of the mission - including mentorship, teaching, and connecting people.
4 |
5 | Diversity is one of our huge strengths, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to when they're participating within this community and project. These rules apply equally to founders, mentors and those seeking help and guidance.
6 |
7 | This isn't an exhaustive list of things that you can't do. Rather, take it in the spirit in which it's intended - a guide to make it easier to enrich all of us and the technical communities in which we participate.
8 |
9 | This code of conduct applies to all spaces managed by the Ruby for Good team or at Georgetown University. This includes IRC, Slack, the mailing lists, the issue tracker, Ruby for Good events, and any other forums created by the project team which the community uses for communication.
10 |
11 | Be welcoming, friendly, and patient. Be considerate. Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
12 |
13 | Be respectful. Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It's important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the Ruby for Good community should be respectful when dealing with other members as well as with people outside the Ruby for Good community.
14 |
15 | Be careful in the words that you choose. We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to:
16 |
17 | - Violent threats or language directed against another person.
18 | - Sexist, racist, or otherwise discriminatory jokes and language.
19 | - Posting sexually explicit or violent material.
20 | - Posting (or threatening to post) other people's personally identifying information ("doxing").
21 | - Personal insults, especially those using racist or sexist terms.
22 | - Unwelcome sexual attention.
23 | - Advocating for, or encouraging, any of the above behavior.
24 | - Repeated harassment of others. In general, if someone asks you to stop, then stop.
25 | - If you believe someone is violating the code of conduct, we ask that you report it by emailing [Teresa Finn](mailto:teresa@rubyforgood.org) or [Kalimar Maia](mailto:kalimar@rubyforgood.org). Any email received will be kept confidential.
26 |
27 | When we disagree, try to understand why. Disagreements, both social and technical, happen all the time and Ruby is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we're different. The strength of Ruby comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn't mean that they're wrong. Don't forget that it is human to err and blaming each other doesn't get us anywhere, rather offer to help resolving issues and to help learn from mistakes.
28 |
29 | If a participant engages in harassing behavior, the organizers may take any action they deem appropriate, including warning the offender or expulsion from the event with no refund. The organizers and volunteers will be introduced at the beginning of the event.
30 |
31 | We're people trying to make the world better.
32 |
33 | _For our Code of Conduct we have based ours on the excellent one made by the Django Project._
34 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We ♥ contributors! By participating in this project, you agree to abide by the
4 | Ruby for Good [Code of Conduct](CODE_OF_CONDUCT.md).
5 |
6 | **First:** if you're unsure or afraid of *anything*, just ask or submit the
7 | issue or pull request anyways. You won't be yelled at for giving your best
8 | effort. The worst that can happen is that you'll be politely asked to change
9 | something. We appreciate any sort of contributions, and don't want a wall of
10 | rules to get in the way of that.
11 |
12 | Here are the basic steps to submit a pull request. Make sure that you're working
13 | on an [open issue](https://github.com/rubyforgood/demand-progress/issues)–if the
14 | relevant issue doesn't exist, open it!
15 |
16 | 1. Claim an issue on
17 | [our issue tracker](https://github.com/rubyforgood/demand-progress/issues) by
18 | assigning it to yourself (core team member) or commenting. If the issue
19 | doesn't exist yet, open it.
20 | 1. Fork the repo.
21 | 1. Make sure you have yarn installed (e.g. `brew install yarn`).
22 | 1. Run `./bin/setup`.
23 | 1. Run `./bin/rake committees:house:load` and `./bin/rake committees:senate:load`
24 | to load the House and Senate committee & sub committee relationships.
25 | 1. Optionally, run `./bin/rake db:populate` to add some sample data
26 | to your database.
27 | 1. Run the tests. We only take pull requests with passing tests, and it's great
28 | to know that you have a clean slate: `./bin/rake`
29 | 1. Add a test for your change. If you are adding functionality or fixing a
30 | bug, you should add a test!
31 | 1. Make the test pass.
32 | 1. Push to your fork and submit a pull request. Include the issue number
33 | (ex. `Resolves #1`) in the PR description.
34 |
35 | At this point you're waiting on us–we'll try to respond to your PR quickly.
36 | We may suggest some changes or improvements or alternatives.
37 |
38 | Some things that will increase the chance that your pull request is accepted:
39 |
40 | * Use Rails idioms and helpers
41 | * Include tests that fail without your code, and pass with it
42 | * Update the documentation, the surrounding one, examples elsewhere, guides,
43 | whatever is affected by your contribution
44 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5 |
6 | ruby "2.5.1"
7 |
8 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
9 | gem "rails", "~> 5.2.0"
10 | # Use postgresql as the database for Active Record
11 | gem "pg", ">= 0.18", "< 2.0"
12 | # Use Puma as the app server
13 | gem "puma", "~> 3.11"
14 | # Use SCSS for stylesheets
15 | gem "sass-rails", "~> 5.0"
16 | # Use Uglifier as compressor for JavaScript assets
17 | gem "uglifier", ">= 1.3.0"
18 | # See https://github.com/rails/execjs#readme for more supported runtimes
19 | # gem 'mini_racer', platforms: :ruby
20 |
21 | # Use CoffeeScript for .coffee assets and views
22 | gem "coffee-rails", "~> 4.2"
23 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
24 | gem "turbolinks", "~> 5"
25 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
26 | gem "jbuilder", "~> 2.5"
27 |
28 | gem "webpacker"
29 | gem "bootstrap"
30 | gem "jquery-rails"
31 | # Use Redis adapter to run Action Cable in production
32 | # gem 'redis', '~> 4.0'
33 | # Use ActiveModel has_secure_password
34 | # gem 'bcrypt', '~> 3.1.7'
35 |
36 | # Use ActiveStorage variant
37 | # gem 'mini_magick', '~> 4.8'
38 |
39 | # Use Capistrano for deployment
40 | # gem 'capistrano-rails', group: :development
41 |
42 | # Reduces boot times through caching; required in config/boot.rb
43 | gem "bootsnap", ">= 1.1.0", require: false
44 |
45 | gem "decent_exposure"
46 | # Committee/subcommittee relationship
47 | gem "ancestry"
48 |
49 | # For retrieving and parsing ProPublica Senate/House committee data
50 | gem "httparty"
51 | gem "oj"
52 |
53 | gem "ox"
54 | gem "mechanize"
55 |
56 | group :development, :test do
57 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
58 | gem "byebug", platforms: [:mri, :mingw, :x64_mingw]
59 | gem "rspec-rails"
60 | gem "factory_bot_rails"
61 | gem "rubocop", "~> 0.56.0", require: false
62 | gem "rubocop-rails"
63 | gem "pry-rails"
64 | gem "binding_of_caller"
65 | end
66 |
67 | group :development do
68 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
69 | gem "web-console", ">= 3.3.0"
70 | gem "listen", ">= 3.0.5", "< 3.2"
71 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
72 | gem "spring"
73 | gem "spring-watcher-listen", "~> 2.0.0"
74 | gem "spring-commands-rspec"
75 | gem 'annotate'
76 | end
77 |
78 | group :test do
79 | # Adds support for Capybara system testing and selenium driver
80 | gem "capybara", ">= 2.15", "< 4.0"
81 | gem "selenium-webdriver"
82 | # Easy installation and use of chromedriver to run system tests with Chrome
83 | gem "chromedriver-helper"
84 | gem "database_cleaner"
85 | end
86 |
87 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
88 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
89 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actioncable (5.2.0)
5 | actionpack (= 5.2.0)
6 | nio4r (~> 2.0)
7 | websocket-driver (>= 0.6.1)
8 | actionmailer (5.2.0)
9 | actionpack (= 5.2.0)
10 | actionview (= 5.2.0)
11 | activejob (= 5.2.0)
12 | mail (~> 2.5, >= 2.5.4)
13 | rails-dom-testing (~> 2.0)
14 | actionpack (5.2.0)
15 | actionview (= 5.2.0)
16 | activesupport (= 5.2.0)
17 | rack (~> 2.0)
18 | rack-test (>= 0.6.3)
19 | rails-dom-testing (~> 2.0)
20 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
21 | actionview (5.2.0)
22 | activesupport (= 5.2.0)
23 | builder (~> 3.1)
24 | erubi (~> 1.4)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
27 | activejob (5.2.0)
28 | activesupport (= 5.2.0)
29 | globalid (>= 0.3.6)
30 | activemodel (5.2.0)
31 | activesupport (= 5.2.0)
32 | activerecord (5.2.0)
33 | activemodel (= 5.2.0)
34 | activesupport (= 5.2.0)
35 | arel (>= 9.0)
36 | activestorage (5.2.0)
37 | actionpack (= 5.2.0)
38 | activerecord (= 5.2.0)
39 | marcel (~> 0.3.1)
40 | activesupport (5.2.0)
41 | concurrent-ruby (~> 1.0, >= 1.0.2)
42 | i18n (>= 0.7, < 2)
43 | minitest (~> 5.1)
44 | tzinfo (~> 1.1)
45 | addressable (2.5.2)
46 | public_suffix (>= 2.0.2, < 4.0)
47 | ancestry (3.0.2)
48 | activerecord (>= 3.2.0)
49 | annotate (2.7.2)
50 | activerecord (>= 3.2, < 6.0)
51 | rake (>= 10.4, < 13.0)
52 | archive-zip (0.11.0)
53 | io-like (~> 0.3.0)
54 | arel (9.0.0)
55 | ast (2.4.0)
56 | autoprefixer-rails (9.0.0)
57 | execjs
58 | bindex (0.5.0)
59 | binding_of_caller (0.8.0)
60 | debug_inspector (>= 0.0.1)
61 | bootsnap (1.3.1)
62 | msgpack (~> 1.0)
63 | bootstrap (4.1.3)
64 | autoprefixer-rails (>= 6.0.3)
65 | popper_js (>= 1.12.9, < 2)
66 | sass (>= 3.5.2)
67 | builder (3.2.3)
68 | byebug (10.0.2)
69 | capybara (3.4.2)
70 | addressable
71 | mini_mime (>= 0.1.3)
72 | nokogiri (~> 1.8)
73 | rack (>= 1.6.0)
74 | rack-test (>= 0.6.3)
75 | xpath (~> 3.1)
76 | childprocess (0.9.0)
77 | ffi (~> 1.0, >= 1.0.11)
78 | chromedriver-helper (1.2.0)
79 | archive-zip (~> 0.10)
80 | nokogiri (~> 1.8)
81 | coderay (1.1.2)
82 | coffee-rails (4.2.2)
83 | coffee-script (>= 2.2.0)
84 | railties (>= 4.0.0)
85 | coffee-script (2.4.1)
86 | coffee-script-source
87 | execjs
88 | coffee-script-source (1.12.2)
89 | concurrent-ruby (1.0.5)
90 | connection_pool (2.2.2)
91 | crass (1.0.4)
92 | database_cleaner (1.7.0)
93 | debug_inspector (0.0.3)
94 | decent_exposure (3.0.2)
95 | activesupport (>= 4.0)
96 | diff-lcs (1.3)
97 | domain_name (0.5.20180417)
98 | unf (>= 0.0.5, < 1.0.0)
99 | erubi (1.7.1)
100 | execjs (2.7.0)
101 | factory_bot (4.10.0)
102 | activesupport (>= 3.0.0)
103 | factory_bot_rails (4.10.0)
104 | factory_bot (~> 4.10.0)
105 | railties (>= 3.0.0)
106 | ffi (1.9.25)
107 | globalid (0.4.1)
108 | activesupport (>= 4.2.0)
109 | http-cookie (1.0.3)
110 | domain_name (~> 0.5)
111 | httparty (0.16.2)
112 | multi_xml (>= 0.5.2)
113 | i18n (1.0.1)
114 | concurrent-ruby (~> 1.0)
115 | io-like (0.3.0)
116 | jbuilder (2.7.0)
117 | activesupport (>= 4.2.0)
118 | multi_json (>= 1.2)
119 | jquery-rails (4.3.3)
120 | rails-dom-testing (>= 1, < 3)
121 | railties (>= 4.2.0)
122 | thor (>= 0.14, < 2.0)
123 | listen (3.1.5)
124 | rb-fsevent (~> 0.9, >= 0.9.4)
125 | rb-inotify (~> 0.9, >= 0.9.7)
126 | ruby_dep (~> 1.2)
127 | loofah (2.2.2)
128 | crass (~> 1.0.2)
129 | nokogiri (>= 1.5.9)
130 | mail (2.7.0)
131 | mini_mime (>= 0.1.1)
132 | marcel (0.3.2)
133 | mimemagic (~> 0.3.2)
134 | mechanize (2.7.6)
135 | domain_name (~> 0.5, >= 0.5.1)
136 | http-cookie (~> 1.0)
137 | mime-types (>= 1.17.2)
138 | net-http-digest_auth (~> 1.1, >= 1.1.1)
139 | net-http-persistent (>= 2.5.2)
140 | nokogiri (~> 1.6)
141 | ntlm-http (~> 0.1, >= 0.1.1)
142 | webrobots (>= 0.0.9, < 0.2)
143 | method_source (0.9.0)
144 | mime-types (3.1)
145 | mime-types-data (~> 3.2015)
146 | mime-types-data (3.2016.0521)
147 | mimemagic (0.3.2)
148 | mini_mime (1.0.0)
149 | mini_portile2 (2.3.0)
150 | minitest (5.11.3)
151 | msgpack (1.2.4)
152 | multi_json (1.13.1)
153 | multi_xml (0.6.0)
154 | net-http-digest_auth (1.4.1)
155 | net-http-persistent (3.0.0)
156 | connection_pool (~> 2.2)
157 | nio4r (2.3.1)
158 | nokogiri (1.8.4)
159 | mini_portile2 (~> 2.3.0)
160 | ntlm-http (0.1.1)
161 | oj (3.6.5)
162 | ox (2.9.4)
163 | parallel (1.12.1)
164 | parser (2.5.1.2)
165 | ast (~> 2.4.0)
166 | pg (1.0.0)
167 | popper_js (1.14.3)
168 | powerpack (0.1.2)
169 | pry (0.11.3)
170 | coderay (~> 1.1.0)
171 | method_source (~> 0.9.0)
172 | pry-rails (0.3.6)
173 | pry (>= 0.10.4)
174 | public_suffix (3.0.2)
175 | puma (3.12.0)
176 | rack (2.0.5)
177 | rack-proxy (0.6.4)
178 | rack
179 | rack-test (1.1.0)
180 | rack (>= 1.0, < 3)
181 | rails (5.2.0)
182 | actioncable (= 5.2.0)
183 | actionmailer (= 5.2.0)
184 | actionpack (= 5.2.0)
185 | actionview (= 5.2.0)
186 | activejob (= 5.2.0)
187 | activemodel (= 5.2.0)
188 | activerecord (= 5.2.0)
189 | activestorage (= 5.2.0)
190 | activesupport (= 5.2.0)
191 | bundler (>= 1.3.0)
192 | railties (= 5.2.0)
193 | sprockets-rails (>= 2.0.0)
194 | rails-dom-testing (2.0.3)
195 | activesupport (>= 4.2.0)
196 | nokogiri (>= 1.6)
197 | rails-html-sanitizer (1.0.4)
198 | loofah (~> 2.2, >= 2.2.2)
199 | railties (5.2.0)
200 | actionpack (= 5.2.0)
201 | activesupport (= 5.2.0)
202 | method_source
203 | rake (>= 0.8.7)
204 | thor (>= 0.18.1, < 2.0)
205 | rainbow (3.0.0)
206 | rake (12.3.1)
207 | rb-fsevent (0.10.3)
208 | rb-inotify (0.9.10)
209 | ffi (>= 0.5.0, < 2)
210 | rspec-core (3.7.1)
211 | rspec-support (~> 3.7.0)
212 | rspec-expectations (3.7.0)
213 | diff-lcs (>= 1.2.0, < 2.0)
214 | rspec-support (~> 3.7.0)
215 | rspec-mocks (3.7.0)
216 | diff-lcs (>= 1.2.0, < 2.0)
217 | rspec-support (~> 3.7.0)
218 | rspec-rails (3.7.2)
219 | actionpack (>= 3.0)
220 | activesupport (>= 3.0)
221 | railties (>= 3.0)
222 | rspec-core (~> 3.7.0)
223 | rspec-expectations (~> 3.7.0)
224 | rspec-mocks (~> 3.7.0)
225 | rspec-support (~> 3.7.0)
226 | rspec-support (3.7.1)
227 | rubocop (0.56.0)
228 | parallel (~> 1.10)
229 | parser (>= 2.5)
230 | powerpack (~> 0.1)
231 | rainbow (>= 2.2.2, < 4.0)
232 | ruby-progressbar (~> 1.7)
233 | unicode-display_width (~> 1.0, >= 1.0.1)
234 | rubocop-rails (1.5.0)
235 | railties (>= 3.0)
236 | rubocop (~> 0.53)
237 | ruby-progressbar (1.9.0)
238 | ruby_dep (1.5.0)
239 | rubyzip (1.2.1)
240 | sass (3.5.7)
241 | sass-listen (~> 4.0.0)
242 | sass-listen (4.0.0)
243 | rb-fsevent (~> 0.9, >= 0.9.4)
244 | rb-inotify (~> 0.9, >= 0.9.7)
245 | sass-rails (5.0.7)
246 | railties (>= 4.0.0, < 6)
247 | sass (~> 3.1)
248 | sprockets (>= 2.8, < 4.0)
249 | sprockets-rails (>= 2.0, < 4.0)
250 | tilt (>= 1.1, < 3)
251 | selenium-webdriver (3.13.1)
252 | childprocess (~> 0.5)
253 | rubyzip (~> 1.2)
254 | spring (2.0.2)
255 | activesupport (>= 4.2)
256 | spring-commands-rspec (1.0.4)
257 | spring (>= 0.9.1)
258 | spring-watcher-listen (2.0.1)
259 | listen (>= 2.7, < 4.0)
260 | spring (>= 1.2, < 3.0)
261 | sprockets (3.7.2)
262 | concurrent-ruby (~> 1.0)
263 | rack (> 1, < 3)
264 | sprockets-rails (3.2.1)
265 | actionpack (>= 4.0)
266 | activesupport (>= 4.0)
267 | sprockets (>= 3.0.0)
268 | thor (0.20.0)
269 | thread_safe (0.3.6)
270 | tilt (2.0.8)
271 | turbolinks (5.1.1)
272 | turbolinks-source (~> 5.1)
273 | turbolinks-source (5.1.0)
274 | tzinfo (1.2.5)
275 | thread_safe (~> 0.1)
276 | uglifier (4.1.16)
277 | execjs (>= 0.3.0, < 3)
278 | unf (0.1.4)
279 | unf_ext
280 | unf_ext (0.0.7.5)
281 | unicode-display_width (1.4.0)
282 | web-console (3.6.2)
283 | actionview (>= 5.0)
284 | activemodel (>= 5.0)
285 | bindex (>= 0.4.0)
286 | railties (>= 5.0)
287 | webpacker (3.5.5)
288 | activesupport (>= 4.2)
289 | rack-proxy (>= 0.6.1)
290 | railties (>= 4.2)
291 | webrobots (0.1.2)
292 | websocket-driver (0.7.0)
293 | websocket-extensions (>= 0.1.0)
294 | websocket-extensions (0.1.3)
295 | xpath (3.1.0)
296 | nokogiri (~> 1.8)
297 |
298 | PLATFORMS
299 | ruby
300 |
301 | DEPENDENCIES
302 | ancestry
303 | annotate
304 | binding_of_caller
305 | bootsnap (>= 1.1.0)
306 | bootstrap
307 | byebug
308 | capybara (>= 2.15, < 4.0)
309 | chromedriver-helper
310 | coffee-rails (~> 4.2)
311 | database_cleaner
312 | decent_exposure
313 | factory_bot_rails
314 | httparty
315 | jbuilder (~> 2.5)
316 | jquery-rails
317 | listen (>= 3.0.5, < 3.2)
318 | mechanize
319 | oj
320 | ox
321 | pg (>= 0.18, < 2.0)
322 | pry-rails
323 | puma (~> 3.11)
324 | rails (~> 5.2.0)
325 | rspec-rails
326 | rubocop (~> 0.56.0)
327 | rubocop-rails
328 | sass-rails (~> 5.0)
329 | selenium-webdriver
330 | spring
331 | spring-commands-rspec
332 | spring-watcher-listen (~> 2.0.0)
333 | turbolinks (~> 5)
334 | tzinfo-data
335 | uglifier (>= 1.3.0)
336 | web-console (>= 3.3.0)
337 | webpacker
338 |
339 | RUBY VERSION
340 | ruby 2.5.1p57
341 |
342 | BUNDLED WITH
343 | 1.16.3
344 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Ruby for Good
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # demand-progress
2 |
3 | ### A functioning democracy requires an engaged citizenry.
4 |
5 | Our stakeholder, [Demand Progress](https://demandprogress.org/) is focused on Internet Freedom, Open Government, and Financial Reform. It works on a bipartisan basis to make information about Congress available to the public through the Congressional Data Coalition. In order for civil society to hold government accountable they must keep track of shifting legislative schedules and content.
6 |
7 | It is difficult to follow when Congress has scheduled a committee/subcommittee hearing/meeting without a paid subscription to a news service that gathers this info.
8 |
9 | The Senate and House release meeting notices online in structured data formats, but there’s no well-designed publicly-available central place to see all the notices from the different committees or subcommittees. (The [closest attempt](https://www.govtrack.us/congress/committees/calendar) is run by GovTrack, and it is good but is missing key features).
10 |
11 | The Demand Progress team will build a user-friendly web based weekly agenda (calendar) for congressional hearings and markups. Users will be able to view the week with Monday at the top of the page, with items organized chronologically in a list, broken out by day of the week and hour. Each item will contain sufficient information about the proceeding, including who is holding it (committee and or subcommittee), the subject matter, a link to the committee website, the date and time, and where it can be watched online. With one click, users will be able to add these events to their own calendar, either singly or a set of items at a time. In addition, users will be able to visually identify changed calendar items updated in the last 24 hour. If time allows we will have a couple other stretch goals requested by our stakeholder including a tool for converting PDF's of pre-introduction legislation to plain text and/or a tool for parsing organizational sign-on lists for letters of support.
12 |
13 | Team Leads: [David Bock](https://github.com/bokmann) and [Joel Cahalan](https://github.com/compostbrain)
14 |
15 | ## Local setup
16 |
17 | ### Repo pulldown and clicky setup
18 |
19 | * Fork the repo in Github
20 | * Clone it to your local machine
21 | * Request a ProPublica API key: https://www.propublica.org/datastore/api/propublica-congress-api
22 |
23 | ### Install dependencies
24 |
25 | * Install ruby 2.3.1: `$ rbenv install 2.3.1` or `$ rvm install 2.3.1`
26 | * Install `bundler` gem: `$ gem install bundler`
27 | * Install ruby dependencies: `$ bundle install`
28 | * Install javascript dependencies: `$ yarn install`
29 |
30 | ### Rig up your environment
31 |
32 | * Create your database user in postgres: `$ createuser -s -r demand-progress`
33 | * Create your database and tables: `$ rails db:setup`
34 | * Set your propublica API key: `$ export PROPUBLICA_API_KEY=YOUR_API_KEY_VALUE`
35 | * Load the committees data: `$ rake committees:senate:load && rake committees:house:load`
36 |
37 | ### Fire it up!
38 |
39 | * Start the server: `rails server`
40 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Add your own tasks in files placed in lib/tasks ending in .rake,
4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
5 |
6 | require_relative "config/application"
7 |
8 | Rails.application.load_tasks
9 |
--------------------------------------------------------------------------------
/USER-STORIES.md:
--------------------------------------------------------------------------------
1 | # User Stories for Congressional Hearing and Markup Calendar
2 |
3 | * ### As a busy advocate, I want a well designed webpage containing a calendar of congressional hearings and markups (referred to as events hereafter) so that I can easily access and use the information.
4 | * ### As a busy advocate, I want to have the text in the calendar formatted for easy visual scanning and simple copy and paste, so that I can transfer information to other applications.
5 | * ### As a busy public advocate, I want be able to click a particular event and have it added to my personal calendar app (google calendar, apple calendar, outlook calendar) so that I can easily add it to my schedule, with relevant information about the event.
6 | * ### As a busy public advocate, I want a link in the event details to the relevant committee page, so that I can easily access more information if needed.
7 | * ### As a busy public advocate, I want an indicator of when the event was added (i.e., within the last 24 hours), so that I can see at a glance when a new event has been added.
8 | * ### As a busy public advocate, I want an RSS feed, so that I can get updates.
9 | * ### As a busy public advocate, I want a link to the livestream of the event, so that I can quickly view proceeding I am interested in.
10 | * ### As a busy public advocate, I want sufficient information in the calendar so I can tell whether it contains information that might be of interest to me, including: the title of the event, who is holding it, where it is being held, when it is being held, any witnesses, any bills under consideration, whether it is open or closed, and any nominations under consideration.
11 |
12 | # User Stories for converting pdfs of pre-introduced legislation to plain text
13 |
14 | * ### As a busy advocate, I want a tool that converts pdf of a bill to plain text with line numbers, headers, and footers removed, so that I can make better use of the text.
15 | * ### As a busy advocate, I want a tool that will compare two versions of a bill and show what has been added or removed, so that I can easily track changes.
16 |
17 | # User Stories for organizational sign-on lists for letter of support.
18 |
19 | * ### As a busy advocate, I want a tool that will check a list for proper capitalization, so that formatting is automated.
20 | * ### As a busy advocate, I want a tool that will remove duplicates from a list, so that formatting is automated.
21 | * ### As a busy advocate, I want a tool that will ensure one space between words, so that formatting is automated.
22 | * ### As a busy advocate, I want a tool that checks for typos, so that formatting is automated.
23 | * ### As a busy advocate, I want a tool that alphabetizes the list, so that formatting is automated.
24 | * ### As a busy advocate, I want a tool that formats the list into a comma separated paragraph, so that formatting is automated.
25 |
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/app/assets/images/.keep
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
5 | // vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require rails-ujs
14 | //= require activestorage
15 | //= require jquery3
16 | //= require popper
17 | //= require bootstrap-sprockets
18 | //= require turbolinks
19 | //= require_tree .
20 |
--------------------------------------------------------------------------------
/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/app/assets/javascripts/channels/.keep
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.scss:
--------------------------------------------------------------------------------
1 | @import "bootstrap";
2 | @import "events";
3 |
4 | /* Sticky footer styles
5 | -------------------------------------------------- */
6 | html {
7 | position: relative;
8 | min-height: 100%;
9 | }
10 | body {
11 | /* Margin bottom by footer height */
12 | margin-bottom: 60px;
13 | }
14 | .footer {
15 | position: absolute;
16 | bottom: 0;
17 | width: 100%;
18 | /* Set the fixed height of the footer here */
19 | height: 60px;
20 | background-color: #f5f5f5;
21 | }
22 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/events.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the Events controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/organizations.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the committees controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/scaffolds.scss:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #fff;
3 | color: #333;
4 | margin: 33px;
5 | font-family: verdana, arial, helvetica, sans-serif;
6 | font-size: 13px;
7 | line-height: 18px;
8 | }
9 |
10 | p, ol, ul, td {
11 | font-family: verdana, arial, helvetica, sans-serif;
12 | font-size: 13px;
13 | line-height: 18px;
14 | }
15 |
16 | pre {
17 | background-color: #eee;
18 | padding: 10px;
19 | font-size: 11px;
20 | }
21 |
22 | a {
23 | color: #000;
24 |
25 | &:visited {
26 | color: #666;
27 | }
28 |
29 | &:hover {
30 | color: #fff;
31 | background-color: #000;
32 | }
33 | }
34 |
35 | th {
36 | padding-bottom: 5px;
37 | }
38 |
39 | td {
40 | padding: 0 5px 7px;
41 | }
42 |
43 | div {
44 | &.field, &.actions {
45 | margin-bottom: 10px;
46 | }
47 | }
48 |
49 | #notice {
50 | color: green;
51 | }
52 |
53 | .field_with_errors {
54 | padding: 2px;
55 | background-color: red;
56 | display: table;
57 | }
58 |
59 | #error_explanation {
60 | width: 450px;
61 | border: 2px solid red;
62 | padding: 7px 7px 0;
63 | margin-bottom: 20px;
64 | background-color: #f0f0f0;
65 |
66 | h2 {
67 | text-align: left;
68 | font-weight: bold;
69 | padding: 5px 5px 5px 15px;
70 | font-size: 12px;
71 | margin: -7px -7px 0;
72 | background-color: #c00;
73 | color: #fff;
74 | }
75 |
76 | ul li {
77 | font-size: 12px;
78 | list-style: square;
79 | }
80 | }
81 |
82 | label {
83 | display: block;
84 | }
85 |
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Channel < ActionCable::Channel::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Connection < ActionCable::Connection::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationController < ActionController::Base
4 | end
5 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/app/controllers/events_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class EventsController < ApplicationController
4 | before_action :find_event, :only => %i[show]
5 |
6 | def index
7 | @events = Event.this_week_forward.by_occurs_at
8 | end
9 |
10 | def show
11 | @event
12 | end
13 |
14 | private
15 |
16 | def find_event
17 | @event = Event.find(params[:id])
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/app/controllers/organizations_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class OrganizationsController < ApplicationController
4 | before_action :set_organization, only: [:show, :edit, :update, :destroy]
5 |
6 | # GET /organizations
7 | # GET /organizations.json
8 | def index
9 | @organizations = Organization.all
10 | end
11 |
12 | # GET /organizations/1
13 | # GET /organizations/1.json
14 | def show
15 | end
16 |
17 | # GET /organizations/new
18 | def new
19 | @organization = Organization.new
20 | end
21 |
22 | # GET /organizations/1/edit
23 | def edit
24 | end
25 |
26 | # POST /organizations
27 | # POST /organizations.json
28 | def create
29 | @organization = Organization.new(organization_params)
30 |
31 | respond_to do |format|
32 | if @organization.save
33 | format.html { redirect_to @organization, notice: "Organization was successfully created." }
34 | format.json { render :show, status: :created, location: @organization }
35 | else
36 | format.html { render :new }
37 | format.json { render json: @organization.errors, status: :unprocessable_entity }
38 | end
39 | end
40 | end
41 |
42 | # PATCH/PUT /organizations/1
43 | # PATCH/PUT /organizations/1.json
44 | def update
45 | respond_to do |format|
46 | if @organization.update(organization_params)
47 | format.html { redirect_to @organization, notice: "Organization was successfully updated." }
48 | format.json { render :show, status: :ok, location: @organization }
49 | else
50 | format.html { render :edit }
51 | format.json { render json: @organization.errors, status: :unprocessable_entity }
52 | end
53 | end
54 | end
55 |
56 | # DELETE /organizations/1
57 | # DELETE /organizations/1.json
58 | def destroy
59 | @organization.destroy
60 | respond_to do |format|
61 | format.html { redirect_to organizations_url, notice: "Organization was successfully destroyed." }
62 | format.json { head :no_content }
63 | end
64 | end
65 |
66 | private
67 | # Use callbacks to share common setup or constraints between actions.
68 | def set_organization
69 | @organization = Organization.find(params[:id])
70 | end
71 |
72 | # Never trust parameters from the scary internet, only allow the white list through.
73 | def organization_params
74 | params.require(:organization).permit(:name, :code, :website)
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationHelper
4 | end
5 |
--------------------------------------------------------------------------------
/app/helpers/committees_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module CommitteesHelper
4 | end
5 |
--------------------------------------------------------------------------------
/app/javascript/packs/AgendaList/agenda_manager.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Checkbox from './checkbox_committee';
3 | import DailyAgenda from './daily_agenda'
4 | import { remove } from 'lodash'
5 |
6 | /*TODO:
7 | ` 1) create sample daily_agenda data. [{ daily_agenda: , events []}]
8 | 2) filter out the daily agenda date by if the committe name is checked
9 | 3) change the proptype to handle the committee info being an array
10 | 4) in the event.js adjust the committee info to map an array
11 | */
12 | const committee_info = [
13 | {
14 | committee_code: "C01",
15 | committee_name: "Committee One",
16 | count: 23
17 | },
18 | {
19 | committee_code: "C02",
20 | committee_name: "Committee Two",
21 | count: 67
22 | },
23 | {
24 | committee_code: "C03",
25 | committee_name: "Committee Three",
26 | count: 98
27 | }
28 | ];
29 |
30 |
31 |
32 | class AgendaManager extends Component {
33 | state = {
34 | selectedCommittees: []
35 | }
36 |
37 | handleFilter = (label, isSelected) => {
38 | const { selectedCommittees } = this.state
39 |
40 | let currentCommitteeArray = selectedCommittees
41 | if (isSelected) {
42 | currentCommitteeArray = [...currentCommitteeArray, label]
43 | } else {
44 | currentCommitteeArray = remove(currentCommitteeArray, (committeeLabel) => committeeLabel !== label)
45 | }
46 |
47 | this.setState({ selectedCommittees: currentCommitteeArray })
48 | }
49 |
50 | createCheckbox = item => (
51 |
56 | )
57 |
58 | createCheckboxes = () => (
59 | committee_info.map(this.createCheckbox)
60 | )
61 |
62 | render() {
63 | return (
64 |
65 |
66 |
67 |
68 |
69 |
70 |
Filter By Committee
71 | {this.createCheckboxes()}
72 |
73 |
74 |
75 | );
76 | }
77 | }
78 |
79 | export default AgendaManager;
80 |
--------------------------------------------------------------------------------
/app/javascript/packs/AgendaList/checkbox_committee.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | class CheckboxCommittee extends Component {
4 | state = {
5 | isChecked: false
6 | }
7 |
8 | toggleCheckboxCommitteeChange = () => {
9 | const { handleCheckboxCommitteeChange, label } = this.props;
10 | const { isChecked } = this.state
11 |
12 | const newState = isChecked ? false : true
13 | this.setState({ isChecked: newState })
14 |
15 | handleCheckboxCommitteeChange(label, newState);
16 | }
17 |
18 | render() {
19 | const { label } = this.props;
20 | const { isChecked } = this.state;
21 |
22 | return (
23 |
24 |
25 |
30 | {label}
31 |
32 |
33 | );
34 | }
35 | }
36 |
37 | CheckboxCommittee.propTypes = {
38 | label: PropTypes.string,
39 | handleCheckboxCommitteeChange: PropTypes.func
40 | }
41 |
42 | export default CheckboxCommittee;
43 |
--------------------------------------------------------------------------------
/app/javascript/packs/AgendaList/daily_agenda.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Event from './event'
4 |
5 | export function foo(events, selectedCommittees){
6 | if (selectedCommittees.length === 0) { return events.map(event => { return })}
7 |
8 | return events.map((event) => {
9 | return selectedCommittees.includes(event.committee_name) &&
10 | })
11 | }
12 |
13 | export default function DailyAgenda({agenda_date, events, selectedCommittees}){
14 | let returnedEvents = foo(events, selectedCommittees);
15 |
16 | return(
17 |
18 |
{agenda_date}
19 |
{returnedEvents}
20 |
21 | )
22 | }
23 |
24 | DailyAgenda.propTypes = {
25 | agenda_date: PropTypes.string,
26 | events: PropTypes.arrayOf(PropTypes.shape({
27 | id: PropTypes.number,
28 | topic: PropTypes.string,
29 | occurs_at: PropTypes.string,
30 | location: PropTypes.string,
31 | committee_name: PropTypes.string,
32 | subcommittee_name: PropTypes.string
33 | })),
34 | selectedCommittees: PropTypes.array
35 | }
36 |
37 | DailyAgenda.defaultProps = {
38 | agenda_date: '2018-06-09',
39 | selectedCommittees: []
40 | }
41 |
--------------------------------------------------------------------------------
/app/javascript/packs/AgendaList/event.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Moment from 'react-moment';
4 | import 'moment-timezone';
5 |
6 | export function formatSubcommitteeTitle(subcommittee_name){
7 | if (subcommittee_name !== undefined){
8 | return ({subcommittee_name} )
9 | }
10 | }
11 | export function formatOccurDate(occurs_at){
12 | return (
13 |
14 | {occurs_at}
15 |
16 | )
17 | }
18 |
19 | export default function Event({event}){
20 | let formattedSubcommitteeTitle = formatSubcommitteeTitle(event.subcommittee_name)
21 | let formattedOccursAt = formatOccurDate(event.occurs_at)
22 | return (
23 |
24 |
25 | {event.committee_name}
26 | {formattedSubcommitteeTitle}
27 |
28 |
29 | Location
30 | {event.location}
31 |
32 |
{event.topic}
33 |
34 | {formattedOccursAt}
35 |
36 |
37 | )
38 | }
39 |
40 | Event.propTypes = {
41 | event: PropTypes.shape({
42 | id: PropTypes.number,
43 | topic: PropTypes.string,
44 | occurs_at: PropTypes.string,
45 | location: PropTypes.string,
46 | committee_name: PropTypes.string,
47 | subcommittee_name: PropTypes.string
48 | })
49 | }
50 |
51 | Event.defaultProps = {
52 | event: [
53 | {
54 | committee_name: "This is a committee name",
55 | subcommittee_name: "This is a subcommittee name",
56 | topic: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin nec dui a leo mattis facilisis. Nulla eget massa vel elit ullamcorper laoreet. Nullam non ipsum imperdiet, efficitur metus vel, malesuada nisl. Curabitur laoreet, sem sit amet pellentesque tempor, est risus varius lacus, eget molestie elit ipsum a mauris.",
57 | location: "MVC-101",
58 | occurs_at: '2018-07-01 23:00:00'
59 | }
60 | ]
61 | }
62 |
--------------------------------------------------------------------------------
/app/javascript/packs/AgendaList/tmp_list.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function TmpList({committee_names}){
4 | displayNames = committee_names.map( (name) => {{name}
})
5 | return(
6 | {displayNames}
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/app/javascript/packs/agenda.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import PropTypes from 'prop-types'
4 | import { sample } from 'lodash'
5 | import AgendaManager from './AgendaList/agenda_manager'
6 |
7 | const COMMITTEE_NAMES = ["Committee One", "Committee Two", "Committee Three"]
8 |
9 | const injectCommitteeName = (event) => {
10 | return { ...event, committee_name: sample(COMMITTEE_NAMES) }
11 | }
12 |
13 | export default function Agenda({events}){
14 | let agendaHash = JSON.parse(events)
15 | agendaHash = agendaHash.map(injectCommitteeName)
16 |
17 | return (
18 |
21 | )
22 | }
23 |
24 | Agenda.propTypes = {
25 | events: PropTypes.string
26 | }
27 |
28 | document.addEventListener('DOMContentLoaded', () => {
29 | const node = document.getElementById('agenda-events')
30 | const data = node.getAttribute('data')
31 | ReactDOM.render(
32 | ,
33 | document.body.appendChild(document.createElement('div')),
34 | )
35 | })
36 |
--------------------------------------------------------------------------------
/app/javascript/packs/application.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console:0 */
2 | // This file is automatically compiled by Webpack, along with any other files
3 | // present in this directory. You're encouraged to place your actual application logic in
4 | // a relevant structure within app/javascript and only use these pack files to reference
5 | // that code so it'll be compiled.
6 | //
7 | // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
8 | // layout file, like app/views/layouts/application.html.erb
9 |
10 | console.log('Hello World from Webpacker')
11 |
--------------------------------------------------------------------------------
/app/javascript/packs/hello_react.jsx:
--------------------------------------------------------------------------------
1 | // Run this example by adding <%= javascript_pack_tag 'hello_react' %> to the head of your layout file,
2 | // like app/views/layouts/application.html.erb. All it does is render Hello React
at the bottom
3 | // of the page.
4 |
5 | import React from 'react'
6 | import ReactDOM from 'react-dom'
7 | import PropTypes from 'prop-types'
8 |
9 | const Hello = props => (
10 | Hello {props.name}!
11 | )
12 |
13 | Hello.defaultProps = {
14 | name: 'David'
15 | }
16 |
17 | Hello.propTypes = {
18 | name: PropTypes.string
19 | }
20 |
21 | document.addEventListener('DOMContentLoaded', () => {
22 | ReactDOM.render(
23 | ,
24 | document.body.appendChild(document.createElement('div')),
25 | )
26 | })
27 |
--------------------------------------------------------------------------------
/app/javascript/packs/tmp_data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "agenda_date": "2018-06-17" ,
4 | "events":[
5 | {
6 | "id": 7,
7 | "topic": "topic name",
8 | "location": "MVC 1337",
9 | "occurs_at": "2018-07-01 08:00:00",
10 | "committee_info": [
11 | {
12 | "committe_name": "http://react.tips/checkboxes-in-react/",
13 | "subcommittee_name": "subcommittee name"
14 | }
15 | ]
16 | }
17 | ]
18 | }
19 | ]
20 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationJob < ActiveJob::Base
4 | end
5 |
--------------------------------------------------------------------------------
/app/lib/house_events_scraper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # HouseEventsScraper is used to load House hearings from the house's website:
4 | # https://docs.house.gov/Committee/Calendar/ByWeek.aspx
5 | # It currently just grabs this week and next week's hearings. We'll need to make
6 | # this more sophisticated in the future.
7 | #
8 | # Example usage:
9 | # HouseEventsScraper.run
10 |
11 | class HouseEventsScraper
12 | def run
13 | persist!(xml_events)
14 | end
15 |
16 | def self.run
17 | new.run
18 | end
19 |
20 | private
21 |
22 | # TODO: this needs to be more sophisticated - need to resolve updates, deletes
23 | def persist!(xml_events)
24 | xml_events.each do |xml_event|
25 | attributes = EventParser.parse(xml_event)
26 | Event.create(attributes)
27 | end
28 | end
29 |
30 | def xml_events
31 | meeting_details_pages.map do |page|
32 | form = page.form_with(id: "Form1")
33 | form.add_field!("__EVENTTARGET", "ctl00$MainContent$LinkButtonDownloadMtgXML")
34 | file = form.submit
35 | file.body
36 | end
37 | end
38 |
39 | def meeting_details_pages
40 | index_page.links_with(text: "Meeting Details").map(&:click)
41 | end
42 |
43 | def index_page
44 | Mechanize.new.get("https://docs.house.gov/Committee/Calendar/ByWeek.aspx")
45 | end
46 |
47 | class EventParser
48 | def initialize(event_xml)
49 | @event_xml = Ox.load(event_xml, mode: :hash)
50 | end
51 |
52 | def parse
53 | {
54 | topic: topic,
55 | raw_location: raw_location,
56 | occurs_at: occurs_at
57 | }
58 | end
59 |
60 | def self.parse(event_xml)
61 | new(event_xml).parse
62 | end
63 |
64 | private
65 |
66 | def topic
67 | meeting_details[:'meeting-title']
68 | end
69 |
70 | def raw_location
71 | meeting_details[:'meeting-location']
72 | end
73 |
74 | def meeting_details
75 | meeting_details ||=
76 | @event_xml[:"committee-meeting"][5][:'meeting-details']
77 | end
78 |
79 | def occurs_at
80 | DateTime.parse(occurs_at_string)
81 | end
82 |
83 | def occurs_at_string
84 | [
85 | date_details[:'calendar-date'],
86 | date_details[:'start-time'],
87 | '-05:00'
88 | ].join(' ')
89 | end
90 |
91 | def date_details
92 | @date_details ||= meeting_details[:'meeting-date']
93 | end
94 | end
95 | end
96 |
--------------------------------------------------------------------------------
/app/lib/organization_loader.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # The CommmitteeLoader is used to load House/Senate committee/subcommittee
4 | # relationships using data populated from the free ProPublica API.
5 | #
6 | # https://github.com/rails/rails/issues/13142#issuecomment-29969951
7 | #
8 | # Example usage:
9 | #
10 | # OrganizationLoader.load(chamber: :house, parent: Organization.find_by(name: "House"))
11 | class OrganizationLoader
12 | attr_reader :session, :chamber, :parent
13 |
14 | # Convenience, allowing OrganizationLoader.load(...) syntax
15 | class << self
16 | def load(session: 115, chamber:, parent:)
17 | new(session: session, chamber: chamber, parent: parent).load
18 | end
19 | end
20 |
21 | def initialize(session: 115, chamber:, parent:)
22 | @session = session
23 | @chamber = chamber
24 | @parent = parent
25 | end
26 |
27 | def load
28 | committee_response = HTTParty.get(url, headers).body
29 |
30 | committees = Oj.load(committee_response)["results"][0]["committees"]
31 |
32 | committees.each do |committee|
33 | new_committee = Organization.create!(name: committee["name"], code: committee["id"], website: committee["url"], parent: parent)
34 |
35 | committee["subcommittees"].each do |sub|
36 |
37 | sub_response = HTTParty.get(sub["api_uri"], headers).body
38 |
39 | sub_result = Oj.load(sub_response)["results"][0]
40 |
41 | # NOTE: I don't think the subcommittees actually have a website,
42 | # but should investigate these subcommittee results further
43 | subcommittee = Organization.create!(name: sub_result["name"], code: sub_result["id"], parent: new_committee)
44 | end
45 | end
46 | end
47 |
48 | private
49 |
50 | def url
51 | "https://api.propublica.org/congress/v1/#{session}/#{chamber}/committees.json"
52 | end
53 |
54 | # ProPublica API requires the API key in the headers
55 | def headers
56 | { headers: { "X-API-Key" => ENV.fetch("PROPUBLICA_API_KEY") } }
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationMailer < ActionMailer::Base
4 | default from: "from@example.com"
5 | layout "mailer"
6 | end
7 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationRecord < ActiveRecord::Base
4 | self.abstract_class = true
5 | end
6 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/app/models/concerns/.keep
--------------------------------------------------------------------------------
/app/models/event.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # == Schema Information
3 | #
4 | # Table name: events
5 | #
6 | # id :integer not null, primary key
7 | # topic :text
8 | # occurs_at :datetime
9 | # location :string
10 | # created_at :datetime not null
11 | # updated_at :datetime not null
12 | # raw_location :string
13 | #
14 |
15 |
16 | class Event < ApplicationRecord
17 | def self.this_week_forward
18 | where("occurs_at >= ?", Time.current.beginning_of_week)
19 | end
20 |
21 | scope :by_occurs_at, -> { order(:occurs_at) }
22 |
23 | serialize :raw_location
24 | before_save :cache_formatted_location,
25 | if: Proc.new { changed.include? 'raw_location' }
26 |
27 | def self.agenda_by_date
28 | upcoming_agenda_dates.map { |date| { agenda_date: date, events: upcoming_events.select { |event| event.occurs_at.to_date == date }
29 | }}
30 | end
31 |
32 | private
33 |
34 | def upcoming_agenda_dates
35 | Event.this_week_forward.pluck(Arel.sql("distinct date(occurs_at)"))
36 | end
37 |
38 | def upcoming_events
39 | Event.this_week_forward.sort_by(&:occurs_at)
40 | end
41 |
42 | # TODO: Here is an example of a location that is not at the capitol complex:
43 | # {:field=> {
44 | # :"building-name"=>"Alpena Community College - Granum Theatre",
45 | # :"street-address"=>"665 Johnson Street",
46 | # :city=>"Alpena",
47 | # :state=>[
48 | # {:"postal-code"=>"MI"},
49 | # {:"state-fullname"=>"Michigan"}
50 | # ],
51 | # :zip=>"49707"}}
52 | # For now, events that are not at the capitol complex are not formated and
53 | # cached, so their `location` will be empty.
54 | def cache_formatted_location
55 | building_name = raw_location[:'capitol-complex'][:building]
56 | room_name = raw_location[:'capitol-complex'][:room]
57 | self.location = "Building: #{building_name}, Room: #{room_name}"
58 | rescue NoMethodError
59 | logger.debug "Unknown location format found. Here is the structure:\n"
60 | logger.debug raw_location.inspect
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/app/models/organization.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # == Schema Information
3 | #
4 | # Table name: organizations
5 | #
6 | # id :integer not null, primary key
7 | # name :string
8 | # code :string
9 | # website :string
10 | # created_at :datetime not null
11 | # updated_at :datetime not null
12 | # ancestry :string
13 | #
14 |
15 |
16 | class Organization < ApplicationRecord
17 | has_ancestry
18 |
19 | class << self
20 | # Create an Array of Hashes containing the Committees for House and
21 | # Senate. This is intended for conversion into JSON, and used by
22 | # the frontend for a list of committees (for checkboxes, for example)
23 | #
24 | # Example:
25 | #
26 | # Organization.filter_list
27 | #
28 | # [
29 | # {:name=>"Caucus on International Narcotics Control", :code=>"SCNC", :count=>0},
30 | # .....
31 | # {:name=>"Committee on Agriculture, Nutrition, and Forestry", :code=>"SSAF", :count=>4},
32 | # ]
33 | #
34 | # TODO: Using a random count for now. until we get actual events loaded
35 | # into the system
36 | def filter_list
37 | arrange_as_array.
38 | select { |c| c.depth == 2 }.
39 | reduce([]) { |m, i| m << { name: i.name, code: i.code, count: rand(10) } }
40 | end
41 |
42 | # https://github.com/stefankroes/ancestry/wiki/arrange_as_array
43 | def arrange_as_array(options={}, hash=nil)
44 | hash ||= arrange(options)
45 |
46 | arr = []
47 |
48 | hash.each do |node, children|
49 | arr << node
50 | arr += arrange_as_array(options, children) unless children.empty?
51 | end
52 |
53 | arr
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/app/views/events/index.html.erb:
--------------------------------------------------------------------------------
1 | <%= javascript_pack_tag 'agenda', data: raw(@events.to_json), id: 'agenda-events' %>
2 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DemandProgress
5 | <%= csrf_meta_tags %>
6 | <%= csp_meta_tag %>
7 |
8 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
9 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
10 |
11 |
12 |
13 |
14 |
15 |
16 | <%= render 'shared/nav' %>
17 | <%= yield %>
18 |
19 |
20 |
21 | <%= render 'shared/footer' %>
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/views/shared/_footer.html.erb:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/app/views/shared/_nav.html.erb:
--------------------------------------------------------------------------------
1 |
32 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
5 | load Gem.bin_path("bundler", "bundle")
6 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | begin
5 | load File.expand_path("../spring", __FILE__)
6 | rescue LoadError => e
7 | raise unless e.message.include?("spring")
8 | end
9 | APP_PATH = File.expand_path("../config/application", __dir__)
10 | require_relative "../config/boot"
11 | require "rails/commands"
12 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | begin
5 | load File.expand_path("../spring", __FILE__)
6 | rescue LoadError => e
7 | raise unless e.message.include?("spring")
8 | end
9 | require_relative "../config/boot"
10 | require "rake"
11 | Rake.application.run
12 |
--------------------------------------------------------------------------------
/bin/rspec:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | begin
5 | load File.expand_path("../spring", __FILE__)
6 | rescue LoadError => e
7 | raise unless e.message.include?("spring")
8 | end
9 | require "bundler/setup"
10 | load Gem.bin_path("rspec-core", "rspec")
11 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "fileutils"
5 | include FileUtils
6 |
7 | # path to your application root.
8 | APP_ROOT = File.expand_path("..", __dir__)
9 |
10 | def system!(*args)
11 | system(*args) || abort("\n== Command #{args} failed ==")
12 | end
13 |
14 | chdir APP_ROOT do
15 | # This script is a starting point to setup your application.
16 | # Add necessary setup steps to this file.
17 |
18 | puts "== Installing dependencies =="
19 | system! "gem install bundler --conservative"
20 | system("bundle check") || system!("bundle install")
21 |
22 | # Install JavaScript dependencies if using Yarn
23 | system("bin/yarn")
24 |
25 | # puts "\n== Copying sample files =="
26 | # unless File.exist?('config/database.yml')
27 | # cp 'config/database.yml.sample', 'config/database.yml'
28 | # end
29 |
30 | puts "\n== Preparing database =="
31 | system! "bin/rails db:setup"
32 |
33 | puts "\n== Removing old logs and tempfiles =="
34 | system! "bin/rails log:clear tmp:clear"
35 |
36 | puts "\n== Restarting application server =="
37 | system! "bin/rails restart"
38 | end
39 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | # This file loads spring without using Bundler, in order to be fast.
5 | # It gets overwritten when you run the `spring binstub` command.
6 |
7 | unless defined?(Spring)
8 | require "rubygems"
9 | require "bundler"
10 |
11 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
12 | spring = lockfile.specs.detect { |spec| spec.name == "spring" }
13 | if spring
14 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
15 | gem "spring", spring.version
16 | require "spring/binstub"
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "fileutils"
5 | include FileUtils
6 |
7 | # path to your application root.
8 | APP_ROOT = File.expand_path("..", __dir__)
9 |
10 | def system!(*args)
11 | system(*args) || abort("\n== Command #{args} failed ==")
12 | end
13 |
14 | chdir APP_ROOT do
15 | # This script is a way to update your development environment automatically.
16 | # Add necessary update steps to this file.
17 |
18 | puts "== Installing dependencies =="
19 | system! "gem install bundler --conservative"
20 | system("bundle check") || system!("bundle install")
21 |
22 | # Install JavaScript dependencies if using Yarn
23 | # system('bin/yarn')
24 |
25 | puts "\n== Updating database =="
26 | system! "bin/rails db:migrate"
27 |
28 | puts "\n== Removing old logs and tempfiles =="
29 | system! "bin/rails log:clear tmp:clear"
30 |
31 | puts "\n== Restarting application server =="
32 | system! "bin/rails restart"
33 | end
34 |
--------------------------------------------------------------------------------
/bin/webpack:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
5 | ENV["NODE_ENV"] ||= "development"
6 |
7 | require "pathname"
8 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
9 | Pathname.new(__FILE__).realpath)
10 |
11 | require "rubygems"
12 | require "bundler/setup"
13 |
14 | require "webpacker"
15 | require "webpacker/webpack_runner"
16 | Webpacker::WebpackRunner.run(ARGV)
17 |
--------------------------------------------------------------------------------
/bin/webpack-dev-server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
5 | ENV["NODE_ENV"] ||= "development"
6 |
7 | require "pathname"
8 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
9 | Pathname.new(__FILE__).realpath)
10 |
11 | require "rubygems"
12 | require "bundler/setup"
13 |
14 | require "webpacker"
15 | require "webpacker/dev_server_runner"
16 | Webpacker::DevServerRunner.run(ARGV)
17 |
--------------------------------------------------------------------------------
/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | APP_ROOT = File.expand_path("..", __dir__)
5 | Dir.chdir(APP_ROOT) do
6 | begin
7 | exec "yarnpkg", *ARGV
8 | rescue Errno::ENOENT
9 | $stderr.puts "Yarn executable was not detected in the system."
10 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
11 | exit 1
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This file is used by Rack-based servers to start the application.
4 |
5 | require_relative "config/environment"
6 |
7 | run Rails.application
8 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "boot"
4 |
5 | require "rails/all"
6 |
7 | # Require the gems listed in Gemfile, including any gems
8 | # you've limited to :test, :development, or :production.
9 | Bundler.require(*Rails.groups)
10 |
11 | module DemandProgress
12 | class Application < Rails::Application
13 | # Initialize configuration defaults for originally generated Rails version.
14 | config.load_defaults 5.2
15 |
16 | # Settings in config/environments/* take precedence over those specified here.
17 | # Application configuration can go into files in config/initializers
18 | # -- all .rb files in that directory are automatically loaded after loading
19 | # the framework and any gems in your application.
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
4 |
5 | require "bundler/setup" # Set up gems listed in the Gemfile.
6 | require "bootsnap/setup" # Speed up boot time by caching expensive operations.
7 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: demand-progress_production
11 |
--------------------------------------------------------------------------------
/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | UaDnVgXz1F9sw1RAtM3Ugbi0BQN5ut/IaOu01Rf+n6QLDtbr5G3he9tx1C+f4JPS3ns57086GRodkbVOyXgmnqetczA5TI5l9zztBHmEvBtzLl3rU011B3bY8NZ4zXTbqQ5Orf47Sue2Dl8lFX6h/rqYNhXv5j3mPHD/I3GiFRritcKiG+EUIFlrYAplAuwnHWLIVr2Z+0MUp4dfqJAfNLwQgGZ9/45DlSdiHH+1K8YbXAdRwHgjce7FRgD91q69L0tQueif8QQKumramG7nw+FHklEyXub6jpZqqkCsmFlxdtzNXQ9a2h/lVCcogPOCgyBx6/zRfQeTr5rDUhM6QSAnC/dcaYk6QgHbeKxJmMeYVp0sCmj/lGdc7zgSih+yAqtcflt3riSV3EKjGvNkTUHb+R8T7DwXr6CZ--LVJfPWVR3JdmyD7v--MIcHU2SbQunVkCymH0YZ1A==
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # PostgreSQL. Versions 9.1 and up are supported.
2 | #
3 | # Install the pg driver:
4 | # gem install pg
5 | # On OS X with Homebrew:
6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config
7 | # On OS X with MacPorts:
8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
9 | # On Windows:
10 | # gem install pg
11 | # Choose the win32 build.
12 | # Install PostgreSQL and put its /bin directory on your path.
13 | #
14 | # Configure Using Gemfile
15 | # gem 'pg'
16 | #
17 | default: &default
18 | adapter: postgresql
19 | encoding: unicode
20 | host: localhost
21 | username: <%= ENV.fetch("POSTGRESQL_USER", "demand-progress") %>
22 | password: <%= ENV.fetch("POSTGRESQL_PASSWORD", "asdfasdf") %>
23 | # For details on connection pooling, see Rails configuration guide
24 | # http://guides.rubyonrails.org/configuring.html#database-pooling
25 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
26 |
27 | development:
28 | <<: *default
29 | database: demand_progress_development
30 |
31 | # The specified database role being used to connect to postgres.
32 | # To create additional roles in postgres see `$ createuser --help`.
33 | # When left blank, postgres will use the default role. This is
34 | # the same name as the operating system user that initialized the database.
35 | #username: demand-progress
36 |
37 | # The password associated with the postgres role (username).
38 | #password:
39 |
40 | # Connect on a TCP socket. Omitted by default since the client uses a
41 | # domain socket that doesn't need configuration. Windows does not have
42 | # domain sockets, so uncomment these lines.
43 | #host: localhost
44 |
45 | # The TCP port the server listens on. Defaults to 5432.
46 | # If your server runs on a different port number, change accordingly.
47 | #port: 5432
48 |
49 | # Schema search path. The server defaults to $user,public
50 | #schema_search_path: myapp,sharedapp,public
51 |
52 | # Minimum log levels, in increasing order:
53 | # debug5, debug4, debug3, debug2, debug1,
54 | # log, notice, warning, error, fatal, and panic
55 | # Defaults to warning.
56 | #min_messages: notice
57 |
58 | # Warning: The database defined as "test" will be erased and
59 | # re-generated from your development database when you run "rake".
60 | # Do not set this db to the same as development or production.
61 | test:
62 | <<: *default
63 | database: demand_progress_test
64 |
65 | # As with config/secrets.yml, you never want to store sensitive information,
66 | # like your database password, in your source code. If your source code is
67 | # ever seen by anyone, they now have access to your database.
68 | #
69 | # Instead, provide the password as a unix environment variable when you boot
70 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
71 | # for a full rundown on how to provide these environment variables in a
72 | # production deployment.
73 | #
74 | # On Heroku and other platform providers, you may have a full connection URL
75 | # available as an environment variable. For example:
76 | #
77 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
78 | #
79 | # You can use this database configuration with:
80 | #
81 | # production:
82 | # url: <%= ENV['DATABASE_URL'] %>
83 | #
84 | production:
85 | <<: *default
86 | database: demand-progress_production
87 | username: demand-progress
88 | password: <%= ENV['DEMAND-PROGRESS_DATABASE_PASSWORD'] %>
89 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Load the Rails application.
4 | require_relative "application"
5 |
6 | # Initialize the Rails application.
7 | Rails.application.initialize!
8 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.configure do
4 | config.webpacker.check_yarn_integrity = true # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded on
7 | # every request. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable/disable caching. By default caching is disabled.
18 | # Run rails dev:cache to toggle caching.
19 | if Rails.root.join("tmp", "caching-dev.txt").exist?
20 | config.action_controller.perform_caching = true
21 |
22 | config.cache_store = :memory_store
23 | config.public_file_server.headers = {
24 | "Cache-Control" => "public, max-age=#{2.days.to_i}"
25 | }
26 | else
27 | config.action_controller.perform_caching = false
28 |
29 | config.cache_store = :null_store
30 | end
31 |
32 | # Store uploaded files on the local file system (see config/storage.yml for options)
33 | config.active_storage.service = :local
34 |
35 | # Don't care if the mailer can't send.
36 | config.action_mailer.raise_delivery_errors = false
37 |
38 | config.action_mailer.perform_caching = false
39 |
40 | # Print deprecation notices to the Rails logger.
41 | config.active_support.deprecation = :log
42 |
43 | # Raise an error on page load if there are pending migrations.
44 | config.active_record.migration_error = :page_load
45 |
46 | # Highlight code that triggered database queries in logs.
47 | config.active_record.verbose_query_logs = true
48 |
49 | # Debug mode disables concatenation and preprocessing of assets.
50 | # This option may cause significant delays in view rendering with a large
51 | # number of complex assets.
52 | config.assets.debug = true
53 |
54 | # Suppress logger output for asset requests.
55 | config.assets.quiet = true
56 |
57 | # Raises error for missing translations
58 | # config.action_view.raise_on_missing_translations = true
59 |
60 | # Use an evented file watcher to asynchronously detect changes in source code,
61 | # routes, locales, etc. This feature depends on the listen gem.
62 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
63 | end
64 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.configure do
4 | config.webpacker.check_yarn_integrity = false # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # Code is not reloaded between requests.
7 | config.cache_classes = true
8 |
9 | # Eager load code on boot. This eager loads most of Rails and
10 | # your application in memory, allowing both threaded web servers
11 | # and those relying on copy on write to perform better.
12 | # Rake tasks automatically ignore this option for performance.
13 | config.eager_load = true
14 |
15 | # Full error reports are disabled and caching is turned on.
16 | config.consider_all_requests_local = false
17 | config.action_controller.perform_caching = true
18 |
19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
21 | # config.require_master_key = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
26 |
27 | # Compress JavaScripts and CSS.
28 | config.assets.js_compressor = :uglifier
29 | # config.assets.css_compressor = :sass
30 |
31 | # Do not fallback to assets pipeline if a precompiled asset is missed.
32 | config.assets.compile = false
33 |
34 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
35 |
36 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
37 | # config.action_controller.asset_host = 'http://assets.example.com'
38 |
39 | # Specifies the header that your server uses for sending files.
40 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
41 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
42 |
43 | # Store uploaded files on the local file system (see config/storage.yml for options)
44 | config.active_storage.service = :local
45 |
46 | # Mount Action Cable outside main process or domain
47 | # config.action_cable.mount_path = nil
48 | # config.action_cable.url = 'wss://example.com/cable'
49 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
50 |
51 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
52 | # config.force_ssl = true
53 |
54 | # Use the lowest log level to ensure availability of diagnostic information
55 | # when problems arise.
56 | config.log_level = :debug
57 |
58 | # Prepend all log lines with the following tags.
59 | config.log_tags = [ :request_id ]
60 |
61 | # Use a different cache store in production.
62 | # config.cache_store = :mem_cache_store
63 |
64 | # Use a real queuing backend for Active Job (and separate queues per environment)
65 | # config.active_job.queue_adapter = :resque
66 | # config.active_job.queue_name_prefix = "demand-progress_#{Rails.env}"
67 |
68 | config.action_mailer.perform_caching = false
69 |
70 | # Ignore bad email addresses and do not raise email delivery errors.
71 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
72 | # config.action_mailer.raise_delivery_errors = false
73 |
74 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
75 | # the I18n.default_locale when a translation cannot be found).
76 | config.i18n.fallbacks = true
77 |
78 | # Send deprecation notices to registered listeners.
79 | config.active_support.deprecation = :notify
80 |
81 | # Use default logging formatter so that PID and timestamp are not suppressed.
82 | config.log_formatter = ::Logger::Formatter.new
83 |
84 | # Use a different logger for distributed setups.
85 | # require 'syslog/logger'
86 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
87 |
88 | if ENV["RAILS_LOG_TO_STDOUT"].present?
89 | logger = ActiveSupport::Logger.new(STDOUT)
90 | logger.formatter = config.log_formatter
91 | config.logger = ActiveSupport::TaggedLogging.new(logger)
92 | end
93 |
94 | # Do not dump schema after migrations.
95 | config.active_record.dump_schema_after_migration = false
96 | end
97 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # The test environment is used exclusively to run your application's
7 | # test suite. You never need to work with it otherwise. Remember that
8 | # your test database is "scratch space" for the test suite and is wiped
9 | # and recreated between test runs. Don't rely on the data there!
10 | # when running the cypress UI, allow reloading of classes
11 | config.cache_classes = (defined?(Cypress) ? Cypress.configuration.cache_classes : true)
12 |
13 | # Do not eager load code on boot. This avoids loading your whole application
14 | # just for the purpose of running a single test. If you are using a tool that
15 | # preloads Rails for running tests, you may have to set it to true.
16 | config.eager_load = false
17 |
18 | # Configure public file server for tests with Cache-Control for performance.
19 | config.public_file_server.enabled = true
20 | config.public_file_server.headers = {
21 | "Cache-Control" => "public, max-age=#{1.hour.to_i}"
22 | }
23 |
24 | # Show full error reports and disable caching.
25 | config.consider_all_requests_local = true
26 | config.action_controller.perform_caching = false
27 |
28 | # Raise exceptions instead of rendering exception templates.
29 | config.action_dispatch.show_exceptions = false
30 |
31 | # Disable request forgery protection in test environment.
32 | config.action_controller.allow_forgery_protection = false
33 |
34 | # Store uploaded files on the local file system in a temporary directory
35 | config.active_storage.service = :test
36 |
37 | config.action_mailer.perform_caching = false
38 |
39 | # Tell Action Mailer not to deliver emails to the real world.
40 | # The :test delivery method accumulates sent emails in the
41 | # ActionMailer::Base.deliveries array.
42 | config.action_mailer.delivery_method = :test
43 |
44 | # Print deprecation notices to the stderr.
45 | config.active_support.deprecation = :stderr
46 |
47 | # Raises error for missing translations
48 | # config.action_view.raise_on_missing_translations = true
49 | end
50 |
--------------------------------------------------------------------------------
/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # ActiveSupport::Reloader.to_prepare do
6 | # ApplicationController.renderer.defaults.merge!(
7 | # http_host: 'example.org',
8 | # https: false
9 | # )
10 | # end
11 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Version of your assets, change this if you want to expire all your assets.
6 | Rails.application.config.assets.version = "1.0"
7 |
8 | # Add additional assets to the asset load path.
9 | # Rails.application.config.assets.paths << Emoji.images_path
10 | # Add Yarn node_modules folder to the asset load path.
11 | Rails.application.config.assets.paths << Rails.root.join("node_modules")
12 |
13 | # Precompile additional assets.
14 | # application.js, application.css, and all non-JS/CSS in the app/assets
15 | # folder are already added.
16 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
17 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
6 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
7 |
8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
9 | # Rails.backtrace_cleaner.remove_silencers!
10 |
--------------------------------------------------------------------------------
/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Define an application-wide content security policy
6 | # For further information see the following documentation
7 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
8 |
9 | # Rails.application.config.content_security_policy do |policy|
10 | # policy.default_src :self, :https
11 | # policy.font_src :self, :https, :data
12 | # policy.img_src :self, :https, :data
13 | # policy.object_src :none
14 | # policy.script_src :self, :https
15 | # policy.style_src :self, :https
16 |
17 | # # Specify URI for violation reports
18 | # # policy.report_uri "/csp-violation-report-endpoint"
19 | # end
20 |
21 | # If you are using UJS then enable automatic nonce generation
22 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
23 |
24 | # Report CSP violations to a specified URI
25 | # For further information see the following documentation:
26 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
27 | # Rails.application.config.content_security_policy_report_only = true
28 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Specify a serializer for the signed and encrypted cookie jars.
6 | # Valid options are :json, :marshal, and :hybrid.
7 | Rails.application.config.action_dispatch.cookies_serializer = :json
8 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Configure sensitive parameters which will be filtered from the log file.
6 | Rails.application.config.filter_parameters += [:password]
7 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Add new inflection rules using the following format. Inflections
6 | # are locale specific, and you may define rules for as many different
7 | # locales as you wish. All of these examples are active by default:
8 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
9 | # inflect.plural /^(ox)$/i, '\1en'
10 | # inflect.singular /^(ox)en/i, '\1'
11 | # inflect.irregular 'person', 'people'
12 | # inflect.uncountable %w( fish sheep )
13 | # end
14 |
15 | # These inflection rules are supported but not enabled by default:
16 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
17 | # inflect.acronym 'RESTful'
18 | # end
19 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Add new mime types for use in respond_to blocks:
6 | # Mime::Type.register "text/richtext", :rtf
7 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # This file contains settings for ActionController::ParamsWrapper which
6 | # is enabled by default.
7 |
8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
9 | ActiveSupport.on_load(:action_controller) do
10 | wrap_parameters format: [:json]
11 | end
12 |
13 | # To enable root element in JSON for ActiveRecord objects.
14 | # ActiveSupport.on_load(:active_record) do
15 | # self.include_root_in_json = true
16 | # end
17 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at http://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Puma can serve each request in a thread from an internal thread pool.
4 | # The `threads` method setting takes two numbers: a minimum and maximum.
5 | # Any libraries that use thread pools should be configured to match
6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
7 | # and maximum; this matches the default thread size of Active Record.
8 | #
9 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
10 | threads threads_count, threads_count
11 |
12 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
13 | #
14 | port ENV.fetch("PORT") { 3000 }
15 |
16 | # Specifies the `environment` that Puma will run in.
17 | #
18 | environment ENV.fetch("RAILS_ENV") { "development" }
19 |
20 | # Specifies the number of `workers` to boot in clustered mode.
21 | # Workers are forked webserver processes. If using threads and workers together
22 | # the concurrency of the application would be max `threads` * `workers`.
23 | # Workers do not work on JRuby or Windows (both of which do not support
24 | # processes).
25 | #
26 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
27 |
28 | # Use the `preload_app!` method when specifying a `workers` number.
29 | # This directive tells Puma to first boot the application and load code
30 | # before forking the application. This takes advantage of Copy On Write
31 | # process behavior so workers use less memory.
32 | #
33 | # preload_app!
34 |
35 | # Allow puma to be restarted by `rails restart` command.
36 | plugin :tmp_restart
37 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.routes.draw do
4 | resources :organizations
5 | resources :events, :only => %i[index show]
6 | root to: "events#index"
7 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
8 | end
9 |
--------------------------------------------------------------------------------
/config/spring.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | %w[
4 | .ruby-version
5 | .rbenv-vars
6 | tmp/restart.txt
7 | tmp/caching-dev.txt
8 | ].each { |path| Spring.watch(path) }
9 |
--------------------------------------------------------------------------------
/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket
23 |
24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/config/webpack/development.js:
--------------------------------------------------------------------------------
1 | const environment = require('./environment')
2 |
3 | module.exports = environment.toWebpackConfig()
4 |
--------------------------------------------------------------------------------
/config/webpack/environment.js:
--------------------------------------------------------------------------------
1 | const { environment } = require('@rails/webpacker')
2 |
3 | module.exports = environment
4 |
--------------------------------------------------------------------------------
/config/webpack/production.js:
--------------------------------------------------------------------------------
1 | const environment = require('./environment')
2 |
3 | module.exports = environment.toWebpackConfig()
4 |
--------------------------------------------------------------------------------
/config/webpack/test.js:
--------------------------------------------------------------------------------
1 | const environment = require('./environment')
2 |
3 | module.exports = environment.toWebpackConfig()
4 |
--------------------------------------------------------------------------------
/config/webpacker.yml:
--------------------------------------------------------------------------------
1 | # Note: You must restart bin/webpack-dev-server for changes to take effect
2 |
3 | default: &default
4 | source_path: app/javascript
5 | source_entry_path: packs
6 | public_output_path: packs
7 | cache_path: tmp/cache/webpacker
8 |
9 | # Additional paths webpack should lookup modules
10 | # ['app/assets', 'engine/foo/app/assets']
11 | resolved_paths: []
12 |
13 | # Reload manifest.json on all requests so we reload latest compiled packs
14 | cache_manifest: false
15 |
16 | extensions:
17 | - .jsx
18 | - .js
19 | - .sass
20 | - .scss
21 | - .css
22 | - .png
23 | - .svg
24 | - .gif
25 | - .jpeg
26 | - .jpg
27 |
28 | development:
29 | <<: *default
30 | compile: true
31 |
32 | # Reference: https://webpack.js.org/configuration/dev-server/
33 | dev_server:
34 | https: false
35 | host: localhost
36 | port: 3035
37 | public: localhost:3035
38 | hmr: false
39 | # Inline should be set to true if using HMR
40 | inline: true
41 | overlay: true
42 | compress: true
43 | disable_host_check: true
44 | use_local_ip: false
45 | quiet: false
46 | headers:
47 | 'Access-Control-Allow-Origin': '*'
48 | watch_options:
49 | ignored: /node_modules/
50 |
51 |
52 | test:
53 | <<: *default
54 | compile: true
55 |
56 | # Compile test packs to a separate directory
57 | public_output_path: packs-test
58 |
59 | production:
60 | <<: *default
61 |
62 | # Production depends on precompilation of packs prior to booting for performance.
63 | compile: false
64 |
65 | # Cache manifest.json for performance
66 | cache_manifest: true
67 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/cypress/integration/examples/actions.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Actions', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/actions')
6 | })
7 |
8 | // https://on.cypress.io/interacting-with-elements
9 |
10 | it('.type() - type into a DOM element', () => {
11 | // https://on.cypress.io/type
12 | cy.get('.action-email')
13 | .type('fake@email.com').should('have.value', 'fake@email.com')
14 |
15 | // .type() with special character sequences
16 | .type('{leftarrow}{rightarrow}{uparrow}{downarrow}')
17 | .type('{del}{selectall}{backspace}')
18 |
19 | // .type() with key modifiers
20 | .type('{alt}{option}') //these are equivalent
21 | .type('{ctrl}{control}') //these are equivalent
22 | .type('{meta}{command}{cmd}') //these are equivalent
23 | .type('{shift}')
24 |
25 | // Delay each keypress by 0.1 sec
26 | .type('slow.typing@email.com', { delay: 100 })
27 | .should('have.value', 'slow.typing@email.com')
28 |
29 | cy.get('.action-disabled')
30 | // Ignore error checking prior to type
31 | // like whether the input is visible or disabled
32 | .type('disabled error checking', { force: true })
33 | .should('have.value', 'disabled error checking')
34 | })
35 |
36 | it('.focus() - focus on a DOM element', () => {
37 | // https://on.cypress.io/focus
38 | cy.get('.action-focus').focus()
39 | .should('have.class', 'focus')
40 | .prev().should('have.attr', 'style', 'color: orange;')
41 | })
42 |
43 | it('.blur() - blur off a DOM element', () => {
44 | // https://on.cypress.io/blur
45 | cy.get('.action-blur').type('About to blur').blur()
46 | .should('have.class', 'error')
47 | .prev().should('have.attr', 'style', 'color: red;')
48 | })
49 |
50 | it('.clear() - clears an input or textarea element', () => {
51 | // https://on.cypress.io/clear
52 | cy.get('.action-clear').type('Clear this text')
53 | .should('have.value', 'Clear this text')
54 | .clear()
55 | .should('have.value', '')
56 | })
57 |
58 | it('.submit() - submit a form', () => {
59 | // https://on.cypress.io/submit
60 | cy.get('.action-form')
61 | .find('[type="text"]').type('HALFOFF')
62 | cy.get('.action-form').submit()
63 | .next().should('contain', 'Your form has been submitted!')
64 | })
65 |
66 | it('.click() - click on a DOM element', () => {
67 | // https://on.cypress.io/click
68 | cy.get('.action-btn').click()
69 |
70 | // You can click on 9 specific positions of an element:
71 | // -----------------------------------
72 | // | topLeft top topRight |
73 | // | |
74 | // | |
75 | // | |
76 | // | left center right |
77 | // | |
78 | // | |
79 | // | |
80 | // | bottomLeft bottom bottomRight |
81 | // -----------------------------------
82 |
83 | // clicking in the center of the element is the default
84 | cy.get('#action-canvas').click()
85 |
86 | cy.get('#action-canvas').click('topLeft')
87 | cy.get('#action-canvas').click('top')
88 | cy.get('#action-canvas').click('topRight')
89 | cy.get('#action-canvas').click('left')
90 | cy.get('#action-canvas').click('right')
91 | cy.get('#action-canvas').click('bottomLeft')
92 | cy.get('#action-canvas').click('bottom')
93 | cy.get('#action-canvas').click('bottomRight')
94 |
95 | // .click() accepts an x and y coordinate
96 | // that controls where the click occurs :)
97 |
98 | cy.get('#action-canvas')
99 | .click(80, 75) // click 80px on x coord and 75px on y coord
100 | .click(170, 75)
101 | .click(80, 165)
102 | .click(100, 185)
103 | .click(125, 190)
104 | .click(150, 185)
105 | .click(170, 165)
106 |
107 | // click multiple elements by passing multiple: true
108 | cy.get('.action-labels>.label').click({ multiple: true })
109 |
110 | // Ignore error checking prior to clicking
111 | cy.get('.action-opacity>.btn').click({ force: true })
112 | })
113 |
114 | it('.dblclick() - double click on a DOM element', () => {
115 | // https://on.cypress.io/dblclick
116 |
117 | // Our app has a listener on 'dblclick' event in our 'scripts.js'
118 | // that hides the div and shows an input on double click
119 | cy.get('.action-div').dblclick().should('not.be.visible')
120 | cy.get('.action-input-hidden').should('be.visible')
121 | })
122 |
123 | it('.check() - check a checkbox or radio element', () => {
124 | // https://on.cypress.io/check
125 |
126 | // By default, .check() will check all
127 | // matching checkbox or radio elements in succession, one after another
128 | cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]')
129 | .check().should('be.checked')
130 |
131 | cy.get('.action-radios [type="radio"]').not('[disabled]')
132 | .check().should('be.checked')
133 |
134 | // .check() accepts a value argument
135 | cy.get('.action-radios [type="radio"]')
136 | .check('radio1').should('be.checked')
137 |
138 | // .check() accepts an array of values
139 | cy.get('.action-multiple-checkboxes [type="checkbox"]')
140 | .check(['checkbox1', 'checkbox2']).should('be.checked')
141 |
142 | // Ignore error checking prior to checking
143 | cy.get('.action-checkboxes [disabled]')
144 | .check({ force: true }).should('be.checked')
145 |
146 | cy.get('.action-radios [type="radio"]')
147 | .check('radio3', { force: true }).should('be.checked')
148 | })
149 |
150 | it('.uncheck() - uncheck a checkbox element', () => {
151 | // https://on.cypress.io/uncheck
152 |
153 | // By default, .uncheck() will uncheck all matching
154 | // checkbox elements in succession, one after another
155 | cy.get('.action-check [type="checkbox"]')
156 | .not('[disabled]')
157 | .uncheck().should('not.be.checked')
158 |
159 | // .uncheck() accepts a value argument
160 | cy.get('.action-check [type="checkbox"]')
161 | .check('checkbox1')
162 | .uncheck('checkbox1').should('not.be.checked')
163 |
164 | // .uncheck() accepts an array of values
165 | cy.get('.action-check [type="checkbox"]')
166 | .check(['checkbox1', 'checkbox3'])
167 | .uncheck(['checkbox1', 'checkbox3']).should('not.be.checked')
168 |
169 | // Ignore error checking prior to unchecking
170 | cy.get('.action-check [disabled]')
171 | .uncheck({ force: true }).should('not.be.checked')
172 | })
173 |
174 | it('.select() - select an option in a element', () => {
175 | // https://on.cypress.io/select
176 |
177 | // Select option(s) with matching text content
178 | cy.get('.action-select').select('apples')
179 |
180 | cy.get('.action-select-multiple')
181 | .select(['apples', 'oranges', 'bananas'])
182 |
183 | // Select option(s) with matching value
184 | cy.get('.action-select').select('fr-bananas')
185 |
186 | cy.get('.action-select-multiple')
187 | .select(['fr-apples', 'fr-oranges', 'fr-bananas'])
188 | })
189 |
190 | it('.scrollIntoView() - scroll an element into view', () => {
191 | // https://on.cypress.io/scrollintoview
192 |
193 | // normally all of these buttons are hidden,
194 | // because they're not within
195 | // the viewable area of their parent
196 | // (we need to scroll to see them)
197 | cy.get('#scroll-horizontal button')
198 | .should('not.be.visible')
199 |
200 | // scroll the button into view, as if the user had scrolled
201 | cy.get('#scroll-horizontal button').scrollIntoView()
202 | .should('be.visible')
203 |
204 | cy.get('#scroll-vertical button')
205 | .should('not.be.visible')
206 |
207 | // Cypress handles the scroll direction needed
208 | cy.get('#scroll-vertical button').scrollIntoView()
209 | .should('be.visible')
210 |
211 | cy.get('#scroll-both button')
212 | .should('not.be.visible')
213 |
214 | // Cypress knows to scroll to the right and down
215 | cy.get('#scroll-both button').scrollIntoView()
216 | .should('be.visible')
217 | })
218 |
219 | it('cy.scrollTo() - scroll the window or element to a position', () => {
220 |
221 | // https://on.cypress.io/scrollTo
222 |
223 | // You can scroll to 9 specific positions of an element:
224 | // -----------------------------------
225 | // | topLeft top topRight |
226 | // | |
227 | // | |
228 | // | |
229 | // | left center right |
230 | // | |
231 | // | |
232 | // | |
233 | // | bottomLeft bottom bottomRight |
234 | // -----------------------------------
235 |
236 | // if you chain .scrollTo() off of cy, we will
237 | // scroll the entire window
238 | cy.scrollTo('bottom')
239 |
240 | cy.get('#scrollable-horizontal').scrollTo('right')
241 |
242 | // or you can scroll to a specific coordinate:
243 | // (x axis, y axis) in pixels
244 | cy.get('#scrollable-vertical').scrollTo(250, 250)
245 |
246 | // or you can scroll to a specific percentage
247 | // of the (width, height) of the element
248 | cy.get('#scrollable-both').scrollTo('75%', '25%')
249 |
250 | // control the easing of the scroll (default is 'swing')
251 | cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' })
252 |
253 | // control the duration of the scroll (in ms)
254 | cy.get('#scrollable-both').scrollTo('center', { duration: 2000 })
255 | })
256 |
257 | it('.trigger() - trigger an event on a DOM element', () => {
258 | // https://on.cypress.io/trigger
259 |
260 | // To interact with a range input (slider)
261 | // we need to set its value & trigger the
262 | // event to signal it changed
263 |
264 | // Here, we invoke jQuery's val() method to set
265 | // the value and trigger the 'change' event
266 | cy.get('.trigger-input-range')
267 | .invoke('val', 25)
268 | .trigger('change')
269 | .get('input[type=range]').siblings('p')
270 | .should('have.text', '25')
271 | })
272 | })
273 |
--------------------------------------------------------------------------------
/cypress/integration/examples/aliasing.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Aliasing', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/aliasing')
6 | })
7 |
8 | it('.as() - alias a DOM element for later use', () => {
9 | // https://on.cypress.io/as
10 |
11 | // Alias a DOM element for use later
12 | // We don't have to traverse to the element
13 | // later in our code, we reference it with @
14 |
15 | cy.get('.as-table').find('tbody>tr')
16 | .first().find('td').first()
17 | .find('button').as('firstBtn')
18 |
19 | // when we reference the alias, we place an
20 | // @ in front of its name
21 | cy.get('@firstBtn').click()
22 |
23 | cy.get('@firstBtn')
24 | .should('have.class', 'btn-success')
25 | .and('contain', 'Changed')
26 | })
27 |
28 | it('.as() - alias a route for later use', () => {
29 |
30 | // Alias the route to wait for its response
31 | cy.server()
32 | cy.route('GET', 'comments/*').as('getComment')
33 |
34 | // we have code that gets a comment when
35 | // the button is clicked in scripts.js
36 | cy.get('.network-btn').click()
37 |
38 | // https://on.cypress.io/wait
39 | cy.wait('@getComment').its('status').should('eq', 200)
40 |
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/cypress/integration/examples/assertions.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Assertions', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/assertions')
6 | })
7 |
8 | describe('Implicit Assertions', () => {
9 |
10 | it('.should() - make an assertion about the current subject', () => {
11 | // https://on.cypress.io/should
12 | cy.get('.assertion-table')
13 | .find('tbody tr:last').should('have.class', 'success')
14 | })
15 |
16 | it('.and() - chain multiple assertions together', () => {
17 | // https://on.cypress.io/and
18 | cy.get('.assertions-link')
19 | .should('have.class', 'active')
20 | .and('have.attr', 'href')
21 | .and('include', 'cypress.io')
22 | })
23 | })
24 |
25 | describe('Explicit Assertions', () => {
26 | // https://on.cypress.io/assertions
27 | it('expect - make an assertion about a specified subject', () => {
28 | // We can use Chai's BDD style assertions
29 | expect(true).to.be.true
30 |
31 | // Pass a function to should that can have any number
32 | // of explicit assertions within it.
33 | cy.get('.assertions-p').find('p')
34 | .should(($p) => {
35 | // return an array of texts from all of the p's
36 | let texts = $p.map((i, el) => // https://on.cypress.io/$
37 | Cypress.$(el).text())
38 |
39 | // jquery map returns jquery object
40 | // and .get() convert this to simple array
41 | texts = texts.get()
42 |
43 | // array should have length of 3
44 | expect(texts).to.have.length(3)
45 |
46 | // set this specific subject
47 | expect(texts).to.deep.eq([
48 | 'Some text from first p',
49 | 'More text from second p',
50 | 'And even more text from third p',
51 | ])
52 | })
53 | })
54 |
55 | it('assert - assert shape of an object', () => {
56 | const person = {
57 | name: 'Joe',
58 | age: 20,
59 | }
60 | assert.isObject(person, 'value is object')
61 | })
62 | })
63 | })
64 |
--------------------------------------------------------------------------------
/cypress/integration/examples/connectors.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Connectors', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/connectors')
6 | })
7 |
8 | it('.each() - iterate over an array of elements', () => {
9 | // https://on.cypress.io/each
10 | cy.get('.connectors-each-ul>li')
11 | .each(($el, index, $list) => {
12 | console.log($el, index, $list)
13 | })
14 | })
15 |
16 | it('.its() - get properties on the current subject', () => {
17 | // https://on.cypress.io/its
18 | cy.get('.connectors-its-ul>li')
19 | // calls the 'length' property yielding that value
20 | .its('length')
21 | .should('be.gt', 2)
22 | })
23 |
24 | it('.invoke() - invoke a function on the current subject', () => {
25 | // our div is hidden in our script.js
26 | // $('.connectors-div').hide()
27 |
28 | // https://on.cypress.io/invoke
29 | cy.get('.connectors-div').should('be.hidden')
30 | // call the jquery method 'show' on the 'div.container'
31 | .invoke('show')
32 | .should('be.visible')
33 | })
34 |
35 | it('.spread() - spread an array as individual args to callback function', () => {
36 | // https://on.cypress.io/spread
37 | const arr = ['foo', 'bar', 'baz']
38 |
39 | cy.wrap(arr).spread((foo, bar, baz) => {
40 | expect(foo).to.eq('foo')
41 | expect(bar).to.eq('bar')
42 | expect(baz).to.eq('baz')
43 | })
44 | })
45 |
46 | it('.then() - invoke a callback function with the current subject', () => {
47 | // https://on.cypress.io/then
48 | cy.get('.connectors-list>li').then(($lis) => {
49 | expect($lis).to.have.length(3)
50 | expect($lis.eq(0)).to.contain('Walk the dog')
51 | expect($lis.eq(1)).to.contain('Feed the cat')
52 | expect($lis.eq(2)).to.contain('Write JavaScript')
53 | })
54 | })
55 | })
56 |
--------------------------------------------------------------------------------
/cypress/integration/examples/cookies.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Cookies', () => {
4 | beforeEach(() => {
5 | Cypress.Cookies.debug(true)
6 |
7 | cy.visit('https://example.cypress.io/commands/cookies')
8 |
9 | // clear cookies again after visiting to remove
10 | // any 3rd party cookies picked up such as cloudflare
11 | cy.clearCookies()
12 | })
13 |
14 | it('cy.getCookie() - get a browser cookie', () => {
15 | // https://on.cypress.io/getcookie
16 | cy.get('#getCookie .set-a-cookie').click()
17 |
18 | // cy.getCookie() yields a cookie object
19 | cy.getCookie('token').should('have.property', 'value', '123ABC')
20 | })
21 |
22 | it('cy.getCookies() - get browser cookies', () => {
23 | // https://on.cypress.io/getcookies
24 | cy.getCookies().should('be.empty')
25 |
26 | cy.get('#getCookies .set-a-cookie').click()
27 |
28 | // cy.getCookies() yields an array of cookies
29 | cy.getCookies().should('have.length', 1).should((cookies) => {
30 |
31 | // each cookie has these properties
32 | expect(cookies[0]).to.have.property('name', 'token')
33 | expect(cookies[0]).to.have.property('value', '123ABC')
34 | expect(cookies[0]).to.have.property('httpOnly', false)
35 | expect(cookies[0]).to.have.property('secure', false)
36 | expect(cookies[0]).to.have.property('domain')
37 | expect(cookies[0]).to.have.property('path')
38 | })
39 | })
40 |
41 | it('cy.setCookie() - set a browser cookie', () => {
42 | // https://on.cypress.io/setcookie
43 | cy.getCookies().should('be.empty')
44 |
45 | cy.setCookie('foo', 'bar')
46 |
47 | // cy.getCookie() yields a cookie object
48 | cy.getCookie('foo').should('have.property', 'value', 'bar')
49 | })
50 |
51 | it('cy.clearCookie() - clear a browser cookie', () => {
52 | // https://on.cypress.io/clearcookie
53 | cy.getCookie('token').should('be.null')
54 |
55 | cy.get('#clearCookie .set-a-cookie').click()
56 |
57 | cy.getCookie('token').should('have.property', 'value', '123ABC')
58 |
59 | // cy.clearCookies() yields null
60 | cy.clearCookie('token').should('be.null')
61 |
62 | cy.getCookie('token').should('be.null')
63 | })
64 |
65 | it('cy.clearCookies() - clear browser cookies', () => {
66 | // https://on.cypress.io/clearcookies
67 | cy.getCookies().should('be.empty')
68 |
69 | cy.get('#clearCookies .set-a-cookie').click()
70 |
71 | cy.getCookies().should('have.length', 1)
72 |
73 | // cy.clearCookies() yields null
74 | cy.clearCookies()
75 |
76 | cy.getCookies().should('be.empty')
77 | })
78 | })
79 |
--------------------------------------------------------------------------------
/cypress/integration/examples/cypress_api.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Cypress.Commands', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/cypress-api')
6 | })
7 |
8 | // https://on.cypress.io/custom-commands
9 |
10 | it('.add() - create a custom command', () => {
11 | Cypress.Commands.add('console', {
12 | prevSubject: true,
13 | }, (subject, method) => {
14 | // the previous subject is automatically received
15 | // and the commands arguments are shifted
16 |
17 | // allow us to change the console method used
18 | method = method || 'log'
19 |
20 | // log the subject to the console
21 | console[method]('The subject is', subject)
22 |
23 | // whatever we return becomes the new subject
24 | // we don't want to change the subject so
25 | // we return whatever was passed in
26 | return subject
27 | })
28 |
29 | cy.get('button').console('info').then(($button) => {
30 | // subject is still $button
31 | })
32 | })
33 | })
34 |
35 |
36 | context('Cypress.Cookies', () => {
37 | beforeEach(() => {
38 | cy.visit('https://example.cypress.io/cypress-api')
39 | })
40 |
41 | // https://on.cypress.io/cookies
42 | it('.debug() - enable or disable debugging', () => {
43 | Cypress.Cookies.debug(true)
44 |
45 | // Cypress will now log in the console when
46 | // cookies are set or cleared
47 | cy.setCookie('fakeCookie', '123ABC')
48 | cy.clearCookie('fakeCookie')
49 | cy.setCookie('fakeCookie', '123ABC')
50 | cy.clearCookie('fakeCookie')
51 | cy.setCookie('fakeCookie', '123ABC')
52 | })
53 |
54 | it('.preserveOnce() - preserve cookies by key', () => {
55 | // normally cookies are reset after each test
56 | cy.getCookie('fakeCookie').should('not.be.ok')
57 |
58 | // preserving a cookie will not clear it when
59 | // the next test starts
60 | cy.setCookie('lastCookie', '789XYZ')
61 | Cypress.Cookies.preserveOnce('lastCookie')
62 | })
63 |
64 | it('.defaults() - set defaults for all cookies', () => {
65 | // now any cookie with the name 'session_id' will
66 | // not be cleared before each new test runs
67 | Cypress.Cookies.defaults({
68 | whitelist: 'session_id',
69 | })
70 | })
71 | })
72 |
73 |
74 | context('Cypress.Server', () => {
75 | beforeEach(() => {
76 | cy.visit('https://example.cypress.io/cypress-api')
77 | })
78 |
79 | // Permanently override server options for
80 | // all instances of cy.server()
81 |
82 | // https://on.cypress.io/cypress-server
83 | it('.defaults() - change default config of server', () => {
84 | Cypress.Server.defaults({
85 | delay: 0,
86 | force404: false,
87 | whitelist (xhr) {
88 | // handle custom logic for whitelisting
89 | },
90 | })
91 | })
92 | })
93 |
94 | context('Cypress.arch', () => {
95 | beforeEach(() => {
96 | cy.visit('https://example.cypress.io/cypress-api')
97 | })
98 |
99 | it('Get CPU architecture name of underlying OS', () => {
100 | // https://on.cypress.io/arch
101 | expect(Cypress.arch).to.exist
102 | })
103 | })
104 |
105 | context('Cypress.config()', () => {
106 | beforeEach(() => {
107 | cy.visit('https://example.cypress.io/cypress-api')
108 | })
109 |
110 | it('Get and set configuration options', () => {
111 | // https://on.cypress.io/config
112 | let myConfig = Cypress.config()
113 |
114 | expect(myConfig).to.have.property('animationDistanceThreshold', 5)
115 | expect(myConfig).to.have.property('baseUrl', null)
116 | expect(myConfig).to.have.property('defaultCommandTimeout', 4000)
117 | expect(myConfig).to.have.property('requestTimeout', 5000)
118 | expect(myConfig).to.have.property('responseTimeout', 30000)
119 | expect(myConfig).to.have.property('viewportHeight', 660)
120 | expect(myConfig).to.have.property('viewportWidth', 1000)
121 | expect(myConfig).to.have.property('pageLoadTimeout', 60000)
122 | expect(myConfig).to.have.property('waitForAnimations', true)
123 |
124 | expect(Cypress.config('pageLoadTimeout')).to.eq(60000)
125 |
126 | // this will change the config for the rest of your tests!
127 | Cypress.config('pageLoadTimeout', 20000)
128 |
129 | expect(Cypress.config('pageLoadTimeout')).to.eq(20000)
130 |
131 | Cypress.config('pageLoadTimeout', 60000)
132 | })
133 | })
134 |
135 | context('Cypress.dom', () => {
136 | beforeEach(() => {
137 | cy.visit('https://example.cypress.io/cypress-api')
138 | })
139 |
140 | // https://on.cypress.io/dom
141 | it('.isHidden() - determine if a DOM element is hidden', () => {
142 | let hiddenP = Cypress.$('.dom-p p.hidden').get(0)
143 | let visibleP = Cypress.$('.dom-p p.visible').get(0)
144 |
145 | // our first paragraph has css class 'hidden'
146 | expect(Cypress.dom.isHidden(hiddenP)).to.be.true
147 | expect(Cypress.dom.isHidden(visibleP)).to.be.false
148 | })
149 | })
150 |
151 | context('Cypress.env()', () => {
152 | beforeEach(() => {
153 | cy.visit('https://example.cypress.io/cypress-api')
154 | })
155 |
156 | // We can set environment variables for highly dynamic values
157 |
158 | // https://on.cypress.io/environment-variables
159 | it('Get environment variables', () => {
160 | // https://on.cypress.io/env
161 | // set multiple environment variables
162 | Cypress.env({
163 | host: 'veronica.dev.local',
164 | api_server: 'http://localhost:8888/v1/',
165 | })
166 |
167 | // get environment variable
168 | expect(Cypress.env('host')).to.eq('veronica.dev.local')
169 |
170 | // set environment variable
171 | Cypress.env('api_server', 'http://localhost:8888/v2/')
172 | expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/')
173 |
174 | // get all environment variable
175 | expect(Cypress.env()).to.have.property('host', 'veronica.dev.local')
176 | expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/')
177 | })
178 | })
179 |
180 | context('Cypress.log', () => {
181 | beforeEach(() => {
182 | cy.visit('https://example.cypress.io/cypress-api')
183 | })
184 |
185 | it('Control what is printed to the Command Log', () => {
186 | // https://on.cypress.io/cypress-log
187 | })
188 | })
189 |
190 |
191 | context('Cypress.platform', () => {
192 | beforeEach(() => {
193 | cy.visit('https://example.cypress.io/cypress-api')
194 | })
195 |
196 | it('Get underlying OS name', () => {
197 | // https://on.cypress.io/platform
198 | expect(Cypress.platform).to.be.exist
199 | })
200 | })
201 |
202 | context('Cypress.version', () => {
203 | beforeEach(() => {
204 | cy.visit('https://example.cypress.io/cypress-api')
205 | })
206 |
207 | it('Get current version of Cypress being run', () => {
208 | // https://on.cypress.io/version
209 | expect(Cypress.version).to.be.exist
210 | })
211 | })
212 |
--------------------------------------------------------------------------------
/cypress/integration/examples/files.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Files', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/files')
6 | })
7 | it('cy.fixture() - load a fixture', () => {
8 | // https://on.cypress.io/fixture
9 |
10 | // Instead of writing a response inline you can
11 | // use a fixture file's content.
12 |
13 | cy.server()
14 | cy.fixture('example.json').as('comment')
15 | cy.route('GET', 'comments/*', '@comment').as('getComment')
16 |
17 | // we have code that gets a comment when
18 | // the button is clicked in scripts.js
19 | cy.get('.fixture-btn').click()
20 |
21 | cy.wait('@getComment').its('responseBody')
22 | .should('have.property', 'name')
23 | .and('include', 'Using fixtures to represent data')
24 |
25 | // you can also just write the fixture in the route
26 | cy.route('GET', 'comments/*', 'fixture:example.json').as('getComment')
27 |
28 | // we have code that gets a comment when
29 | // the button is clicked in scripts.js
30 | cy.get('.fixture-btn').click()
31 |
32 | cy.wait('@getComment').its('responseBody')
33 | .should('have.property', 'name')
34 | .and('include', 'Using fixtures to represent data')
35 |
36 | // or write fx to represent fixture
37 | // by default it assumes it's .json
38 | cy.route('GET', 'comments/*', 'fx:example').as('getComment')
39 |
40 | // we have code that gets a comment when
41 | // the button is clicked in scripts.js
42 | cy.get('.fixture-btn').click()
43 |
44 | cy.wait('@getComment').its('responseBody')
45 | .should('have.property', 'name')
46 | .and('include', 'Using fixtures to represent data')
47 | })
48 |
49 | it('cy.readFile() - read a files contents', () => {
50 | // https://on.cypress.io/readfile
51 |
52 | // You can read a file and yield its contents
53 | // The filePath is relative to your project's root.
54 | cy.readFile('cypress.json').then((json) => {
55 | expect(json).to.be.an('object')
56 | })
57 | })
58 |
59 | it('cy.writeFile() - write to a file', () => {
60 | // https://on.cypress.io/writefile
61 |
62 | // You can write to a file
63 |
64 | // Use a response from a request to automatically
65 | // generate a fixture file for use later
66 | cy.request('https://jsonplaceholder.typicode.com/users')
67 | .then((response) => {
68 | cy.writeFile('cypress/fixtures/users.json', response.body)
69 | })
70 | cy.fixture('users').should((users) => {
71 | expect(users[0].name).to.exist
72 | })
73 |
74 | // JavaScript arrays and objects are stringified
75 | // and formatted into text.
76 | cy.writeFile('cypress/fixtures/profile.json', {
77 | id: 8739,
78 | name: 'Jane',
79 | email: 'jane@example.com',
80 | })
81 |
82 | cy.fixture('profile').should((profile) => {
83 | expect(profile.name).to.eq('Jane')
84 | })
85 | })
86 | })
87 |
--------------------------------------------------------------------------------
/cypress/integration/examples/local_storage.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Local Storage', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/local-storage')
6 | })
7 | // Although local storage is automatically cleared
8 | // in between tests to maintain a clean state
9 | // sometimes we need to clear the local storage manually
10 |
11 | it('cy.clearLocalStorage() - clear all data in local storage', () => {
12 | // https://on.cypress.io/clearlocalstorage
13 | cy.get('.ls-btn').click().should(() => {
14 | expect(localStorage.getItem('prop1')).to.eq('red')
15 | expect(localStorage.getItem('prop2')).to.eq('blue')
16 | expect(localStorage.getItem('prop3')).to.eq('magenta')
17 | })
18 |
19 | // clearLocalStorage() yields the localStorage object
20 | cy.clearLocalStorage().should((ls) => {
21 | expect(ls.getItem('prop1')).to.be.null
22 | expect(ls.getItem('prop2')).to.be.null
23 | expect(ls.getItem('prop3')).to.be.null
24 | })
25 |
26 | // Clear key matching string in Local Storage
27 | cy.get('.ls-btn').click().should(() => {
28 | expect(localStorage.getItem('prop1')).to.eq('red')
29 | expect(localStorage.getItem('prop2')).to.eq('blue')
30 | expect(localStorage.getItem('prop3')).to.eq('magenta')
31 | })
32 |
33 | cy.clearLocalStorage('prop1').should((ls) => {
34 | expect(ls.getItem('prop1')).to.be.null
35 | expect(ls.getItem('prop2')).to.eq('blue')
36 | expect(ls.getItem('prop3')).to.eq('magenta')
37 | })
38 |
39 | // Clear keys matching regex in Local Storage
40 | cy.get('.ls-btn').click().should(() => {
41 | expect(localStorage.getItem('prop1')).to.eq('red')
42 | expect(localStorage.getItem('prop2')).to.eq('blue')
43 | expect(localStorage.getItem('prop3')).to.eq('magenta')
44 | })
45 |
46 | cy.clearLocalStorage(/prop1|2/).should((ls) => {
47 | expect(ls.getItem('prop1')).to.be.null
48 | expect(ls.getItem('prop2')).to.be.null
49 | expect(ls.getItem('prop3')).to.eq('magenta')
50 | })
51 | })
52 | })
53 |
--------------------------------------------------------------------------------
/cypress/integration/examples/location.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Location', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/location')
6 | })
7 |
8 | it('cy.hash() - get the current URL hash', () => {
9 | // https://on.cypress.io/hash
10 | cy.hash().should('be.empty')
11 | })
12 |
13 | it('cy.location() - get window.location', () => {
14 | // https://on.cypress.io/location
15 | cy.location().should((location) => {
16 | expect(location.hash).to.be.empty
17 | expect(location.href).to.eq('https://example.cypress.io/commands/location')
18 | expect(location.host).to.eq('example.cypress.io')
19 | expect(location.hostname).to.eq('example.cypress.io')
20 | expect(location.origin).to.eq('https://example.cypress.io')
21 | expect(location.pathname).to.eq('/commands/location')
22 | expect(location.port).to.eq('')
23 | expect(location.protocol).to.eq('https:')
24 | expect(location.search).to.be.empty
25 | })
26 | })
27 |
28 | it('cy.url() - get the current URL', () => {
29 | // https://on.cypress.io/url
30 | cy.url().should('eq', 'https://example.cypress.io/commands/location')
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/cypress/integration/examples/misc.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Misc', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/misc')
6 | })
7 |
8 | it('.end() - end the command chain', () => {
9 | // https://on.cypress.io/end
10 |
11 | // cy.end is useful when you want to end a chain of commands
12 | // and force Cypress to re-query from the root element
13 | cy.get('.misc-table').within(() => {
14 | // ends the current chain and yields null
15 | cy.contains('Cheryl').click().end()
16 |
17 | // queries the entire table again
18 | cy.contains('Charles').click()
19 | })
20 | })
21 |
22 | it('cy.exec() - execute a system command', () => {
23 | // https://on.cypress.io/exec
24 |
25 | // execute a system command.
26 | // so you can take actions necessary for
27 | // your test outside the scope of Cypress.
28 | cy.exec('echo Jane Lane')
29 | .its('stdout').should('contain', 'Jane Lane')
30 |
31 | // we can use Cypress.platform string to
32 | // select appropriate command
33 | // https://on.cypress/io/platform
34 | cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`)
35 |
36 | if (Cypress.platform === 'win32') {
37 | cy.exec('print cypress.json')
38 | .its('stderr').should('be.empty')
39 | } else {
40 | cy.exec('cat cypress.json')
41 | .its('stderr').should('be.empty')
42 |
43 | cy.exec('pwd')
44 | .its('code').should('eq', 0)
45 | }
46 | })
47 |
48 | it('cy.focused() - get the DOM element that has focus', () => {
49 | // https://on.cypress.io/focused
50 | cy.get('.misc-form').find('#name').click()
51 | cy.focused().should('have.id', 'name')
52 |
53 | cy.get('.misc-form').find('#description').click()
54 | cy.focused().should('have.id', 'description')
55 | })
56 |
57 | it('cy.screenshot() - take a screenshot', () => {
58 | // https://on.cypress.io/screenshot
59 | cy.screenshot('my-image')
60 | })
61 |
62 | it('cy.wrap() - wrap an object', () => {
63 | // https://on.cypress.io/wrap
64 | cy.wrap({ foo: 'bar' })
65 | .should('have.property', 'foo')
66 | .and('include', 'bar')
67 | })
68 | })
69 |
--------------------------------------------------------------------------------
/cypress/integration/examples/navigation.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Navigation', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io')
6 | cy.get('.navbar-nav').contains('Commands').click()
7 | cy.get('.dropdown-menu').contains('Navigation').click()
8 | })
9 |
10 | it('cy.go() - go back or forward in the browser\'s history', () => {
11 | // https://on.cypress.io/go
12 |
13 | cy.location('pathname').should('include', 'navigation')
14 |
15 | cy.go('back')
16 | cy.location('pathname').should('not.include', 'navigation')
17 |
18 | cy.go('forward')
19 | cy.location('pathname').should('include', 'navigation')
20 |
21 | // clicking back
22 | cy.go(-1)
23 | cy.location('pathname').should('not.include', 'navigation')
24 |
25 | // clicking forward
26 | cy.go(1)
27 | cy.location('pathname').should('include', 'navigation')
28 | })
29 |
30 | it('cy.reload() - reload the page', () => {
31 | // https://on.cypress.io/reload
32 | cy.reload()
33 |
34 | // reload the page without using the cache
35 | cy.reload(true)
36 | })
37 |
38 | it('cy.visit() - visit a remote url', () => {
39 | // https://on.cypress.io/visit
40 |
41 | // Visit any sub-domain of your current domain
42 |
43 | // Pass options to the visit
44 | cy.visit('https://example.cypress.io/commands/navigation', {
45 | timeout: 50000, // increase total time for the visit to resolve
46 | onBeforeLoad (contentWindow) {
47 | // contentWindow is the remote page's window object
48 | },
49 | onLoad (contentWindow) {
50 | // contentWindow is the remote page's window object
51 | },
52 | })
53 | })
54 | })
55 |
--------------------------------------------------------------------------------
/cypress/integration/examples/network_requests.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Network Requests', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/network-requests')
6 | })
7 |
8 | // Manage AJAX / XHR requests in your app
9 |
10 | it('cy.server() - control behavior of network requests and responses', () => {
11 | // https://on.cypress.io/server
12 |
13 | cy.server().should((server) => {
14 | // the default options on server
15 | // you can override any of these options
16 | expect(server.delay).to.eq(0)
17 | expect(server.method).to.eq('GET')
18 | expect(server.status).to.eq(200)
19 | expect(server.headers).to.be.null
20 | expect(server.response).to.be.null
21 | expect(server.onRequest).to.be.undefined
22 | expect(server.onResponse).to.be.undefined
23 | expect(server.onAbort).to.be.undefined
24 |
25 | // These options control the server behavior
26 | // affecting all requests
27 |
28 | // pass false to disable existing route stubs
29 | expect(server.enable).to.be.true
30 | // forces requests that don't match your routes to 404
31 | expect(server.force404).to.be.false
32 | // whitelists requests from ever being logged or stubbed
33 | expect(server.whitelist).to.be.a('function')
34 | })
35 |
36 | cy.server({
37 | method: 'POST',
38 | delay: 1000,
39 | status: 422,
40 | response: {},
41 | })
42 |
43 | // any route commands will now inherit the above options
44 | // from the server. anything we pass specifically
45 | // to route will override the defaults though.
46 | })
47 |
48 | it('cy.request() - make an XHR request', () => {
49 | // https://on.cypress.io/request
50 | cy.request('https://jsonplaceholder.typicode.com/comments')
51 | .should((response) => {
52 | expect(response.status).to.eq(200)
53 | expect(response.body).to.have.length(500)
54 | expect(response).to.have.property('headers')
55 | expect(response).to.have.property('duration')
56 | })
57 | })
58 |
59 | it('cy.route() - route responses to matching requests', () => {
60 | // https://on.cypress.io/route
61 |
62 | let message = 'whoa, this comment does not exist'
63 | cy.server()
64 |
65 | // Listen to GET to comments/1
66 | cy.route('GET', 'comments/*').as('getComment')
67 |
68 | // we have code that gets a comment when
69 | // the button is clicked in scripts.js
70 | cy.get('.network-btn').click()
71 |
72 | // https://on.cypress.io/wait
73 | cy.wait('@getComment').its('status').should('eq', 200)
74 |
75 | // Listen to POST to comments
76 | cy.route('POST', '/comments').as('postComment')
77 |
78 | // we have code that posts a comment when
79 | // the button is clicked in scripts.js
80 | cy.get('.network-post').click()
81 | cy.wait('@postComment')
82 |
83 | // get the route
84 | cy.get('@postComment').should((xhr) => {
85 | expect(xhr.requestBody).to.include('email')
86 | expect(xhr.requestHeaders).to.have.property('Content-Type')
87 | expect(xhr.responseBody).to.have.property('name', 'Using POST in cy.route()')
88 | })
89 |
90 | // Stub a response to PUT comments/ ****
91 | cy.route({
92 | method: 'PUT',
93 | url: 'comments/*',
94 | status: 404,
95 | response: { error: message },
96 | delay: 500,
97 | }).as('putComment')
98 |
99 | // we have code that puts a comment when
100 | // the button is clicked in scripts.js
101 | cy.get('.network-put').click()
102 |
103 | cy.wait('@putComment')
104 |
105 | // our 404 statusCode logic in scripts.js executed
106 | cy.get('.network-put-comment').should('contain', message)
107 | })
108 | })
109 |
--------------------------------------------------------------------------------
/cypress/integration/examples/querying.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Querying', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/querying')
6 | })
7 |
8 | // The most commonly used query is 'cy.get()', you can
9 | // think of this like the '$' in jQuery
10 |
11 | it('cy.get() - query DOM elements', () => {
12 | // https://on.cypress.io/get
13 |
14 | cy.get('#query-btn').should('contain', 'Button')
15 |
16 | cy.get('.query-btn').should('contain', 'Button')
17 |
18 | cy.get('#querying .well>button:first').should('contain', 'Button')
19 | // ↲
20 | // Use CSS selectors just like jQuery
21 | })
22 |
23 | it('cy.contains() - query DOM elements with matching content', () => {
24 | // https://on.cypress.io/contains
25 | cy.get('.query-list')
26 | .contains('bananas').should('have.class', 'third')
27 |
28 | // we can pass a regexp to `.contains()`
29 | cy.get('.query-list')
30 | .contains(/^b\w+/).should('have.class', 'third')
31 |
32 | cy.get('.query-list')
33 | .contains('apples').should('have.class', 'first')
34 |
35 | // passing a selector to contains will
36 | // yield the selector containing the text
37 | cy.get('#querying')
38 | .contains('ul', 'oranges')
39 | .should('have.class', 'query-list')
40 |
41 | cy.get('.query-button')
42 | .contains('Save Form')
43 | .should('have.class', 'btn')
44 | })
45 |
46 | it('.within() - query DOM elements within a specific element', () => {
47 | // https://on.cypress.io/within
48 | cy.get('.query-form').within(() => {
49 | cy.get('input:first').should('have.attr', 'placeholder', 'Email')
50 | cy.get('input:last').should('have.attr', 'placeholder', 'Password')
51 | })
52 | })
53 |
54 | it('cy.root() - query the root DOM element', () => {
55 | // https://on.cypress.io/root
56 |
57 | // By default, root is the document
58 | cy.root().should('match', 'html')
59 |
60 | cy.get('.query-ul').within(() => {
61 | // In this within, the root is now the ul DOM element
62 | cy.root().should('have.class', 'query-ul')
63 | })
64 | })
65 | })
66 |
--------------------------------------------------------------------------------
/cypress/integration/examples/spies_stubs_clocks.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Spies, Stubs, and Clock', () => {
4 | it('cy.spy() - wrap a method in a spy', () => {
5 | // https://on.cypress.io/spy
6 | cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
7 |
8 | let obj = {
9 | foo () {},
10 | }
11 |
12 | let spy = cy.spy(obj, 'foo').as('anyArgs')
13 |
14 | obj.foo()
15 |
16 | expect(spy).to.be.called
17 | })
18 |
19 | it('cy.stub() - create a stub and/or replace a function with stub', () => {
20 | // https://on.cypress.io/stub
21 | cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
22 |
23 | let obj = {
24 | foo () {},
25 | }
26 |
27 | let stub = cy.stub(obj, 'foo').as('foo')
28 |
29 | obj.foo('foo', 'bar')
30 |
31 | expect(stub).to.be.called
32 | })
33 |
34 | it('cy.clock() - control time in the browser', () => {
35 | // https://on.cypress.io/clock
36 |
37 | // create the date in UTC so its always the same
38 | // no matter what local timezone the browser is running in
39 | let now = new Date(Date.UTC(2017, 2, 14)).getTime()
40 |
41 | cy.clock(now)
42 | cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
43 | cy.get('#clock-div').click()
44 | .should('have.text', '1489449600')
45 | })
46 |
47 | it('cy.tick() - move time in the browser', () => {
48 | // https://on.cypress.io/tick
49 |
50 | // create the date in UTC so its always the same
51 | // no matter what local timezone the browser is running in
52 | let now = new Date(Date.UTC(2017, 2, 14)).getTime()
53 |
54 | cy.clock(now)
55 | cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
56 | cy.get('#tick-div').click()
57 | .should('have.text', '1489449600')
58 | cy.tick(10000) // 10 seconds passed
59 | cy.get('#tick-div').click()
60 | .should('have.text', '1489449610')
61 | })
62 | })
63 |
--------------------------------------------------------------------------------
/cypress/integration/examples/traversal.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Traversal', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/traversal')
6 | })
7 |
8 | it('.children() - get child DOM elements', () => {
9 | // https://on.cypress.io/children
10 | cy.get('.traversal-breadcrumb')
11 | .children('.active')
12 | .should('contain', 'Data')
13 | })
14 |
15 | it('.closest() - get closest ancestor DOM element', () => {
16 | // https://on.cypress.io/closest
17 | cy.get('.traversal-badge')
18 | .closest('ul')
19 | .should('have.class', 'list-group')
20 | })
21 |
22 | it('.eq() - get a DOM element at a specific index', () => {
23 | // https://on.cypress.io/eq
24 | cy.get('.traversal-list>li')
25 | .eq(1).should('contain', 'siamese')
26 | })
27 |
28 | it('.filter() - get DOM elements that match the selector', () => {
29 | // https://on.cypress.io/filter
30 | cy.get('.traversal-nav>li')
31 | .filter('.active').should('contain', 'About')
32 | })
33 |
34 | it('.find() - get descendant DOM elements of the selector', () => {
35 | // https://on.cypress.io/find
36 | cy.get('.traversal-pagination')
37 | .find('li').find('a')
38 | .should('have.length', 7)
39 | })
40 |
41 | it('.first() - get first DOM element', () => {
42 | // https://on.cypress.io/first
43 | cy.get('.traversal-table td')
44 | .first().should('contain', '1')
45 | })
46 |
47 | it('.last() - get last DOM element', () => {
48 | // https://on.cypress.io/last
49 | cy.get('.traversal-buttons .btn')
50 | .last().should('contain', 'Submit')
51 | })
52 |
53 | it('.next() - get next sibling DOM element', () => {
54 | // https://on.cypress.io/next
55 | cy.get('.traversal-ul')
56 | .contains('apples').next().should('contain', 'oranges')
57 | })
58 |
59 | it('.nextAll() - get all next sibling DOM elements', () => {
60 | // https://on.cypress.io/nextall
61 | cy.get('.traversal-next-all')
62 | .contains('oranges')
63 | .nextAll().should('have.length', 3)
64 | })
65 |
66 | it('.nextUntil() - get next sibling DOM elements until next el', () => {
67 | // https://on.cypress.io/nextuntil
68 | cy.get('#veggies')
69 | .nextUntil('#nuts').should('have.length', 3)
70 | })
71 |
72 | it('.not() - remove DOM elements from set of DOM elements', () => {
73 | // https://on.cypress.io/not
74 | cy.get('.traversal-disabled .btn')
75 | .not('[disabled]').should('not.contain', 'Disabled')
76 | })
77 |
78 | it('.parent() - get parent DOM element from DOM elements', () => {
79 | // https://on.cypress.io/parent
80 | cy.get('.traversal-mark')
81 | .parent().should('contain', 'Morbi leo risus')
82 | })
83 |
84 | it('.parents() - get parent DOM elements from DOM elements', () => {
85 | // https://on.cypress.io/parents
86 | cy.get('.traversal-cite')
87 | .parents().should('match', 'blockquote')
88 | })
89 |
90 | it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => {
91 | // https://on.cypress.io/parentsuntil
92 | cy.get('.clothes-nav')
93 | .find('.active')
94 | .parentsUntil('.clothes-nav')
95 | .should('have.length', 2)
96 | })
97 |
98 | it('.prev() - get previous sibling DOM element', () => {
99 | // https://on.cypress.io/prev
100 | cy.get('.birds').find('.active')
101 | .prev().should('contain', 'Lorikeets')
102 | })
103 |
104 | it('.prevAll() - get all previous sibling DOM elements', () => {
105 | // https://on.cypress.io/prevAll
106 | cy.get('.fruits-list').find('.third')
107 | .prevAll().should('have.length', 2)
108 | })
109 |
110 | it('.prevUntil() - get all previous sibling DOM elements until el', () => {
111 | // https://on.cypress.io/prevUntil
112 | cy.get('.foods-list').find('#nuts')
113 | .prevUntil('#veggies').should('have.length', 3)
114 | })
115 |
116 | it('.siblings() - get all sibling DOM elements', () => {
117 | // https://on.cypress.io/siblings
118 | cy.get('.traversal-pills .active')
119 | .siblings().should('have.length', 2)
120 | })
121 | })
122 |
--------------------------------------------------------------------------------
/cypress/integration/examples/utilities.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Utilities', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/utilities')
6 | })
7 |
8 | it('Cypress._ - call a lodash method', () => {
9 | // https://on.cypress.io/_
10 | cy.request('https://jsonplaceholder.typicode.com/users')
11 | .then((response) => {
12 | let ids = Cypress._.chain(response.body).map('id').take(3).value()
13 |
14 | expect(ids).to.deep.eq([1, 2, 3])
15 | })
16 | })
17 |
18 | it('Cypress.$ - call a jQuery method', () => {
19 | // https://on.cypress.io/$
20 | let $li = Cypress.$('.utility-jquery li:first')
21 |
22 | cy.wrap($li)
23 | .should('not.have.class', 'active')
24 | .click()
25 | .should('have.class', 'active')
26 | })
27 |
28 | it('Cypress.Blob - blob utilities and base64 string conversion', () => {
29 | // https://on.cypress.io/blob
30 | cy.get('.utility-blob').then(($div) =>
31 | // https://github.com/nolanlawson/blob-util#imgSrcToDataURL
32 | // get the dataUrl string for the javascript-logo
33 | Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous')
34 | .then((dataUrl) => {
35 | // create an element and set its src to the dataUrl
36 | let img = Cypress.$(' ', { src: dataUrl })
37 | // need to explicitly return cy here since we are initially returning
38 | // the Cypress.Blob.imgSrcToDataURL promise to our test
39 | // append the image
40 | $div.append(img)
41 |
42 | cy.get('.utility-blob img').click()
43 | .should('have.attr', 'src', dataUrl)
44 | }))
45 | })
46 |
47 | it('Cypress.minimatch - test out glob patterns against strings', () => {
48 | // https://on.cypress.io/minimatch
49 | Cypress.minimatch('/users/1/comments', '/users/*/comments', {
50 | matchBase: true,
51 | })
52 | })
53 |
54 |
55 | it('Cypress.moment() - format or parse dates using a moment method', () => {
56 | // https://on.cypress.io/moment
57 | let time = Cypress.moment().utc('2014-04-25T19:38:53.196Z').format('h:mm A')
58 |
59 | cy.get('.utility-moment').contains('3:38 PM')
60 | .should('have.class', 'badge')
61 | })
62 |
63 |
64 | it('Cypress.Promise - instantiate a bluebird promise', () => {
65 | // https://on.cypress.io/promise
66 | let waited = false
67 |
68 | function waitOneSecond () {
69 | // return a promise that resolves after 1 second
70 | return new Cypress.Promise((resolve, reject) => {
71 | setTimeout(() => {
72 | // set waited to true
73 | waited = true
74 |
75 | // resolve with 'foo' string
76 | resolve('foo')
77 | }, 1000)
78 | })
79 | }
80 |
81 | cy.then(() =>
82 | // return a promise to cy.then() that
83 | // is awaited until it resolves
84 | waitOneSecond().then((str) => {
85 | expect(str).to.eq('foo')
86 | expect(waited).to.be.true
87 | }))
88 | })
89 | })
90 |
--------------------------------------------------------------------------------
/cypress/integration/examples/viewport.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Viewport', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/viewport')
6 | })
7 |
8 | it('cy.viewport() - set the viewport size and dimension', () => {
9 | // https://on.cypress.io/viewport
10 |
11 | cy.get('#navbar').should('be.visible')
12 | cy.viewport(320, 480)
13 |
14 | // the navbar should have collapse since our screen is smaller
15 | cy.get('#navbar').should('not.be.visible')
16 | cy.get('.navbar-toggle').should('be.visible').click()
17 | cy.get('.nav').find('a').should('be.visible')
18 |
19 | // lets see what our app looks like on a super large screen
20 | cy.viewport(2999, 2999)
21 |
22 | // cy.viewport() accepts a set of preset sizes
23 | // to easily set the screen to a device's width and height
24 |
25 | // We added a cy.wait() between each viewport change so you can see
26 | // the change otherwise it is a little too fast to see :)
27 |
28 | cy.viewport('macbook-15')
29 | cy.wait(200)
30 | cy.viewport('macbook-13')
31 | cy.wait(200)
32 | cy.viewport('macbook-11')
33 | cy.wait(200)
34 | cy.viewport('ipad-2')
35 | cy.wait(200)
36 | cy.viewport('ipad-mini')
37 | cy.wait(200)
38 | cy.viewport('iphone-6+')
39 | cy.wait(200)
40 | cy.viewport('iphone-6')
41 | cy.wait(200)
42 | cy.viewport('iphone-5')
43 | cy.wait(200)
44 | cy.viewport('iphone-4')
45 | cy.wait(200)
46 | cy.viewport('iphone-3')
47 | cy.wait(200)
48 |
49 | // cy.viewport() accepts an orientation for all presets
50 | // the default orientation is 'portrait'
51 | cy.viewport('ipad-2', 'portrait')
52 | cy.wait(200)
53 | cy.viewport('iphone-4', 'landscape')
54 | cy.wait(200)
55 |
56 | // The viewport will be reset back to the default dimensions
57 | // in between tests (the default can be set in cypress.json)
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/cypress/integration/examples/waiting.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Waiting', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/waiting')
6 | })
7 | // BE CAREFUL of adding unnecessary wait times.
8 | // https://on.cypress.io/best-practices#Unnecessary-Waiting
9 |
10 | // https://on.cypress.io/wait
11 | it('cy.wait() - wait for a specific amount of time', () => {
12 | cy.get('.wait-input1').type('Wait 1000ms after typing')
13 | cy.wait(1000)
14 | cy.get('.wait-input2').type('Wait 1000ms after typing')
15 | cy.wait(1000)
16 | cy.get('.wait-input3').type('Wait 1000ms after typing')
17 | cy.wait(1000)
18 | })
19 |
20 | it('cy.wait() - wait for a specific route', () => {
21 | cy.server()
22 |
23 | // Listen to GET to comments/1
24 | cy.route('GET', 'comments/*').as('getComment')
25 |
26 | // we have code that gets a comment when
27 | // the button is clicked in scripts.js
28 | cy.get('.network-btn').click()
29 |
30 | // wait for GET comments/1
31 | cy.wait('@getComment').its('status').should('eq', 200)
32 | })
33 |
34 | })
35 |
--------------------------------------------------------------------------------
/cypress/integration/examples/window.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Window', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/window')
6 | })
7 |
8 | it('cy.window() - get the global window object', () => {
9 | // https://on.cypress.io/window
10 | cy.window().should('have.property', 'top')
11 | })
12 |
13 | it('cy.document() - get the document object', () => {
14 | // https://on.cypress.io/document
15 | cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
16 | })
17 |
18 | it('cy.title() - get the title', () => {
19 | // https://on.cypress.io/title
20 | cy.title().should('include', 'Kitchen Sink')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/db/migrate/20180608143102_create_committees.rb:
--------------------------------------------------------------------------------
1 | class CreateCommittees < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :committees do |t|
4 | t.string :name
5 | t.string :code
6 | t.string :website
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20180608180059_add_ancestry_to_committee.rb:
--------------------------------------------------------------------------------
1 | class AddAncestryToCommittee < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :committees, :ancestry, :string
4 | add_index :committees, :ancestry
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20180608182158_create_events.rb:
--------------------------------------------------------------------------------
1 | class CreateEvents < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :events do |t|
4 | t.text :topic
5 | t.datetime :occurs_at
6 | t.string :location
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20180610012137_change_commitees_to_organizations.rb:
--------------------------------------------------------------------------------
1 | class ChangeCommiteesToOrganizations < ActiveRecord::Migration[5.2]
2 | def change
3 | rename_table "committees", "organizations"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20180610121037_add_raw_location_to_event.rb:
--------------------------------------------------------------------------------
1 | class AddRawLocationToEvent < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :events, :raw_location, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/sample/events.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.create :event,
4 | topic: "Examining Effects of Mismanagement of the Cormorant in the Great "\
5 | "Lakes Region",
6 | occurs_at: Time.current.beginning_of_week.noon,
7 | location: "SH-216"
8 |
9 | FactoryBot.create :event,
10 | topic: "Hearings to examine the Inspector General's first report on "\
11 | "Department of Justice and Federal Bureau of Investigation actions in"\
12 | "advance of the 2016 presidential election.",
13 | occurs_at: Time.current.next_week.noon,
14 | location: "SD-538"
15 |
16 | FactoryBot.create :event,
17 | topic: "Hearings to examine the Inspector General's first report on "\
18 | "Department of Justice and Federal Bureau of Investigation actions in"\
19 | "advance of the 2016 presidential election.",
20 | occurs_at: 2.days.from_now,
21 | location: "SD-538"
22 |
23 | FactoryBot.create :event,
24 | topic: "Hearings to examine the Inspector General's first report on "\
25 | "Department of Justice and Federal Bureau of Investigation actions in"\
26 | "advance of the 2016 presidential election.",
27 | occurs_at: Time.current.next_week.end_of_hour,
28 | location: "SD-538"
29 |
30 | FactoryBot.create :event,
31 | topic: "Oversight of the ATF National Canine Division",
32 | occurs_at: Time.current.last_week.noon,
33 | location: "CHOB-334"
34 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 2018_06_10_121037) do
14 |
15 | # These are extensions that must be enabled in order to support this database
16 | enable_extension "plpgsql"
17 |
18 | create_table "events", force: :cascade do |t|
19 | t.text "topic"
20 | t.datetime "occurs_at"
21 | t.string "location"
22 | t.datetime "created_at", null: false
23 | t.datetime "updated_at", null: false
24 | t.string "raw_location"
25 | end
26 |
27 | create_table "organizations", force: :cascade do |t|
28 | t.string "name"
29 | t.string "code"
30 | t.string "website"
31 | t.datetime "created_at", null: false
32 | t.datetime "updated_at", null: false
33 | t.string "ancestry"
34 | t.index ["ancestry"], name: "index_organizations_on_ancestry"
35 | end
36 |
37 | end
38 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 |
9 | Organization.delete_all
10 | congress = Organization.create!(name: "115th Congress", website: "https://www.congress.gov/")
11 | senate = Organization.create!(name: "Senate", website: "https://www.senate.gov/", parent: congress)
12 | house = Organization.create!(name: "House", website: "https://www.house.gov/", parent: congress)
13 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/lib/assets/.keep
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/lib/tasks/.keep
--------------------------------------------------------------------------------
/lib/tasks/committees.rake:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # These rake tasks populate the House/Senate relationships from the ProPublica
4 | # free API (registration & API key required). By default, they point to the
5 | # House/Senate children of the 115th Session. This should be refactored for
6 | # extensiblity if needed later.
7 |
8 | namespace "committees" do
9 | namespace "house" do
10 | desc "Load House Committees and Subcommittees"
11 | task :load do
12 | OrganizationLoader.load(chamber: :house, parent: Organization.find_by(name: "House"))
13 | end
14 | end
15 |
16 | namespace "senate" do
17 | desc "Load Senate Committees and Subcommittees"
18 | task :load do
19 | OrganizationLoader.load(chamber: :senate, parent: Organization.find_by(name: "Senate"))
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/tasks/db.rake:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | namespace :db do
4 | desc "Populates the database with sample data (defined in db/sample)"
5 | task populate: :environment do
6 | sample_data_files = Rails.root.join("db", "sample", "**", "*.rb")
7 | Dir[sample_data_files].each { |f| require f }
8 | end
9 |
10 | desc "Resets the database and then populates it with the sample data"
11 | task repopulate: %i[reset populate]
12 | end
13 |
--------------------------------------------------------------------------------
/lib/tasks/events.rake:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | namespace "events" do
4 | namespace "house" do
5 | desc "Load upcoming house hearings"
6 | task scrape: :environment do
7 | HouseEventsScraper.run
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/log/.keep
--------------------------------------------------------------------------------
/logfile:
--------------------------------------------------------------------------------
1 | 2018-06-08 23:46:49.583 EDT [6561] FATAL: lock file "postmaster.pid" already exists
2 | 2018-06-08 23:46:49.583 EDT [6561] HINT: Is another postmaster (PID 482) running in data directory "/usr/local/var/postgres"?
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demand-progress",
3 | "private": true,
4 | "dependencies": {
5 | "@rails/webpacker": "^3.5.3",
6 | "babel-preset-react": "^6.24.1",
7 | "cypress": "^3.0.2",
8 | "lodash": "^4.17.10",
9 | "moment": "^2.22.2",
10 | "moment-timezone": "^0.5.17",
11 | "prop-types": "^15.6.1",
12 | "react": "^16.4.0",
13 | "react-dom": "^16.4.0",
14 | "react-moment": "^0.7.0"
15 | },
16 | "scripts": {
17 | "cypress:open": "cypress open"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/spec/controllers/committees_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails_helper"
4 |
5 | # This spec was generated by rspec-rails when you ran the scaffold generator.
6 | # It demonstrates how one might use RSpec to specify the controller code that
7 | # was generated by Rails when you ran the scaffold generator.
8 | #
9 | # It assumes that the implementation code is generated by the rails scaffold
10 | # generator. If you are using any extension libraries to generate different
11 | # controller code, this generated spec may or may not pass.
12 | #
13 | # It only uses APIs available in rails and/or rspec-rails. There are a number
14 | # of tools you can use to make these specs even more expressive, but we're
15 | # sticking to rails and rspec-rails APIs to keep things simple and stable.
16 | #
17 | # Compared to earlier versions of this generator, there is very limited use of
18 | # stubs and message expectations in this spec. Stubs are only used when there
19 | # is no simpler way to get a handle on the object needed for the example.
20 | # Message expectations are only used when there is no simpler way to specify
21 | # that an instance is receiving a specific message.
22 | #
23 | # Also compared to earlier versions of this generator, there are no longer any
24 | # expectations of assigns and templates rendered. These features have been
25 | # removed from Rails core in Rails 5, but can be added back in via the
26 | # `rails-controller-testing` gem.
27 |
28 | RSpec.describe OrganizationsController, type: :controller do
29 |
30 | # This should return the minimal set of attributes required to create a valid
31 | # Organization. As you add validations to Organization, be sure to
32 | # adjust the attributes here as well.
33 | let(:valid_attributes) {
34 | skip("Add a hash of attributes valid for your model")
35 | }
36 |
37 | let(:invalid_attributes) {
38 | skip("Add a hash of attributes invalid for your model")
39 | }
40 |
41 | # This should return the minimal set of values that should be in the session
42 | # in order to pass any filters (e.g. authentication) defined in
43 | # OrganizationsController. Be sure to keep this updated too.
44 | let(:valid_session) { {} }
45 |
46 | describe "GET #index" do
47 | it "returns a success response" do
48 | organization = Organization.create! valid_attributes
49 | get :index, params: {}, session: valid_session
50 | expect(response).to be_success
51 | end
52 | end
53 |
54 | describe "GET #show" do
55 | it "returns a success response" do
56 | organization = Organization.create! valid_attributes
57 | get :show, params: { id: organization.to_param }, session: valid_session
58 | expect(response).to be_success
59 | end
60 | end
61 |
62 | describe "GET #edit" do
63 | it "returns a success response" do
64 | organization = Organization.create! valid_attributes
65 | get :edit, params: { id: organization.to_param }, session: valid_session
66 | expect(response).to be_success
67 | end
68 | end
69 |
70 | describe "POST #create" do
71 | context "with valid params" do
72 | it "creates a new Organization" do
73 | expect {
74 | post :create, params: { organization: valid_attributes }, session: valid_session
75 | }.to change(Organization, :count).by(1)
76 | end
77 |
78 | it "redirects to the created organization" do
79 | post :create, params: { organization: valid_attributes }, session: valid_session
80 | expect(response).to redirect_to(Organization.last)
81 | end
82 | end
83 |
84 | context "with invalid params" do
85 | it "returns a success response (i.e. to display the 'new' template)" do
86 | post :create, params: { organization: invalid_attributes }, session: valid_session
87 | expect(response).to be_success
88 | end
89 | end
90 | end
91 |
92 | describe "PUT #update" do
93 | context "with valid params" do
94 | let(:new_attributes) {
95 | skip("Add a hash of attributes valid for your model")
96 | }
97 |
98 | it "updates the requested organization" do
99 | organization = Organization.create! valid_attributes
100 | put :update, params: { id: organization.to_param, organization: new_attributes }, session: valid_session
101 | organization.reload
102 | skip("Add assertions for updated state")
103 | end
104 |
105 | it "redirects to the organization" do
106 | organization = Organization.create! valid_attributes
107 | put :update, params: { id: organization.to_param, organization: valid_attributes }, session: valid_session
108 | expect(response).to redirect_to(organization)
109 | end
110 | end
111 |
112 | context "with invalid params" do
113 | it "returns a success response (i.e. to display the 'edit' template)" do
114 | organization = Organization.create! valid_attributes
115 | put :update, params: { id: organization.to_param, organization: invalid_attributes }, session: valid_session
116 | expect(response).to be_success
117 | end
118 | end
119 | end
120 |
121 | describe "DELETE #destroy" do
122 | it "destroys the requested organization" do
123 | organization = Organization.create! valid_attributes
124 | expect {
125 | delete :destroy, params: { id: organization.to_param }, session: valid_session
126 | }.to change(Organization, :count).by(-1)
127 | end
128 |
129 | it "redirects to the organizations list" do
130 | organization = Organization.create! valid_attributes
131 | delete :destroy, params: { id: organization.to_param }, session: valid_session
132 | expect(response).to redirect_to(organizations_url)
133 | end
134 | end
135 |
136 | end
137 |
--------------------------------------------------------------------------------
/spec/controllers/events_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails_helper"
4 |
5 | RSpec.describe EventsController, type: :controller do
6 |
7 | describe "GET #index" do
8 | it "returns a list of events from the beginning of this week forward" do
9 | old_event = create :event, :last_week
10 | events = create_list :event, 3, :next_week
11 |
12 | get :index
13 |
14 | expect(@controller.view_context.send(:events)).to include *events
15 | expect(@controller.view_context.send(:events)).to_not include old_event
16 | end
17 |
18 | it "orders the events oldest to newest" do
19 | newer_event = create :event, :next_week
20 | older_event = create :event, :this_week
21 |
22 | get :index
23 |
24 | expect(@controller.view_context.send(:events).first).to eq older_event
25 | end
26 | end
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/spec/factories/events.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :event do
5 | trait :last_week do
6 | occurs_at { Time.current.last_week }
7 | end
8 |
9 | trait :this_week do
10 | occurs_at { Time.current }
11 | end
12 |
13 | trait :next_week do
14 | occurs_at { Time.current.next_week }
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/fixtures/files/HHRG-115-ED00-20180606.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | R
4 | en-us
5 | Pursuant to Title 17 Section 105 of the United States Code, this file is not subject to copyright protection and is in the public domain.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Committee on Education and the Workforce
22 |
23 |
24 | 2018-06-06
25 | 09:45:00
26 |
27 |
28 |
29 | 2175
30 | RHOB
31 |
32 |
33 | "Examining the Policies and Priorities of the U.S. Department of Health and Human Services”
34 | The hearing originally scheduled at 10:00 a.m. will now begin at 9:45.
35 |
36 |
37 |
--------------------------------------------------------------------------------
/spec/fixtures/files/HHRG-115-RU00-20180605.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | S
4 | en-us
5 | Pursuant to Title 17 Section 105 of the United States Code, this file is not subject to copyright protection and is in the public domain.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Committee on Rules
14 |
15 |
16 | 2018-06-05
17 | 17:00:00
18 |
19 |
20 |
21 | H-313
22 | CAPITOL
23 |
24 |
25 | H.R. 5895—Energy and Water Development and Related Agencies Appropriations Act, 2019 [Energy and Water, Legislative Branch, and Military Construction and Veterans Affairs Appropriations Act, 2019] (Showing the text of H.R. 5895, H.R. 5894, and H.R. 5786 as reported by the Committee on Appropriations, as modified.) [General Debate]; H.R. 8—Water Resources Development Act of 2018 (Rules Committee Print 115-72—Showing the text of H.R. 8 as ordered reported by the Committee on Transportation and Infrastructure, with modifications.); Senate Amendment to H.R. 3294—Project Safe Neighborhoods Grant Program Authorization Act of 2018
26 |
27 |
28 |
29 | H.R. 5895—Energy and Water Development and Related Agencies Appropriations Act, 2019 [Energy and Water, Legislative Branch, and Military Construction and Veterans Affairs Appropriations Act, 2019]
30 | H.R. 5895
31 |
32 | H.R. 5895
33 | rh
34 | BR
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | Rules Committee Print 115-71— Showing the text of H.R. 5895, H.R. 5894, and H.R. 5786 as reported by the Committee on Appropriations, as modified.
43 | RCP 115-71
44 |
45 | RCP 115-71
46 | ih
47 | BR
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | H. Rept. 115-697—Report from the Committee on Appropriations to accompany H.R. 5895 [Energy and Water Development Appropriations Bill, 2019]
56 | H. Rept. 115-697
57 |
58 | H. Rept. 115-697
59 | ih
60 | CR
61 |
62 |
63 |
64 |
65 |
66 |
67 | H. Rept. 115-696—Report from the Committee on Appropriations to accompany H.R. 5894 [Legislative Branch Appropriations Bill, 2019]
68 | H. Rept. 115-696
69 |
70 | H. Rept. 115-696
71 | ih
72 | CR
73 |
74 |
75 |
76 |
77 |
78 |
79 | H. Rept. 115-673—Report from the Committee on Appropriations to accompany H.R. 5786 [Military Construction, Veterans Affairs, and Related Agencies Appropriations Bill, 2019]
80 | H. Rept. 115-673
81 |
82 | H. Rept. 115-673
83 | ih
84 | CR
85 |
86 |
87 |
88 |
89 |
90 |
91 | Rules Committee Print 115-72— Showing the text of H.R. 8 as ordered reported by the Committee on Transportation and Infrastructure, with modifications.
92 | RCP 115-72
93 |
94 | RCP 115-72
95 | ih
96 | BR
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Senate amendment to H.R. 3249—Project Safe Neighborhoods Grant Program Authorization Act of 2018
105 | Senate Amendment to H.R. 3249
106 |
107 | Senate Amendment to H.R. 3249
108 | es
109 | BR
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | H. Rept. 115-597—Report from the Committee on Appropriations to accompany H.R. 3249
118 | H. Rept. 115-597
119 |
120 | H. Rept. 115-597
121 | ih
122 | CR
123 |
124 |
125 |
126 |
127 |
128 |
129 | H. Rept. 115-708—Report from the Committee on Transportation and Infrastructure to accompany H.R. 8
130 | H. Rept. 115-708
131 |
132 | H. Rept. 115-708
133 | ih
134 | CR
135 |
136 |
137 |
138 |
139 |
140 |
141 | H.R. 8—Water Resources Development Act of 2018
142 | H.R. 8
143 |
144 | H.R. 8
145 | rh
146 | BR
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | H. Rept. 115-711—Report to accompany H. Res. 918
155 | H. Rept. 115-711
156 |
157 | H. Rept. 115-711
158 | ih
159 | CR
160 |
161 |
162 |
163 |
164 |
165 |
166 | H. Res. 918
167 |
168 | ih
169 | SD
170 | 1
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/spec/lib/house_events_scraper/event_parser_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe HouseEventsScraper::EventParser do
4 | let(:event_xml) { file_fixture("HHRG-115-RU00-20180605.xml").read }
5 |
6 | describe 'parsed attributes' do
7 | subject(:parsed_attributes) do
8 | HouseEventsScraper::EventParser.parse(event_xml)
9 | end
10 |
11 | it 'should have the correct topic' do
12 | expect(parsed_attributes[:topic]).to eq <<~TEXT.chomp
13 | H.R. 5895—Energy and Water Development and Related Agencies Appropriations Act, 2019 [Energy and Water, Legislative Branch, and Military Construction and Veterans Affairs Appropriations Act, 2019] (Showing the text of H.R. 5895, H.R. 5894, and H.R. 5786 as reported by the Committee on Appropriations, as modified.) [General Debate]; H.R. 8—Water Resources Development Act of 2018 (Rules Committee Print 115-72—Showing the text of H.R. 8 as ordered reported by the Committee on Transportation and Infrastructure, with modifications.); Senate Amendment to H.R. 3294—Project Safe Neighborhoods Grant Program Authorization Act of 2018
14 | TEXT
15 | end
16 |
17 | it 'should have the correct date and time' do
18 | expect(parsed_attributes[:occurs_at].year).to eq 2018
19 | expect(parsed_attributes[:occurs_at].month).to eq 6
20 | expect(parsed_attributes[:occurs_at].day).to eq 5
21 | expect(parsed_attributes[:occurs_at].hour).to eq 17
22 | expect(parsed_attributes[:occurs_at].minute).to eq 0
23 | end
24 |
25 | it 'should have the correct raw location' do
26 | expect(parsed_attributes[:raw_location]).to eq(
27 | {"capitol-complex": {room: "H-313", building: "CAPITOL"}}
28 | )
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/spec/lib/house_events_scraper_spec.rb:
--------------------------------------------------------------------------------
1 | require "rails_helper"
2 |
3 | RSpec.describe HouseEventsScraper do
4 | # TODO: we should use VCR or mock the web requests somehow
5 | before(:all) do
6 | HouseEventsScraper.run
7 | end
8 |
9 | it "should add some events" do
10 | skip "This actually makes web requests so let's turn it off by until we "\
11 | "fix that. If you're working on this class, just turn it on locally."
12 | expect(Event.count).to be > 0
13 | end
14 |
15 | after(:all) do
16 | Event.destroy_all
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/models/event_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails_helper"
4 |
5 | RSpec.describe Event, type: :model do
6 | describe ".this_week_forward" do
7 | it "should include events happening this week" do
8 | event = create :event, :this_week
9 |
10 | expect(Event.this_week_forward).to include event
11 | end
12 |
13 | it "should include events happening next week" do
14 | event = create :event, :next_week
15 |
16 | expect(Event.this_week_forward).to include event
17 | end
18 |
19 | it "should not include events that happened last week" do
20 | event = create :event, :last_week
21 |
22 | expect(Event.this_week_forward).to_not include event
23 | end
24 | end
25 |
26 | describe ".by_occurs_at" do
27 | it "should order the events by the `occurs_at` attribute (in asc order)" do
28 | newer_event = create :event, :next_week
29 | older_event = create :event, :this_week
30 |
31 | expect(Event.by_occurs_at).to eq [older_event, newer_event]
32 | end
33 | end
34 |
35 | describe "location" do
36 | it "is updated when a new raw location is saved" do
37 | raw_location = {"capitol-complex": {room: "H-313", building: "CAPITOL"}}
38 | event = create :event, raw_location: raw_location
39 |
40 | expect(event.location).to eq "Building: CAPITOL, Room: H-313"
41 | end
42 |
43 | it "doesn't mess up events that have a location saved directly" do
44 | event = create :event, location: "D.C. Zoo"
45 |
46 | expect(event.location).to eq "D.C. Zoo"
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/spec/models/organization_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails_helper"
4 |
5 | RSpec.describe Organization, type: :model do
6 | pending "add some examples to (or delete) #{__FILE__}"
7 | end
8 |
--------------------------------------------------------------------------------
/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This file is copied to spec/ when you run 'rails generate rspec:install'
4 | require "spec_helper"
5 | ENV["RAILS_ENV"] ||= "test"
6 | require File.expand_path("../../config/environment", __FILE__)
7 | # Prevent database truncation if the environment is production
8 | abort("The Rails environment is running in production mode!") if Rails.env.production?
9 | require "rspec/rails"
10 | # Add additional requires below this line. Rails is not loaded until this point!
11 |
12 | # Requires supporting ruby files with custom matchers and macros, etc, in
13 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
14 | # run as spec files by default. This means that files in spec/support that end
15 | # in _spec.rb will both be required and run as specs, causing the specs to be
16 | # run twice. It is recommended that you do not name files matching this glob to
17 | # end with _spec.rb. You can configure this pattern with the --pattern
18 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
19 | #
20 | # The following line is provided for convenience purposes. It has the downside
21 | # of increasing the boot-up time by auto-requiring all files in the support
22 | # directory. Alternatively, in the individual `*_spec.rb` files, manually
23 | # require only the support files necessary.
24 | #
25 | Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
26 |
27 | # Checks for pending migrations and applies them before tests are run.
28 | # If you are not using ActiveRecord, you can remove this line.
29 | ActiveRecord::Migration.maintain_test_schema!
30 |
31 | RSpec.configure do |config|
32 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
33 | config.fixture_path = "#{::Rails.root}/spec/fixtures"
34 |
35 | # If you're not using ActiveRecord, or you'd prefer not to run each of your
36 | # examples within a transaction, remove the following line or assign false
37 | # instead of true.
38 | config.use_transactional_fixtures = true
39 |
40 | # RSpec Rails can automatically mix in different behaviours to your tests
41 | # based on their file location, for example enabling you to call `get` and
42 | # `post` in specs under `spec/controllers`.
43 | #
44 | # You can disable this behaviour by removing the line below, and instead
45 | # explicitly tag your specs with their type, e.g.:
46 | #
47 | # RSpec.describe UsersController, :type => :controller do
48 | # # ...
49 | # end
50 | #
51 | # The different available types are documented in the features, such as in
52 | # https://relishapp.com/rspec/rspec-rails/docs
53 | config.infer_spec_type_from_file_location!
54 |
55 | # Filter lines from Rails gems in backtraces.
56 | config.filter_rails_from_backtrace!
57 | # arbitrary gems may also be filtered via:
58 | # config.filter_gems_from_backtrace("gem name")
59 | end
60 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all
4 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5 | # The generated `.rspec` file contains `--require spec_helper` which will cause
6 | # this file to always be loaded, without a need to explicitly require it in any
7 | # files.
8 | #
9 | # Given that it is always loaded, you are encouraged to keep this file as
10 | # light-weight as possible. Requiring heavyweight dependencies from this file
11 | # will add to the boot time of your test suite on EVERY test run, even for an
12 | # individual file that may not need all of that loaded. Instead, consider making
13 | # a separate helper file that requires the additional dependencies and performs
14 | # the additional setup, and require it from the spec files that actually need
15 | # it.
16 | #
17 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
18 | RSpec.configure do |config|
19 | # rspec-expectations config goes here. You can use an alternate
20 | # assertion/expectation library such as wrong or the stdlib/minitest
21 | # assertions if you prefer.
22 | config.expect_with :rspec do |expectations|
23 | # This option will default to `true` in RSpec 4. It makes the `description`
24 | # and `failure_message` of custom matchers include text for helper methods
25 | # defined using `chain`, e.g.:
26 | # be_bigger_than(2).and_smaller_than(4).description
27 | # # => "be bigger than 2 and smaller than 4"
28 | # ...rather than:
29 | # # => "be bigger than 2"
30 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
31 | end
32 |
33 | # rspec-mocks config goes here. You can use an alternate test double
34 | # library (such as bogus or mocha) by changing the `mock_with` option here.
35 | config.mock_with :rspec do |mocks|
36 | # Prevents you from mocking or stubbing a method that does not exist on
37 | # a real object. This is generally recommended, and will default to
38 | # `true` in RSpec 4.
39 | mocks.verify_partial_doubles = true
40 | end
41 |
42 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
43 | # have no way to turn it off -- the option exists only for backwards
44 | # compatibility in RSpec 3). It causes shared context metadata to be
45 | # inherited by the metadata hash of host groups and examples, rather than
46 | # triggering implicit auto-inclusion in groups with matching metadata.
47 | config.shared_context_metadata_behavior = :apply_to_host_groups
48 |
49 | # The settings below are suggested to provide a good initial experience
50 | # with RSpec, but feel free to customize to your heart's content.
51 | =begin
52 | # This allows you to limit a spec run to individual examples or groups
53 | # you care about by tagging them with `:focus` metadata. When nothing
54 | # is tagged with `:focus`, all examples get run. RSpec also provides
55 | # aliases for `it`, `describe`, and `context` that include `:focus`
56 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
57 | config.filter_run_when_matching :focus
58 |
59 | # Allows RSpec to persist some state between runs in order to support
60 | # the `--only-failures` and `--next-failure` CLI options. We recommend
61 | # you configure your source control system to ignore this file.
62 | config.example_status_persistence_file_path = "spec/examples.txt"
63 |
64 | # Limits the available syntax to the non-monkey patched syntax that is
65 | # recommended. For more details, see:
66 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
67 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
68 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
69 | config.disable_monkey_patching!
70 |
71 | # Many RSpec users commonly either run the entire suite or an individual
72 | # file, and it's useful to allow more verbose output when running an
73 | # individual spec file.
74 | if config.files_to_run.one?
75 | # Use the documentation formatter for detailed output,
76 | # unless a formatter has already been configured
77 | # (e.g. via a command-line flag).
78 | config.default_formatter = "doc"
79 | end
80 |
81 | # Print the 10 slowest examples and example groups at the
82 | # end of the spec run, to help surface which specs are running
83 | # particularly slow.
84 | config.profile_examples = 10
85 |
86 | # Run specs in random order to surface order dependencies. If you find an
87 | # order dependency and want to debug it, you can fix the order by providing
88 | # the seed, which is printed after each run.
89 | # --seed 1234
90 | config.order = :random
91 |
92 | # Seed global randomization in this process using the `--seed` CLI option.
93 | # Setting this allows you to use `--seed` to deterministically reproduce
94 | # test failures related to randomization by passing the same `--seed` value
95 | # as the one that triggered the failure.
96 | Kernel.srand config.seed
97 | =end
98 | end
99 |
--------------------------------------------------------------------------------
/spec/support/factory_bot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.configure do |config|
4 | config.include FactoryBot::Syntax::Methods
5 | end
6 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/tmp/.keep
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyforgood/demand-progress/255ed574cd874267c69decbf32907f8b1922edb1/vendor/.keep
--------------------------------------------------------------------------------